sqlmap 的页面相似度判断
comparison
流程分析
sqlmap 的响应页面相似度的判断函数为 comparison 位于 sqlmap/lib/request/comparison.py 下。sqlmap 进行判断主要有两种方式:
- 如果手动设置了
string,notString,regexp,code等字符串比较的方式,那么 sqlmap 就会直接进行字符串匹配或者正则匹配来判断是否满足要求,只会返回 0 或者 1. - 如果是默认的无配置情况,就会相对复杂,sqlmap 首先会判断掉 DBMS 错误页面或者响应异常等情况,之后主要分为两种情况。1. 是 nullConnection 的请求方式。2. 正常的请求方式:
- 如果是 nullConnection 的请求方式 , 即只能够拿到 content-length 页面长度。会直接拿该 content-length 长度/标准页面的 content-length 长度,获得的值即作为页面相似度。
if kb.nullConnection and pageLength: if not seqMatcher.a: errMsg = "problem occurred while retrieving original page content " errMsg += "which prevents sqlmap from continuation. Please rerun, " errMsg += "and if the problem persists turn off any optimization switches" raise SqlmapNoneDataException(errMsg) ratio = 1. * pageLength / len(seqMatcher.a) if ratio > 1.: ratio = 1. / ratio - 如果是正常请求,那么 sqlmap 首先会去除掉页面中的动态内容(例如广告等)。动态内容则是在调用 checkStability 函数流程中,通过 findDynamicContent 函数对比页面内容检出的动态元素集合。接下来要确认与原始页面的比较内容即 seq1 和 seq2。
- 如果设置了
--titles参数即,那么 sqlmap 接下来会只会根据 title 进行比较。sqlmap 会通过正则对响应页面进行匹配,通过 extractRegexResult 函数提取对应的 title 值作为后续的比较内容。# Regular expression used for extracting HTML title HTML_TITLE_REGEX = r"(?i)<title>(?P<result>[^<]+)</title>" - 否则会通过 getFilteredPageContent 函数提取响应页面的纯文本内容,即去除掉 HTML 中的 script, style 和 comment 等标签内容。
def getFilteredPageContent(page, onlyText=True, split=" "): """ Returns filtered page content without script, style and/or comments or all HTML tags >>> getFilteredPageContent(u'<html><title>foobar</title><body>test</body></html>') == "foobar test" True """ retVal = page # only if the page's charset has been successfully identified if isinstance(page, six.text_type): retVal = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>%s" % (r"|<[^>]+>|\t|\n|\r" if onlyText else ""), split, page) retVal = re.sub(r"%s{2,}" % split, split, retVal) retVal = htmlUnescape(retVal.strip().strip(split)) return retVal
- 如果设置了
- 之后会去除掉 removeReflectiveValues 函数对页面处理后可能会存在中
__REFLECTED_VALUE__值。 - 接下来如果 sqlmap 会根据 #checkStability 函数中判断该站点是一个高度动态的页面即
kb.heavilyDynamic=True的结果来调用不同的相似度计算函数 , 并返回结果。:- 如果是高度动态的页面那边会调用
SequenceMatcher.ratio函数,尝试更精准的计算相似度 - 否则首先会计算 Hash 值,尝试在缓存
kb.cache.comparison中查找是否有缓存。不存在才会调用SequenceMatcher.quick_ratio计算相似度,相对于 ration 函数计算速度会更快一点,并且对于精准度要求也没那么高。
if conf.titles: # 提取title作为比较内容 seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq2 = extractRegexResult(HTML_TITLE_REGEX, page) else: # 去除掉页面的标签内容,选取html纯文本进行比较 seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a seq2 = getFilteredPageContent(page, True) if conf.textOnly else page if seq1 is None or seq2 is None: return None seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") # checkStability中如果判断是高度动态的站点 if kb.heavilyDynamic: seq1 = seq1.split("\n") seq2 = seq2.split("\n") key = None else: key = (hash(seq1), hash(seq2)) seqMatcher.set_seq1(seq1) seqMatcher.set_seq2(seq2) if key in kb.cache.comparison: ratio = kb.cache.comparison[key] else: ratio = round(seqMatcher.quick_ratio() if not kb.heavilyDynamic else seqMatcher.ratio(), 3) if key: kb.cache.comparison[key] = ratio - 如果是高度动态的页面那边会调用
- 如果最终的相似度结果在 0.02 < ratio < 0.98 之间,并且是第一次判断那么会直接将这个相似度作为基线,用来后续的相似页面进行判断,波动的幅度在
DIFF_TOLERANCE = 0.05左右,超出则认为是存在异常。
- 如果是 nullConnection 的请求方式 , 即只能够拿到 content-length 页面长度。会直接拿该 content-length 长度/标准页面的 content-length 长度,获得的值即作为页面相似度。
参考链接
checkStability
当在尝试布尔盲注时,sqlmap 会首先使用 checkStability 函数对网站的动态性进行检测,提交页面相似的比较准确率。
- 首先 sqlmap 会再次访问这个正常的 URL,将响应内容同 初始访问时的页面
kb.originalPage进行对比。如果页面相同,那么直接判断为是静态页面即kb.pageStable=True。 - 否则 sqlmap 会尝试让使用者提供
--string或者--regex参数指定内容来对页面进行匹配。 - 如果均未指定 sqlmap 才会调用 #checkDynamicContent 函数来分析响应页面中存在的动态元素
checkDynamicContent
该函数是用来分析页面中存在动态元素的,例如广告等。
- 首先 sqlmap 会调用
SequenceMatcher.quick_ratio函数来快速计算两个页面的相似度,只有在页面相似度<UPPER_RATIO_BOUND = 0.98的情况下,会调用 #findDynamicContent 来搜索存在的动态页面内容。 - 之后不断的通过
Request.queryPage请求目标 URL,然后再判断相似度,如果相似度仍< UPPER_RATIO_BOUND = 0.98则再次调用 #findDynamicContent 函数搜索动态内容,知道相似度>0.98 为止,并且标志该目标属于动态网站kb.heavilyDynamic = True。 - 如果尝试次数过多 (>
conf.retries),则判断为网站过于变化过快too dynamiac,只通过字符串进行匹配conf.textOnly=True。
findDynamicContent
sqlmap 会通过 SequenceMatcher.get_matching_blocks 来匹配两个页面中内容相同的区块,并且去除掉长度过小的区块(默认是 40,< 2*DYNAMICITY_BOUNDARY_LENGTH)。
之后会依次分析两个区块之前的内容,该内容即为动态的内容。然后取该内容中的前面/后面 DYNAMICITY_BOUNDARY_LENGTH 的内容作为 prefix/suffix,将这两个 (prefix, suffix) 作为 key 值存入 kb.dynamicMarkings 作为动态页面的标记点,后续 removeDynamicContent 在去除动态内容时则是根据该标记来去除的。