From 472afaafa8f0d308aa8d8dc568b41c53db0680fc Mon Sep 17 00:00:00 2001 From: wang <3202024218@qq.com> Date: Sun, 29 Dec 2024 18:56:41 +0800 Subject: [PATCH] add comments to blind/inference.py --- .../lib/techniques/blind/inference.py | 243 ++++++++++-------- 1 file changed, 140 insertions(+), 103 deletions(-) diff --git a/src/sqlmap-master/lib/techniques/blind/inference.py b/src/sqlmap-master/lib/techniques/blind/inference.py index 748bbbf..f29cfe4 100644 --- a/src/sqlmap-master/lib/techniques/blind/inference.py +++ b/src/sqlmap-master/lib/techniques/blind/inference.py @@ -5,92 +5,113 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -from __future__ import division - -import re -import time - -from lib.core.agent import agent -from lib.core.common import Backend -from lib.core.common import calculateDeltaSeconds -from lib.core.common import dataToStdout -from lib.core.common import decodeDbmsHexValue -from lib.core.common import decodeIntToUnicode -from lib.core.common import filterControlChars -from lib.core.common import getCharset -from lib.core.common import getCounter -from lib.core.common import getPartRun -from lib.core.common import getTechnique -from lib.core.common import getTechniqueData -from lib.core.common import goGoodSamaritan -from lib.core.common import hashDBRetrieve -from lib.core.common import hashDBWrite -from lib.core.common import incrementCounter -from lib.core.common import isDigit -from lib.core.common import isListLike -from lib.core.common import safeStringFormat -from lib.core.common import singleTimeWarnMessage -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.data import queries -from lib.core.enums import ADJUST_TIME_DELAY -from lib.core.enums import CHARSET_TYPE -from lib.core.enums import DBMS -from lib.core.enums import PAYLOAD -from lib.core.exception import SqlmapThreadException -from lib.core.exception import SqlmapUnsupportedFeatureException -from lib.core.settings import CHAR_INFERENCE_MARK -from lib.core.settings import INFERENCE_BLANK_BREAK -from lib.core.settings import INFERENCE_EQUALS_CHAR -from lib.core.settings import INFERENCE_GREATER_CHAR -from lib.core.settings import INFERENCE_MARKER -from lib.core.settings import INFERENCE_NOT_EQUALS_CHAR -from lib.core.settings import INFERENCE_UNKNOWN_CHAR -from lib.core.settings import MAX_BISECTION_LENGTH -from lib.core.settings import MAX_REVALIDATION_STEPS -from lib.core.settings import NULL -from lib.core.settings import PARTIAL_HEX_VALUE_MARKER -from lib.core.settings import PARTIAL_VALUE_MARKER -from lib.core.settings import PAYLOAD_DELIMITER -from lib.core.settings import RANDOM_INTEGER_MARKER -from lib.core.settings import VALID_TIME_CHARS_RUN_THRESHOLD -from lib.core.threads import getCurrentThreadData -from lib.core.threads import runThreads -from lib.core.unescaper import unescaper -from lib.request.connect import Connect as Request -from lib.utils.progress import ProgressBar -from lib.utils.safe2bin import safecharencode -from lib.utils.xrange import xrange -from thirdparty import six +# 导入必要的模块 +from __future__ import division # 使用真除法,避免整数除法的问题 + +import re # 正则表达式模块 +import time # 时间相关操作 + +# 导入sqlmap自定义模块 +from lib.core.agent import agent # SQL语句构造和处理 +from lib.core.common import Backend # 数据库后端相关 +from lib.core.common import calculateDeltaSeconds # 计算时间差 +from lib.core.common import dataToStdout # 输出到标准输出 +from lib.core.common import decodeDbmsHexValue # 解码数据库十六进制值 +from lib.core.common import decodeIntToUnicode # 整数转Unicode字符 +from lib.core.common import filterControlChars # 过滤控制字符 +from lib.core.common import getCharset # 获取字符集 +from lib.core.common import getCounter # 获取计数器 +from lib.core.common import getPartRun # 获取部分运行信息 +from lib.core.common import getTechnique # 获取注入技术 +from lib.core.common import getTechniqueData # 获取注入技术数据 +from lib.core.common import goGoodSamaritan # 智能预测功能 +from lib.core.common import hashDBRetrieve # 从哈希数据库读取 +from lib.core.common import hashDBWrite # 写入哈希数据库 +from lib.core.common import incrementCounter # 增加计数器 +from lib.core.common import isDigit # 判断是否为数字 +from lib.core.common import isListLike # 判断是否为列表类型 +from lib.core.common import safeStringFormat # 安全的字符串格式化 +from lib.core.common import singleTimeWarnMessage # 单次警告消息 +from lib.core.data import conf # 配置信息 +from lib.core.data import kb # 知识库 +from lib.core.data import logger # 日志记录 +from lib.core.data import queries # SQL查询语句 +from lib.core.enums import ADJUST_TIME_DELAY # 时间延迟调整枚举 +from lib.core.enums import CHARSET_TYPE # 字符集类型枚举 +from lib.core.enums import DBMS # 数据库类型枚举 +from lib.core.enums import PAYLOAD # Payload类型枚举 +from lib.core.exception import SqlmapThreadException # 线程异常 +from lib.core.exception import SqlmapUnsupportedFeatureException # 不支持特性异常 +from lib.core.settings import CHAR_INFERENCE_MARK # 字符推断标记 +from lib.core.settings import INFERENCE_BLANK_BREAK # 空白中断标记 +from lib.core.settings import INFERENCE_EQUALS_CHAR # 等于字符标记 +from lib.core.settings import INFERENCE_GREATER_CHAR # 大于字符标记 +from lib.core.settings import INFERENCE_MARKER # 推断标记 +from lib.core.settings import INFERENCE_NOT_EQUALS_CHAR # 不等于字符标记 +from lib.core.settings import INFERENCE_UNKNOWN_CHAR # 未知字符标记 +from lib.core.settings import MAX_BISECTION_LENGTH # 最大二分长度 +from lib.core.settings import MAX_REVALIDATION_STEPS # 最大重新验证步骤 +from lib.core.settings import NULL # 空值 +from lib.core.settings import PARTIAL_HEX_VALUE_MARKER # 部分十六进制值标记 +from lib.core.settings import PARTIAL_VALUE_MARKER # 部分值标记 +from lib.core.settings import PAYLOAD_DELIMITER # Payload分隔符 +from lib.core.settings import RANDOM_INTEGER_MARKER # 随机整数标记 +from lib.core.settings import VALID_TIME_CHARS_RUN_THRESHOLD # 有效时间字符运行阈值 +from lib.core.threads import getCurrentThreadData # 获取当前线程数据 +from lib.core.threads import runThreads # 运行线程 +from lib.core.unescaper import unescaper # 反转义处理 +from lib.request.connect import Connect as Request # HTTP请求处理 +from lib.utils.progress import ProgressBar # 进度条 +from lib.utils.safe2bin import safecharencode # 安全字符编码 +from lib.utils.xrange import xrange # 兼容Python2/3的range +from thirdparty import six # Python 2/3兼容库 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 + 二分法算法,用于执行盲注SQL注入 + + 参数说明: + payload - SQL注入的payload模板 + expression - 需要注入的SQL表达式 + length - 查询结果的长度限制 + charsetType - 字符集类型 + firstChar - 起始字符位置 + lastChar - 结束字符位置 + dump - 是否导出数据 """ - abortedFlag = False - showEta = False - partialValue = u"" - finalValue = None - retrievedLength = 0 + # 初始化变量 + abortedFlag = False # 中止标志 + showEta = False # 是否显示进度条 + partialValue = u"" # 部分结果值 + finalValue = None # 最终结果值 + retrievedLength = 0 # 已获取的长度 + # 检查payload是否为空 if payload is None: return 0, None + # 根据字符集类型获取ASCII表 if charsetType is None and conf.charset: asciiTbl = sorted(set(ord(_) for _ in conf.charset)) else: asciiTbl = getCharset(charsetType) + # 获取当前线程数据 threadData = getCurrentThreadData() + + # 判断是否为基于时间的注入 timeBasedCompare = (getTechnique() 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, "") @@ -98,6 +119,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) + # 如果结果中包含部分值标记 elif PARTIAL_VALUE_MARKER in retVal: retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") @@ -105,13 +127,15 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None partialValue = retVal infoMsg = "resuming partial value: %s" % safecharencode(partialValue) logger.info(infoMsg) + # 其他情况直接使用缓存结果 else: infoMsg = "resumed: %s" % safecharencode(retVal) logger.info(infoMsg) return 0, retVal - if Backend.isDbms(DBMS.MCKOI): + # 针对不同数据库的特殊处理 + if Backend.isDbms(DBMS.MCKOI): # McKoi数据库 match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I) if match: original = queries[Backend.getIdentifiedDbms()].inference.query @@ -119,7 +143,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None payload = payload.replace(right, "(SELECT %s FROM %s)" % (right, match.group(2).strip())) expression = match.group(1).strip() - elif Backend.isDbms(DBMS.FRONTBASE): + elif Backend.isDbms(DBMS.FRONTBASE): # FrontBase数据库 match = re.search(r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I) if match: payload = payload.replace(INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR)) @@ -127,17 +151,18 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None expression = match.group(2).strip() 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用于"common prediction"特性或API调用 + if conf.predictOutput: # 如果启用了预测输出 kb.partRun = getPartRun() - elif conf.api: + elif conf.api: # 如果是API调用 kb.partRun = getPartRun(alias=False) else: kb.partRun = None - if partialValue: + # 设置起始字符位置 + if partialValue: # 如果有部分值,从部分值长度开始 firstChar = len(partialValue) - elif re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): + elif re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): # 如果是长度查询,从0开始 firstChar = 0 elif conf.firstChar is not None and (isinstance(conf.firstChar, int) or (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())): firstChar = int(conf.firstChar) - 1 @@ -148,7 +173,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: firstChar = 0 - if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): + # 设置结束字符位置 + if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): # 如果是长度查询,结束位置为0 lastChar = 0 elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())): lastChar = int(conf.lastChar) @@ -157,6 +183,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: lastChar = 0 + # 处理数据库相关的字段转换 if Backend.getDbms(): _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) nulledCastedField = agent.nullAndCastField(fieldToCastStr) @@ -165,6 +192,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: expressionUnescaped = unescaper.escape(expression) + # 处理长度参数 if isinstance(length, six.string_types) and isDigit(length) or isinstance(length, int): length = int(length) else: @@ -179,9 +207,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if length and length > MAX_BISECTION_LENGTH: length = None + # 是否显示进度条 showEta = conf.eta and isinstance(length, int) - if kb.bruteMode: + # 设置线程数 + if kb.bruteMode: # 暴力模式只用1个线程 numThreads = 1 else: numThreads = min(conf.threads or 0, length or 0) or 1 @@ -189,6 +219,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if showEta: progress = ProgressBar(maxValue=length) + # 多线程处理 if numThreads > 1: if not timeBasedCompare or kb.forceThreads: debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) @@ -196,11 +227,13 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: numThreads = 1 + # 单线程模式提示 if conf.threads == 1 and not any((timeBasedCompare, 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 any((showEta, conf.api, kb.bruteMode)): if isinstance(length, int) and numThreads > 1: dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) @@ -209,6 +242,12 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) def tryHint(idx): + """ + 尝试使用提示值进行查询 + + 参数: + idx - 当前字符位置 + """ with kb.locks.hint: hintValue = kb.hintValue @@ -235,15 +274,17 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=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 + 验证字符值是否正确 + + 参数: + idx - 字符位置 + value - 字符值 """ - 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) @@ -262,10 +303,16 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None 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 + 获取指定位置的字符 + + 参数: + idx - 字符位置 + charTbl - 字符表 + continuousOrder - 是否连续顺序 + expand - 是否扩展字符集 + shiftTable - 位移表 + retried - 重试次数 """ - result = tryHint(idx) if result: @@ -279,7 +326,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if kb.disableShiftTable: shiftTable = None elif continuousOrder and shiftTable is None: - # Used for gradual expanding into unicode charspace shiftTable = [2, 2, 3, 3, 3] if "'%s'" % CHAR_INFERENCE_MARK in payload: @@ -332,7 +378,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None 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 @@ -349,7 +394,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None 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) @@ -383,7 +427,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None 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 @@ -397,13 +440,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None 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) @@ -492,14 +529,17 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if result: return decodeIntToUnicode(candidates[0]) - # Go multi-threading (--threads > 1) + # 多线程处理(--threads > 1) if numThreads > 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.index = [firstChar] # 作为列表用于python嵌套函数作用域 threadData.shared.start = firstChar try: def blindThread(): + """ + 盲注线程函数 + """ threadData = getCurrentThreadData() while kb.threadContinue: @@ -517,7 +557,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: break - # NOTE: https://github.com/sqlmapproject/sqlmap/issues/4629 + # 注意: https://github.com/sqlmapproject/sqlmap/issues/4629 if not isListLike(threadData.shared.value): break @@ -574,8 +614,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None infoMsg = None - # If we have got one single character not correctly fetched it - # can mean that the connection to the target URL was lost + # 如果有一个字符没有正确获取,可能意味着与目标URL的连接丢失 if None in value: partialValue = "".join(value[:value.index(None)]) @@ -588,7 +627,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None if conf.verbose in (1, 2) and infoMsg and not any((showEta, conf.api, kb.bruteMode)): dataToStdout(infoMsg) - # No multi-threading (--threads = 1) + # 单线程处理(--threads = 1) else: index = firstChar threadData.shared.value = "" @@ -596,17 +635,15 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None 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 + # 常见预测功能(又名"good samaritan") + # 注意:目前仅在未设置多线程时使用 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 + # 如果common-outputs中有一个单一输出,通过等于查询输出进行检查 if commonValue is not None: - # One-shot query containing equals commonValue + # 一次性查询包含等于commonValue testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) query = getTechniqueData().vector @@ -616,7 +653,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(getTechnique()) - # Did we have luck? + # 是否成功? if result: if showEta: progress.progress(len(commonValue))