0x003 SQLMAP如何检测是否存在SQL注入
我们直接看到lib.controller.controller
中的strat()
方法
因为start()
代码较长,所以我们这里就调重要的代码进行分析
parseTargetUrl()
testSqlInj = False
if PLACE.GET in conf.parameters and not any((conf.data, conf.testParameter)):
for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]):
paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0])
if paramKey not in kb.testedParams:
testSqlInj = True
break
else:
paramKey = (conf.hostname, conf.path, None, None)
if paramKey not in kb.testedParams:
testSqlInj = True
if testSqlInj and conf.hostname in kb.vulnHosts:
if kb.skipVulnHost is None:
message = "SQL injection vulnerability has already been detected "
message += "against '%s'. Do you want to skip " % conf.hostname
message += "further tests involving it? [Y/n]"
kb.skipVulnHost = readInput(message, default='Y', boolean=True)
testSqlInj = not kb.skipVulnHost
if not testSqlInj:
infoMsg = "skipping '%s'" % targetUrl
logger.info(infoMsg)
continue
首先调用了parseTargetUrl()
函数进行了解析
def parseTargetUrl():
"""
Parse target URL and set some attributes into the configuration singleton
>>> pushValue(conf.url)
>>> conf.url = "https://www.test.com/?id=1"
>>> parseTargetUrl()
>>> conf.hostname
'www.test.com'
>>> conf.scheme
'https'
>>> conf.url = popValue()
"""
就是简单解析了一下url
区分是http
还是https
等等
testSqlInj
用来表示是否需要测试SQL注入漏洞,初始值为False
然后判断是否为GET
请求,且没有conf.data
和conf.testParameter
两个参数
如果上述条件满足就可以对每个GET
请求参数进行分析,判断是否这些参数是否在testedParameter
参数中,如果不在,则将testSqlInj
设置为True
如果需要测试的主机在已经知道存在漏洞的主机列表kb.vulnHosts
中,则进一步处理,提示用户是否需要跳过对这个主机的测试
这里正则的逻辑就是先匹配一段非=的任意长度的字符
然后用=
号分割键和值,再对值进行匹配
if testSqlInj:
try:
if place == PLACE.COOKIE:
pushValue(kb.mergeCookies)
kb.mergeCookies = False
check = heuristicCheckSqlInjection(place, parameter)
if check != HEURISTIC_TEST.POSITIVE:
if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED):
infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)
continue
infoMsg = "testing for SQL injection on %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)
# 真正开始sql注入的地方
injection = checkSqlInjection(place, parameter, value)
proceed = not kb.endDetection
injectable = False
if getattr(injection, "place", None) is not None:
if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
kb.falsePositives.append(injection)
else:
injectable = True
kb.injections.append(injection)
if not kb.alerted:
if conf.alert:
infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert
logger.info(infoMsg)
try:
process = subprocess.Popen(conf.alert, shell=True)
process.wait()
except Exception as ex:
errMsg = "error occurred while executing '%s' ('%s')" % (conf.alert, getSafeExString(ex))
logger.error(errMsg)
kb.alerted = True
# In case when user wants to end detection phase (Ctrl+C)
if not proceed:
break
msg = "%sparameter '%s' " % ("%s " % injection.place if injection.place != injection.parameter else "", injection.parameter)
msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] "
if not readInput(msg, default='N', boolean=True):
proceed = False
paramKey = (conf.hostname, conf.path, None, None)
kb.testedParams.add(paramKey)
if not injectable:
warnMsg = "%sparameter '%s' does not seem to be injectable" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.warning(warnMsg)
finally:
if place == PLACE.COOKIE:
kb.mergeCookies = popValue()
这段代码就是真正进行sql注入
的地方
首先启发性检测sql注入
,通过heuristicCheckSqlInjection()
这个函数
如果启发性测试成功了的话,check
就会等于HEURISTIC_TEST.POSITIVE
这个参数,不成功的话就跳过
我们来看看heuristicCheckSqlInjection()
函数也就是启发性sql注入
的代码
def heuristicCheckSqlInjection(place, parameter):
if conf.skipHeuristics:
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 = ""
# 由conf的值决定prefix和suffix
if conf.prefix or conf.suffix:
if conf.prefix:
prefix = conf.prefix
if conf.suffix:
suffix = conf.suffix
# rendStr 生成的启发式payload 单引号或者双引号的个数不等于1
while randStr.count('\'') != 1 or randStr.count('\"') != 1:
"""
返回具有给定字符数的随机字符串值
长度10 HEURISTIC_CHECK_ALPHABET
可以在lib/core/settings.py中找到HEURISTIC_CHECK_ALPHABET的配置
HEURISTIC_CHECK_ALPHABET = ('"', '\'', ')', '(', ',', '.')
随机生成长度为10的 且生成的内容的基础是HEURISTIC_CHECK_ALPHABET
"""
randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET)
# 启发模式设置为True
kb.heuristicMode = True
# 将生成的随机字符串拼接进入payload,如果我们conf的属性中prefix和suffix为空的话,这里直接就是randStr了
payload = "%s%s%s" % (prefix, randStr, suffix)
# 将请求类型 place 请求参数 parameter 和生成的payload也就是randStr传入agent.payload中
# 也就是?id=1 插入payload分隔符 1 后面再加上随机字符串,然后再加上payload分隔符
payload = agent.payload(place, parameter, newValue=payload)
page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
kb.heuristicPage = page
kb.heuristicMode = False
# 通过返回的页面获取可能的路径,也是正则匹配
parseFilePaths(page)
# 通过页面回显查询是否有可识别的数据库报错,如果匹配到了返回True反之则为False
result = wasLastResponseDBMSError()
infoMsg = "heuristic (basic) test shows that %sparameter '%s' might " % ("%s " % paramType if paramType != parameter else "", parameter)
def _(page):
# 启发式的请求的返回有异常字符串
# FORMAT_EXCEPTION_STRINGS同样在setting.py中
# FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "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", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "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", "Arguments are of the wrong type")
return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS)
casting = _(page) and not _(kb.originalPage) # 如果页面回显有异常字符串有True,反之为False
# 这里是启发式判断是否有 数据库报错获取数据库类型 代码执行的错误
if not casting and not result and kb.dynamicParameter and origValue.isdigit() and not kb.heavilyDynamic:
# 上面请求的回显没有报错的话就执行下面的代码
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)
# 根据上面的返回来查看是否匹配了一些异常 也就是setting中定义的值和页面的内容来判断是否存在出入
kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE
if kb.heavilyDynamic:
debugMsg = "heuristic check stopped because of heavy dynamicity"
logger.debug(debugMsg)
return kb.heuristicTest
# 报错信息
if casting:
errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type")
platform = conf.url.split('.')[-1].lower()
if platform == WEB_PLATFORM.ASP:
errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter)
elif platform == WEB_PLATFORM.ASPX:
errMsg += "int.TryParse(Request.QueryString[\"%s\"], out %s)" % (parameter, parameter)
elif platform == WEB_PLATFORM.JSP:
errMsg += "%s=Integer.parseInt(request.getParameter(\"%s\"))" % (parameter, parameter)
else:
errMsg += "$%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.warning(infoMsg)
kb.heuristicMode = True
# 禁用HTML编码
kb.disableHtmlDecoding = True
# 随机生成两个payload,长度为6
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)
# 传入探测是否存在sql注入
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
# Reference: https://bugs.python.org/issue18183
if value.upper() in (page or "").upper():
infoMsg = "heuristic (XSS) test shows that %sparameter '%s' might be vulnerable to cross-site scripting (XSS) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)
if conf.beep:
beep()
for match in re.finditer(FI_ERROR_REGEX, page or ""):
if randStr1.lower() in match.group(0).lower():
infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)
if conf.beep:
beep()
break
kb.disableHtmlDecoding = False
kb.heuristicMode = False
return kb.heuristicTest
接下来的检测sql注入的主要函数我们后续再详细分析
如果文章中内容有误,希望大佬们帮忙指正!