SQLMAP|阅读手记五{测试集遍历}

sqlmap可以说是目前使用人数最多,功能最复杂的注入工具。作为一款开源工具,开发者有意的让我们自行去阅读并扩充,作为一个Web狗,阅读sqlmap源码也是有必要的,更何况从软件工程的角度,sqlmap的源码部署也是很值得学习的。

CheckWaf()

   前面的文章已经讲完了sqlmap对直连数据库情况的处理。接下来就是对每个target进行测试集遍历测试。

   首先是检查网络连接的状态,然后是提取目标信息,比如url,method,data,paramkey等,接下来是对测试目标host的检测,如果在已确认漏洞host列表(kb.vulnHosts)里,就提示用户选择是否还进行深入的检测。

   经过setTargetEnv()就进入了checkWaf()的环节。

def checkWaf():
    """
    Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse
    """
    if any((conf.string, conf.notString, conf.regexp, conf.dummy, conf.offline, conf.skipWaf)):
        return None
    _ = hashDBRetrieve(HASHDB_KEYS.CHECK_WAF_RESULT, True)
    if _ is not None:
        if _:
            warnMsg = "previous heuristics detected that the target "
            warnMsg += "is protected by some kind of WAF/IPS/IDS"
            logger.critical(warnMsg)
        return _
    infoMsg = "checking if the target is protected by "
    infoMsg += "some kind of WAF/IPS/IDS"
    logger.info(infoMsg)
    retVal = False
    payload = "%d %s" % (randomInt(), IDS_WAF_CHECK_PAYLOAD)
    value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER
    value += agent.addPayloadDelimiters("%s=%s" % (randomStr(), payload))
    pushValue(conf.timeout)
    conf.timeout = IDS_WAF_CHECK_TIMEOUT
    try:
        retVal = Request.queryPage(place=PLACE.GET, value=value, getRatioValue=True, noteResponseTime=False, silent=True)[1] < IDS_WAF_CHECK_RATIO
    except SqlmapConnectionException:
        retVal = True
    finally:
        kb.matchRatio = None
        conf.timeout = popValue()
    if retVal:
        warnMsg = "heuristics detected that the target "
        warnMsg += "is protected by some kind of WAF/IPS/IDS"
        logger.critical(warnMsg)
        if not conf.identifyWaf:
            message = "do you want sqlmap to try to detect backend "
            message += "WAF/IPS/IDS? [y/N] "
            if readInput(message, default='N', boolean=True):
                conf.identifyWaf = True
        if conf.timeout == defaults.timeout:
            logger.warning("dropping timeout to %d seconds (i.e. '--timeout=%d')" % (IDS_WAF_CHECK_TIMEOUT, IDS_WAF_CHECK_TIMEOUT))
            conf.timeout = IDS_WAF_CHECK_TIMEOUT
    hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True)
    return retVal

    首先是从hashdb里查看目标有关waf的历史记录,接下来就是打入有关waf检测的payload,具体的指纹信息可以是页面延迟,网络连接的阻断等。

    接下来会让用户进行选择是否对waf进行深入的验证,在waf文件下定义了很多已知waf的检测函数,这里就遍历这些函数。

    接下来是空连接的检测。在这之后是根据不同的level跳过对某个头部位置的测试,根据用户的选择/之前的检测结果跳过对某些参数的测试。

启发式注入检测

def heuristicCheckSqlInjection(place, parameter):
    if kb.nullConnection:
        debugMsg = "heuristic check skipped because NULL connection used"
        logger.debug(debugMsg)
        return None
    origValue = conf.paramDict[place][parameter]
    paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
    prefix = ""
    suffix = ""
    randStr = ""
    if conf.prefix or conf.suffix:
        if conf.prefix:
            prefix = conf.prefix
        if conf.suffix:
            suffix = conf.suffix
    while randStr.count('\'') != 1 or randStr.count('\"') != 1:
        randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET)
    kb.heuristicMode = True
    payload = "%s%s%s" % (prefix, randStr, suffix)
    payload = agent.payload(place, parameter, newValue=payload)
    page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
    kb.heuristicPage = page
    kb.heuristicMode = False
    parseFilePaths(page)
    result = wasLastResponseDBMSError()
    infoMsg = "heuristic (basic) test shows that %s parameter " % paramType
    infoMsg += "'%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.%d%s" % (origValue, random.randint(1, 9), 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=intval($_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', boolean=True)
    elif result:
        infoMsg += "be injectable"
        if Backend.getErrorParsedDBMSes():
            infoMsg += " (possible DBMS: '%s')" % Format.getErrorParsedDBMSes()
        logger.info(infoMsg)
    else:
        infoMsg += "not be injectable"
        logger.warn(infoMsg)
    kb.heuristicMode = True
    randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH)
    value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2)
    payload = "%s%s%s" % (prefix, "'%s" % value, suffix)
    payload = agent.payload(place, parameter, newValue=payload)
    page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
    paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
    if value.lower() in (page or "").lower():
        infoMsg = "heuristic (XSS) test shows that %s parameter " % paramType
        infoMsg += "'%s' might be vulnerable to cross-site scripting attacks" % parameter
        logger.info(infoMsg)
    for match in re.finditer(FI_ERROR_REGEX, page or ""):
        if randStr1.lower() in match.group(0).lower():
            infoMsg = "heuristic (FI) test shows that %s parameter " % paramType
            infoMsg += "'%s' might be vulnerable to file inclusion attacks" % parameter
            logger.info(infoMsg)
            break
    kb.heuristicMode = False
    return kb.heuristicTest

首先是对sql注入的检测

payload = "%s%s%s" % (prefix, randStr, suffix)

randStr就是随机生成的可导致sql语句因闭合问题而报错的字符,这个payload不是用来注入的,而是将其产生的页面作为启发式注入标准页面(kb.heuristicPage),与不注入产生的正常页面(kb.originalPage)作为一个基准性对比。

接下来是一个关键变量casting

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

_()函数如下

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

FORMAT_EXCEPTION_STRINGS 是一些在Web服务中常见的sql语句关于变量类型出错的报错

('Type mismatch', 'Error converting', 'Conversion failed', 'String or binary data would be truncated', 'Failed to convert', 'unable to interpret text value', 'Input string was not in a correct format', 'System.FormatException', 'java.lang.NumberFormatException', 'ValueError: invalid literal', 'DataTypeMismatchException', 'CF_SQL_INTEGER', ' for CFSQLTYPE ', 'cfqueryparam cfsqltype', 'InvalidParamTypeException', 'Invalid parameter type', 'is not of type numeric', '<cfif Not IsNumeric(', 'invalid input syntax for integer', 'invalid input syntax for type', 'invalid number', 'character to number conversion error', 'unable to interpret text value', 'String was not recognized as a valid', 'Convert.ToInt', 'cannot be converted to a ', 'InvalidDataException')

casting为false就代表这种注入样例因为变量类型不统一而无法使用,所以用户可以选择跳过这些样例

第二个关键变量 result

result = wasLastResponseDBMSError()

函数如下

def wasLastResponseDBMSError():
    """
    Returns True if the last web request resulted in a (recognized) DBMS error page
    """
    threadData = getCurrentThreadData()
    return threadData.lastErrorPage and threadData.lastErrorPage[0] == threadData.lastRequestUID

如果启发式注入标准页面是可识别的,则返回ture,否则返回false

这也作为sqlmap启发性测试结果的标志,为true就代表可能存在注入,为false就可能不存在注入

接下来就是对于非sql注入漏洞的检测,sqlmap会随机生成可引发其他类型漏洞报错的字符,然后进行注入测试,在sqlmap源码中可以看出除了sql注入,还测试了xss与文件包含漏洞

就此,整个启发式注入检测就完毕了。

  • 用支付宝打我
  • 用微信打我

发表评论

电子邮件地址不会被公开。 必填项已用*标注