add comments to core

pull/3/head
wang 2 months ago
parent 79f4d4f95a
commit 6869c9f61b

@ -66,20 +66,27 @@ class Agent(object):
"""
def payloadDirect(self, query):
# This method replaces the affected parameter with the SQL
# injection statement to request
query = self.cleanupPayload(query)
# If the query starts with "AND ", replace it with "SELECT "
if query.upper().startswith("AND "):
query = re.sub(r"(?i)AND ", "SELECT ", query, 1)
# If the query starts with " UNION ALL ", remove it
elif query.upper().startswith(" UNION ALL "):
query = re.sub(r"(?i) UNION ALL ", "", query, 1)
# If the query starts with "; ", remove it
elif query.startswith("; "):
query = query.replace("; ", "", 1)
# If the database is Oracle, replace the affected parameter with a null and cast field
if Backend.getIdentifiedDbms() in (DBMS.ORACLE,): # non-standard object(s) make problems to a database connector while returned (e.g. XMLTYPE)
_, _, _, _, _, _, fieldsToCastStr, _ = self.getFields(query)
for field in fieldsToCastStr.split(','):
query = query.replace(field, self.nullAndCastField(field))
# If tamper functions are defined, apply them to the query
if kb.tamperFunctions:
for function in kb.tamperFunctions:
query = function(payload=query)
@ -92,38 +99,53 @@ class Agent(object):
injection statement to request
"""
# 如果配置了直接注入则调用payloadDirect方法
if conf.direct:
return self.payloadDirect(newValue)
retVal = ""
# 如果配置了强制where则使用配置的where
if kb.forceWhere:
where = kb.forceWhere
# 如果没有配置强制where且当前技术可用则使用当前技术的where
elif where is None and isTechniqueAvailable(getTechnique()):
where = getTechniqueData().where
# 如果kb中注入的place不为空则使用kb中的place
if kb.injection.place is not None:
place = kb.injection.place
# 如果kb中注入的parameter不为空则使用kb中的parameter
if kb.injection.parameter is not None:
parameter = kb.injection.parameter
# 获取参数字符串和参数字典
paramString = conf.parameters[place]
paramDict = conf.paramDict[place]
# 获取原始值
origValue = getUnicode(paramDict[parameter])
# 如果有新的值则转换为unicode
newValue = getUnicode(newValue) if newValue else newValue
# 判断参数是否为base64编码
base64Encoding = re.sub(r" \(.+", "", parameter) in conf.base64Parameter
# 如果place为URI或者原始值中包含BOUNDED_INJECTION_MARKER则处理URI
if place == PLACE.URI or BOUNDED_INJECTION_MARKER in origValue:
paramString = origValue
# 如果place为URI则获取URI中的参数
if place == PLACE.URI:
origValue = origValue.split(kb.customInjectionMark)[0]
# 否则,获取原始值中的参数
else:
origValue = filterNone(re.search(_, origValue.split(BOUNDED_INJECTION_MARKER)[0]) for _ in (r"\w+\Z", r"[^\"'><]+\Z", r"[^ ]+\Z"))[0].group(0)
# 获取参数名
origValue = origValue[origValue.rfind('/') + 1:]
# 去除参数名中的特殊字符
for char in ('?', '=', ':', ',', '&'):
if char in origValue:
origValue = origValue[origValue.rfind(char) + 1:]
# 如果place为CUSTOM_POST则处理POST
elif place == PLACE.CUSTOM_POST:
paramString = origValue
origValue = origValue.split(kb.customInjectionMark)[0]

@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 尝试导入cPickle模块如果失败则导入pickle模块
try:
import cPickle as pickle
except:
@ -16,36 +17,47 @@ import sys
import tempfile
import zlib
# 从lib.core.compat模块中导入xrange函数
from lib.core.compat import xrange
# 从lib.core.enums模块中导入MKSTEMP_PREFIX枚举
from lib.core.enums import MKSTEMP_PREFIX
# 从lib.core.exception模块中导入SqlmapSystemException异常
from lib.core.exception import SqlmapSystemException
# 从lib.core.settings模块中导入BIGARRAY_CHUNK_SIZE和BIGARRAY_COMPRESS_LEVEL常量
from lib.core.settings import BIGARRAY_CHUNK_SIZE
from lib.core.settings import BIGARRAY_COMPRESS_LEVEL
# 尝试获取object()对象的大小如果失败则默认大小为16字节
try:
DEFAULT_SIZE_OF = sys.getsizeof(object())
except TypeError:
DEFAULT_SIZE_OF = 16
# 定义一个函数,用于返回给定实例/对象的总大小(以字节为单位)
def _size_of(instance):
"""
Returns total size of a given instance / object (in bytes)
"""
# 获取实例/对象的大小
retval = sys.getsizeof(instance, DEFAULT_SIZE_OF)
# 如果实例/对象是字典类型,则递归计算字典中所有元素的大小
if isinstance(instance, dict):
retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items()))
# 如果实例/对象是可迭代类型,则递归计算可迭代对象中所有元素的大小
elif hasattr(instance, "__iter__"):
retval += sum(_size_of(_) for _ in instance if _ != instance)
return retval
# 定义一个辅助类,用于存储缓存块
class Cache(object):
"""
Auxiliary class used for storing cached chunks
"""
# 初始化函数,接收三个参数:索引、数据和脏标记
def __init__(self, index, data, dirty):
self.index = index
self.data = data
@ -94,9 +106,11 @@ class BigArray(list):
return self
# 添加元素到BigArray中
def append(self, value):
self.chunks[-1].append(value)
# 如果当前chunk的大小超过了设定的chunk大小则将当前chunk写入临时文件并创建一个新的chunk
if self.chunk_length == sys.maxsize:
self._size_counter += _size_of(value)
if self._size_counter >= BIGARRAY_CHUNK_SIZE:
@ -108,10 +122,12 @@ class BigArray(list):
self.chunks[-1] = filename
self.chunks.append([])
# 扩展BigArray
def extend(self, value):
for _ in value:
self.append(_)
# 从BigArray中弹出元素
def pop(self):
if len(self.chunks[-1]) < 1:
self.chunks.pop()
@ -125,6 +141,7 @@ class BigArray(list):
return self.chunks[-1].pop()
# 在BigArray中查找元素
def index(self, value):
for index in xrange(len(self)):
if self[index] == value:
@ -132,6 +149,7 @@ class BigArray(list):
return ValueError, "%s is not in list" % value
# 将chunk写入临时文件
def _dump(self, chunk):
try:
handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY)
@ -148,6 +166,7 @@ class BigArray(list):
errMsg += "writeable by the current user"
raise SqlmapSystemException(errMsg)
# 检查缓存
def _checkcache(self, index):
if (self.cache and self.cache.index != index and self.cache.dirty):
filename = self._dump(self.cache.data)
@ -162,13 +181,16 @@ class BigArray(list):
errMsg += "from a temporary file ('%s')" % ex
raise SqlmapSystemException(errMsg)
# 将BigArray序列化
def __getstate__(self):
return self.chunks, self.filenames
# 将BigArray反序列化
def __setstate__(self, state):
self.__init__()
self.chunks, self.filenames = state
# 获取BigArray中指定索引的元素
def __getitem__(self, y):
while y < 0:
y += len(self)
@ -183,6 +205,7 @@ class BigArray(list):
self._checkcache(index)
return self.cache.data[offset]
# 设置BigArray中指定索引的元素
def __setitem__(self, y, value):
index = y // self.chunk_length
offset = y % self.chunk_length
@ -195,9 +218,11 @@ class BigArray(list):
self.cache.data[offset] = value
self.cache.dirty = True
# 返回BigArray的字符串表示
def __repr__(self):
return "%s%s" % ("..." if len(self.chunks) > 1 else "", self.chunks[-1].__repr__())
# 返回BigArray的迭代器
def __iter__(self):
for i in xrange(len(self)):
try:
@ -205,5 +230,6 @@ class BigArray(list):
except IndexError:
break
# 返回BigArray的长度
def __len__(self):
return len(self.chunks[-1]) if len(self.chunks) == 1 else (len(self.chunks) - 1) * self.chunk_length + len(self.chunks[-1])

@ -270,15 +270,20 @@ class Format(object):
@rtype: C{str}
"""
# Initialize the htmlParsed variable to None
htmlParsed = None
# If the knowledge base htmlFp list is empty or the heuristic test is not positive, do nothing
if len(kb.htmlFp) == 0 or kb.heuristicTest != HEURISTIC_TEST.POSITIVE:
pass
# If the knowledge base htmlFp list has only one element, set htmlParsed to that element
elif len(kb.htmlFp) == 1:
htmlParsed = kb.htmlFp[0]
# If the knowledge base htmlFp list has more than one element, set htmlParsed to a string of all elements joined by " or "
elif len(kb.htmlFp) > 1:
htmlParsed = " or ".join(kb.htmlFp)
# Return the htmlParsed variable
return htmlParsed
@staticmethod
@ -385,34 +390,42 @@ class Backend(object):
@staticmethod
def setVersion(version):
# 如果version是字符串类型则将kb.dbmsVersion设置为version
if isinstance(version, six.string_types):
kb.dbmsVersion = [version]
# 返回kb.dbmsVersion
return kb.dbmsVersion
@staticmethod
def setVersionList(versionsList):
# 如果versionsList是列表类型则将kb.dbmsVersion设置为versionsList
if isinstance(versionsList, list):
kb.dbmsVersion = versionsList
# 如果versionsList是字符串类型则调用Backend.setVersion方法
elif isinstance(versionsList, six.string_types):
Backend.setVersion(versionsList)
# 否则,记录错误信息
else:
logger.error("invalid format of versionsList")
@staticmethod
def forceDbms(dbms, sticky=False):
# 如果kb.stickyDBMS为False则将kb.forcedDbms设置为aliasToDbmsEnum(dbms)并将kb.stickyDBMS设置为sticky
if not kb.stickyDBMS:
kb.forcedDbms = aliasToDbmsEnum(dbms)
kb.stickyDBMS = sticky
@staticmethod
def flushForcedDbms(force=False):
# 如果kb.stickyDBMS为False或者force为True则将kb.forcedDbms设置为None并将kb.stickyDBMS设置为False
if not kb.stickyDBMS or force:
kb.forcedDbms = None
kb.stickyDBMS = False
@staticmethod
def setOs(os):
# 如果os为None则返回None
if os is None:
return None
@ -510,26 +523,36 @@ class Backend(object):
dbms = None
# 如果kb为空则不执行任何操作
if not kb:
pass
# 如果kb中没有testMode并且dbmsHandler存在并且dbmsHandler._dbms存在则将dbms赋值为dbmsHandler._dbms
elif not kb.get("testMode") and conf.get("dbmsHandler") and getattr(conf.dbmsHandler, "_dbms", None):
dbms = conf.dbmsHandler._dbms
# 如果Backend.getForcedDbms()不为空则将dbms赋值为Backend.getForcedDbms()
elif Backend.getForcedDbms() is not None:
dbms = Backend.getForcedDbms()
# 如果Backend.getDbms()不为空则将dbms赋值为Backend.getDbms()
elif Backend.getDbms() is not None:
dbms = Backend.getDbms()
# 如果kb中有injection并且kb.injection.dbms存在则将dbms赋值为kb.injection.dbms
elif kb.get("injection") and kb.injection.dbms:
dbms = unArrayizeValue(kb.injection.dbms)
# 如果Backend.getErrorParsedDBMSes()存在则将dbms赋值为Backend.getErrorParsedDBMSes()
elif Backend.getErrorParsedDBMSes():
dbms = unArrayizeValue(Backend.getErrorParsedDBMSes())
# 如果conf中有dbms则将dbms赋值为conf.get("dbms")
elif conf.get("dbms"):
dbms = conf.get("dbms")
# 将dbms转换为dbmsEnum类型并返回
return aliasToDbmsEnum(dbms)
@staticmethod
def getVersion():
# 如果kb.dbmsVersion不是字符串类型则将kb.dbmsVersion展开并过滤掉None值否则将kb.dbmsVersion赋值给versions
versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
# 如果versions不为空则返回versions的第一个元素否则返回None
if not isNoneValue(versions):
return versions[0]
else:
@ -537,7 +560,9 @@ class Backend(object):
@staticmethod
def getVersionList():
# 如果kb.dbmsVersion不是字符串类型则将kb.dbmsVersion展开并过滤掉None值否则将kb.dbmsVersion赋值给versions
versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
# 如果versions不为空则返回versions否则返回None
if not isNoneValue(versions):
return versions
else:
@ -618,35 +643,48 @@ def paramToDict(place, parameters=None):
testableParameters = OrderedDict()
# 如果place在conf.parameters中并且parameters为空则将parameters设置为conf.parameters[place]
if place in conf.parameters and not parameters:
parameters = conf.parameters[place]
# 将parameters中的&替换为PARAMETER_AMP_MARKER;替换为PARAMETER_SEMICOLON_MARKER
parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters)
# 根据place的值将parameters按照不同的分隔符进行分割
if place == PLACE.COOKIE:
splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER)
else:
splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER)
# 遍历分割后的参数
for element in splitParams:
# 将PARAMETER_AMP_MARKER和PARAMETER_SEMICOLON_MARKER替换为&和;
element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element)
# 将参数按照=进行分割
parts = element.split("=")
# 如果分割后的参数长度大于等于2
if len(parts) >= 2:
# 对参数进行url解码
parameter = urldecode(parts[0].replace(" ", ""))
# 如果参数为空,则跳过
if not parameter:
continue
# 如果conf.paramDel为\n则去掉参数的最后一个字符
if conf.paramDel and conf.paramDel == '\n':
parts[-1] = parts[-1].rstrip()
# 判断参数是否在conf.testParameter中或者参数是否在conf.testParameter中或者参数是否在PLACE.COOKIE中
condition = not conf.testParameter
condition |= conf.testParameter is not None and parameter in conf.testParameter
condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0
# 如果满足条件则将参数和值添加到testableParameters中
if condition:
value = "=".join(parts[1:])
# 如果参数在conf.base64Parameter中则进行base64解码
if parameter in (conf.base64Parameter or []):
try:
kb.base64Originals[parameter] = oldValue = value
@ -660,8 +698,10 @@ def paramToDict(place, parameters=None):
testableParameters[parameter] = value
# 如果没有设置conf.multipleTargets并且参数不是conf.csrfToken则进行警告
if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)):
_ = urldecode(testableParameters[parameter], convall=True)
# 如果参数值以'结尾,并且'的数量为1或者参数值以9开头或者参数值以-开头或者参数值匹配DUMMY_USER_INJECTION并且参数不是GOOGLE_ANALYTICS_COOKIE_PREFIX则进行警告
if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX):
warnMsg = "it appears that you have provided tainted parameter values "
warnMsg += "('%s') with most likely leftover " % element
@ -672,14 +712,17 @@ def paramToDict(place, parameters=None):
message = "are you really sure that you want to continue (sqlmap could have problems)? [y/N] "
# 如果用户输入的不是y则抛出SqlmapSilentQuitException异常
if not readInput(message, default='N', boolean=True):
raise SqlmapSilentQuitException
# 如果参数值为空,则进行警告
elif not _:
warnMsg = "provided value for parameter '%s' is empty. " % parameter
warnMsg += "Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly"
logger.warning(warnMsg)
# 如果place是PLACE.POST或PLACE.GET则进行警告
if place in (PLACE.POST, PLACE.GET):
for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"):
match = re.search(regex, testableParameters[parameter])
@ -687,15 +730,19 @@ def paramToDict(place, parameters=None):
try:
candidates = OrderedDict()
# 遍历参数值
def walk(head, current=None):
if current is None:
current = head
# 如果current是列表则遍历列表
if isListLike(current):
for _ in current:
walk(head, _)
# 如果current是字典则遍历字典
elif isinstance(current, dict):
for key in current.keys():
value = current[key]
# 如果value是bool、int、float、six.string_types或者value是None、[],则进行替换
if isinstance(value, (bool, int, float, six.string_types)) or value in (None, []):
original = current[key]
if isinstance(value, bool):
@ -708,6 +755,7 @@ def paramToDict(place, parameters=None):
current[key] = "%s%s" % (value, BOUNDED_INJECTION_MARKER)
candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % json.dumps(deserialized, separators=(',', ':') if ", " not in testableParameters[parameter] else None), parameters)
current[key] = original
# 如果value是列表、元组、集合、字典则进行递归
elif isinstance(value, (list, tuple, set, dict)):
if value:
walk(head, value)
@ -736,32 +784,42 @@ def paramToDict(place, parameters=None):
except Exception:
pass
# 使用正则表达式替换testableParameters[parameter]中的匹配项
_ = re.sub(regex, r"\g<1>%s\g<%d>" % (kb.customInjectionMark, len(match.groups())), testableParameters[parameter])
# 构造提示信息
message = "it appears that provided value for %sparameter '%s' " % ("%s " % place if place != parameter else "", parameter)
message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_)
# 读取用户输入如果用户选择注入则替换testableParameters[parameter]中的匹配项
if readInput(message, default='N', boolean=True):
testableParameters[parameter] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), (r"\g<1>%s" % re.sub(regex, r"\g<1>%s\g<2>" % BOUNDED_INJECTION_MARKER, testableParameters[parameter].replace("\\", r"\\"))), parameters)
break
# 如果配置了测试参数
if conf.testParameter:
# 如果没有可测试的参数
if not testableParameters:
paramStr = ", ".join(test for test in conf.testParameter)
# 如果测试参数数量大于1
if len(conf.testParameter) > 1:
warnMsg = "provided parameters '%s' " % paramStr
warnMsg += "are not inside the %s" % place
logger.warning(warnMsg)
else:
# 如果测试参数数量为1
parameter = conf.testParameter[0]
# 如果测试参数不在USER_AGENT_ALIASES、REFERER_ALIASES、HOST_ALIASES中
if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True):
debugMsg = "provided parameter '%s' " % paramStr
debugMsg += "is not inside the %s" % place
logger.debug(debugMsg)
# 如果测试参数数量不等于可测试参数数量
elif len(conf.testParameter) != len(testableParameters):
for parameter in conf.testParameter:
# 如果测试参数不在可测试参数中
if parameter not in testableParameters:
debugMsg = "provided parameter '%s' " % parameter
debugMsg += "is not inside the %s" % place
@ -817,13 +875,16 @@ def getManualDirectories():
directories = normalizePath(directories)
# 如果配置文件中有webRoot则使用webRoot作为web服务器文档根目录
if conf.webRoot:
directories = [conf.webRoot]
infoMsg = "using '%s' as web server document root" % conf.webRoot
logger.info(infoMsg)
# 如果directories有值则使用directories作为web服务器文档根目录
elif directories:
infoMsg = "retrieved the web server document root: '%s'" % directories
logger.info(infoMsg)
# 如果以上两种情况都不满足则提示无法自动获取web服务器文档根目录
else:
warnMsg = "unable to automatically retrieve the web server "
warnMsg += "document root"
@ -831,6 +892,7 @@ def getManualDirectories():
directories = []
# 提示用户选择可写目录
message = "what do you want to use for writable directory?\n"
message += "[1] common location(s) ('%s') (default)\n" % ", ".join(root for root in defaultDocRoot)
message += "[2] custom location(s)\n"
@ -1639,48 +1701,64 @@ def parseTargetDirect():
break
# 如果kb.smokeMode为True则直接返回
if kb.smokeMode:
return
# 如果details为空则抛出SqlmapSyntaxException异常
if not details:
errMsg = "invalid target details, valid syntax is for instance "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH'"
raise SqlmapSyntaxException(errMsg)
# 遍历DBMS_DICT字典
for dbmsName, data in DBMS_DICT.items():
# 如果dbmsName等于conf.dbms或者conf.dbms.lower()在data[0]中,则执行以下操作
if dbmsName == conf.dbms or conf.dbms.lower() in data[0]:
try:
# 将conf.dbms设置为dbmsName
conf.dbms = dbmsName
# 如果dbmsName在(DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD)中,则执行以下操作
if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD):
# 如果remote为True则抛出警告信息
if remote:
warnMsg = "direct connection over the network for "
warnMsg += "%s DBMS is not supported" % dbmsName
logger.warning(warnMsg)
# 将conf.hostname设置为localhostconf.port设置为0
conf.hostname = "localhost"
conf.port = 0
# 如果remote为False则抛出SqlmapSyntaxException异常
elif not remote:
errMsg = "missing remote connection details (e.g. "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH')"
raise SqlmapSyntaxException(errMsg)
# 如果dbmsName在(DBMS.MSSQL, DBMS.SYBASE)中,则执行以下操作
if dbmsName in (DBMS.MSSQL, DBMS.SYBASE):
# 导入_mssql模块
__import__("_mssql")
# 导入pymssql模块
pymssql = __import__("pymssql")
# 如果pymssql没有__version__属性或者pymssql.__version__小于1.0.2则抛出SqlmapMissingDependence异常
if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2":
errMsg = "'%s' third-party library must be " % data[1]
errMsg += "version >= 1.0.2 to work properly. "
errMsg += "Download from '%s'" % data[2]
raise SqlmapMissingDependence(errMsg)
# 如果dbmsName等于DBMS.MYSQL则导入pymysql模块
elif dbmsName == DBMS.MYSQL:
__import__("pymysql")
# 如果dbmsName等于DBMS.PGSQL则导入psycopg2模块
elif dbmsName == DBMS.PGSQL:
__import__("psycopg2")
# 如果dbmsName等于DBMS.ORACLE则导入cx_Oracle模块
elif dbmsName == DBMS.ORACLE:
__import__("cx_Oracle")
@ -2437,21 +2515,29 @@ def getSQLSnippet(dbms, sfile, **variables):
filename = os.path.join(paths.SQLMAP_PROCS_PATH, DBMS_DIRECTORY_DICT[dbms], sfile if sfile.endswith('.sql') else "%s.sql" % sfile)
checkFile(filename)
# 读取缓存文件内容
retVal = readCachedFileContent(filename)
# 删除注释
retVal = re.sub(r"#.+", "", retVal)
# 将分号替换为"; "
retVal = re.sub(r";\s+", "; ", retVal).strip("\r\n")
# 替换变量
for _ in variables:
retVal = re.sub(r"%%%s%%" % _, variables[_].replace('\\', r'\\'), retVal)
# 替换随机字符串
for _ in re.findall(r"%RANDSTR\d+%", retVal, re.I):
retVal = retVal.replace(_, randomStr())
# 替换随机整数
for _ in re.findall(r"%RANDINT\d+%", retVal, re.I):
retVal = retVal.replace(_, randomInt())
# 查找未解析的变量
variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I)
# 如果有未解析的变量,则提示用户输入替换值
if variables:
errMsg = "unresolved variable%s '%s' in SQL file '%s'" % ("s" if len(variables) > 1 else "", ", ".join(variables), sfile)
logger.error(errMsg)
@ -2474,6 +2560,7 @@ def readCachedFileContent(filename, mode="rb"):
True
"""
# 如果文件不在缓存中,则读取文件内容并缓存
if filename not in kb.cache.content:
with kb.locks.cache:
if filename not in kb.cache.content:
@ -3935,6 +4022,7 @@ def fetchRandomAgent():
True
"""
# 如果kb.userAgents为空则从文件中加载HTTP User-Agent header值
if not kb.userAgents:
debugMsg = "loading random HTTP User-Agent header(s) from "
debugMsg += "file '%s'" % paths.USER_AGENTS
@ -3947,6 +4035,7 @@ def fetchRandomAgent():
errMsg += "file '%s'" % paths.USER_AGENTS
raise SqlmapSystemException(errMsg)
# 从kb.userAgents中随机选择一个User-Agent header值并返回
return random.sample(kb.userAgents, 1)[0]
def createGithubIssue(errMsg, excMsg):
@ -3954,6 +4043,7 @@ def createGithubIssue(errMsg, excMsg):
Automatically create a Github issue with unhandled exception information
"""
# 从文件中获取已创建的Github issue列表
try:
issues = getFileItems(paths.GITHUB_HISTORY, unique=True)
except:
@ -3961,6 +4051,7 @@ def createGithubIssue(errMsg, excMsg):
finally:
issues = set(issues)
# 对异常信息进行处理,去除不必要的字符
_ = re.sub(r"'[^']+'", "''", excMsg)
_ = re.sub(r"\s+line \d+", "", _)
_ = re.sub(r'File ".+?/(\w+\.py)', r"\g<1>", _)
@ -3968,11 +4059,14 @@ def createGithubIssue(errMsg, excMsg):
_ = re.sub(r"(Unicode[^:]*Error:).+", r"\g<1>", _)
_ = re.sub(r"= _", "= ", _)
# 计算异常信息的MD5值并取前8位作为key
key = hashlib.md5(getBytes(_)).hexdigest()[:8]
# 如果key已经在已创建的Github issue列表中则返回
if key in issues:
return
# 提示用户是否要自动创建一个新的Github issue
msg = "\ndo you want to automatically create a new (anonymized) issue "
msg += "with the unhandled exception information at "
msg += "the official Github repository? [y/N] "
@ -3981,10 +4075,12 @@ def createGithubIssue(errMsg, excMsg):
except:
choice = None
# 如果用户选择创建新的Github issue则进行后续操作
if choice:
_excMsg = None
errMsg = errMsg[errMsg.find("\n"):]
# 构造请求查询是否已存在相同的Github issue
req = _urllib.request.Request(url="https://api.github.com/search/issues?q=%s" % _urllib.parse.quote("repo:sqlmapproject/sqlmap Unhandled exception (#%s)" % key), headers={HTTP_HEADER.USER_AGENT: fetchRandomAgent()})
try:
@ -4071,9 +4167,11 @@ def listToStrValue(value):
'1, 2, 3'
"""
# 如果value是set、tuple或types.GeneratorType类型将其转换为list
if isinstance(value, (set, tuple, types.GeneratorType)):
value = list(value)
# 如果value是list类型将其转换为字符串并去掉首尾的方括号
if isinstance(value, list):
retVal = value.__str__().lstrip('[').rstrip(']')
else:
@ -4146,62 +4244,97 @@ def removeReflectiveValues(content, payload, suppressWarning=False):
value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX)
return value
# 将payload中的PAYLOAD_DELIMITER替换为空字符串并使用urldecode解码然后使用getUnicode转换为Unicode编码
payload = getUnicode(urldecode(payload.replace(PAYLOAD_DELIMITER, ""), convall=True))
# 使用filterStringValue函数过滤payload中的字符串并使用encodeStringEscape函数进行编码然后使用_函数进行转换
regex = _(filterStringValue(payload, r"[A-Za-z0-9]", encodeStringEscape(REFLECTED_REPLACEMENT_REGEX)))
# 如果regex不等于payload
if regex != payload:
# 使用filterNone函数过滤regex中的空字符串并使用REFLECTED_REPLACEMENT_REGEX进行分割然后使用all函数检查分割后的字符串是否都在content中
if all(part.lower() in content.lower() for part in filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))[1:]): # fast optimization check
# 使用REFLECTED_REPLACEMENT_REGEX进行分割
parts = regex.split(REFLECTED_REPLACEMENT_REGEX)
# Note: naive approach
# 将content中的payload替换为REFLECTED_VALUE_MARKER
retVal = content.replace(payload, REFLECTED_VALUE_MARKER)
# 将content中的payload的开头替换为REFLECTED_VALUE_MARKER
retVal = retVal.replace(re.sub(r"\A\w+", "", payload), REFLECTED_VALUE_MARKER)
# 如果分割后的字符串长度大于REFLECTED_MAX_REGEX_PARTS
if len(parts) > REFLECTED_MAX_REGEX_PARTS: # preventing CPU hogs
# 使用REFLECTED_REPLACEMENT_REGEX进行分割并使用join函数进行连接
regex = _("%s%s%s" % (REFLECTED_REPLACEMENT_REGEX.join(parts[:REFLECTED_MAX_REGEX_PARTS // 2]), REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX.join(parts[-REFLECTED_MAX_REGEX_PARTS // 2:])))
# 使用filterNone函数过滤regex中的空字符串并使用REFLECTED_REPLACEMENT_REGEX进行分割
parts = filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))
# 如果regex以REFLECTED_REPLACEMENT_REGEX开头
if regex.startswith(REFLECTED_REPLACEMENT_REGEX):
# 使用REFLECTED_BORDER_REGEX和regex[len(REFLECTED_REPLACEMENT_REGEX):]进行连接
regex = r"%s%s" % (REFLECTED_BORDER_REGEX, regex[len(REFLECTED_REPLACEMENT_REGEX):])
else:
# 使用\b和regex进行连接
regex = r"\b%s" % regex
# 如果regex以REFLECTED_REPLACEMENT_REGEX结尾
if regex.endswith(REFLECTED_REPLACEMENT_REGEX):
# 使用regex[:-len(REFLECTED_REPLACEMENT_REGEX)]和REFLECTED_BORDER_REGEX进行连接
regex = r"%s%s" % (regex[:-len(REFLECTED_REPLACEMENT_REGEX)], REFLECTED_BORDER_REGEX)
else:
# 使用regex和\b进行连接
regex = r"%s\b" % regex
# 创建一个列表用于存储retVal
_retVal = [retVal]
# 定义一个函数用于替换retVal中的regex
def _thread(regex):
try:
# 使用re.sub函数替换retVal中的regex并使用REFLECTED_VALUE_MARKER进行替换
_retVal[0] = re.sub(r"(?i)%s" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
# 如果分割后的字符串长度大于2
if len(parts) > 2:
# 使用REFLECTED_REPLACEMENT_REGEX进行分割并使用join函数进行连接
regex = REFLECTED_REPLACEMENT_REGEX.join(parts[1:])
# 使用re.sub函数替换retVal中的regex并使用REFLECTED_VALUE_MARKER进行替换
_retVal[0] = re.sub(r"(?i)\b%s\b" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
except KeyboardInterrupt:
raise
except:
pass
# 创建一个线程用于执行_thread函数
thread = threading.Thread(target=_thread, args=(regex,))
# 设置线程为守护线程
thread.daemon = True
# 启动线程
thread.start()
# 等待线程执行完毕超时时间为REFLECTED_REPLACEMENT_TIMEOUT
thread.join(REFLECTED_REPLACEMENT_TIMEOUT)
# 如果线程还在运行
if thread.is_alive():
# 将kb.reflectiveMechanism设置为False
kb.reflectiveMechanism = False
# 将retVal设置为content
retVal = content
# 如果不抑制警告
if not suppressWarning:
# 打印debugMsg
debugMsg = "turning off reflection removal mechanism (because of timeouts)"
logger.debug(debugMsg)
else:
# 将retVal设置为_retVal[0]
retVal = _retVal[0]
# 如果retVal不等于content
if retVal != content:
# 将kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT]加1
kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1
# 如果不抑制警告
if not suppressWarning:
warnMsg = "reflective value(s) found and filtering out"
singleTimeWarnMessage(warnMsg)
@ -4367,6 +4500,7 @@ def isNullValue(value):
False
"""
# 判断value是否具有upper()方法并且value的大写等于NULL
return hasattr(value, "upper") and value.upper() == NULL
def expandMnemonics(mnemonics, parser, args):
@ -4374,19 +4508,23 @@ def expandMnemonics(mnemonics, parser, args):
Expands mnemonic options
"""
# 定义一个MnemonicNode类用于存储选项
class MnemonicNode(object):
def __init__(self):
self.next = {}
self.current = []
# 初始化头节点和指针
head = MnemonicNode()
pointer = None
# 遍历parser中的option_groups
for group in parser.option_groups:
for option in group.option_list:
for opt in option._long_opts + option._short_opts:
pointer = head
# 遍历opt中的每个字符
for char in opt:
if char == "-":
continue
@ -4396,12 +4534,14 @@ def expandMnemonics(mnemonics, parser, args):
pointer = pointer.next[char]
pointer.current.append(option)
# 遍历mnemonics中的每个选项
for mnemonic in (mnemonics or "").split(','):
found = None
name = mnemonic.split('=')[0].replace('-', "").strip()
value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None
pointer = head
# 遍历name中的每个字符
for char in name:
if char in pointer.next:
pointer = pointer.next[char]
@ -4409,10 +4549,12 @@ def expandMnemonics(mnemonics, parser, args):
pointer = None
break
# 如果pointer为None或head则抛出异常
if pointer in (None, head):
errMsg = "mnemonic '%s' can't be resolved to any parameter name" % name
raise SqlmapSyntaxException(errMsg)
# 如果pointer.current的长度大于1则说明有多个选项需要进行解析
elif len(pointer.current) > 1:
options = {}
@ -4422,26 +4564,32 @@ def expandMnemonics(mnemonics, parser, args):
if opt.startswith(name):
options[opt] = option
# 如果options为空则说明没有找到对应的选项进行警告
if not options:
warnMsg = "mnemonic '%s' can't be resolved" % name
logger.warning(warnMsg)
# 如果name在options中则说明找到了对应的选项进行调试
elif name in options:
found = name
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
logger.debug(debugMsg)
# 否则,说明有多个选项,进行警告,并选择最短的选项
else:
found = sorted(options.keys(), key=len)[0]
warnMsg = "detected ambiguity (mnemonic '%s' can be resolved to any of: %s). " % (name, ", ".join("'%s'" % key for key in options))
warnMsg += "Resolved to shortest of those ('%s')" % found
logger.warning(warnMsg)
# 如果找到了对应的选项,则进行赋值
if found:
found = options[found]
# 如果pointer.current的长度等于1则说明只有一个选项进行调试
else:
found = pointer.current[0]
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
logger.debug(debugMsg)
# 如果找到了对应的选项,则进行赋值
if found:
try:
value = found.convert_value(found, value)
@ -4468,13 +4616,18 @@ def safeCSValue(value):
'foobar'
"""
# 初始化返回值
retVal = value
# 如果value不为空并且是字符串类型
if retVal and isinstance(retVal, six.string_types):
# 如果value的第一个字符和最后一个字符不是双引号并且value中包含csv分隔符、双引号或换行符
if not (retVal[0] == retVal[-1] == '"'):
if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')):
# 将value中的双引号替换为两个双引号并在value的两端加上双引号
retVal = '"%s"' % retVal.replace('"', '""')
# 返回处理后的value
return retVal
def filterPairValues(values):
@ -4485,11 +4638,15 @@ def filterPairValues(values):
[[1, 2], [4, 5]]
"""
# 初始化返回值
retVal = []
# 如果values不为空并且是可迭代的
if not isNoneValue(values) and hasattr(values, '__iter__'):
# 遍历values中的每个value
retVal = [value for value in values if isinstance(value, (tuple, list, set)) and len(value) == 2]
# 返回处理后的values
return retVal
def randomizeParameterValue(value):
@ -5517,6 +5674,7 @@ def unsafeVariableNaming(value):
True
"""
# 如果value以EVALCODE_ENCODED_PREFIX开头则解码value
if value.startswith(EVALCODE_ENCODED_PREFIX):
value = decodeHex(value[len(EVALCODE_ENCODED_PREFIX):], binary=False)
@ -5532,6 +5690,7 @@ def firstNotNone(*args):
retVal = None
# 遍历args找到第一个不为None的值
for _ in args:
if _ is not None:
retVal = _
@ -5549,6 +5708,7 @@ def removePostHintPrefix(value):
'id'
"""
# 使用正则表达式去除value中的POST提示前缀
return re.sub(r"\A(%s) " % '|'.join(re.escape(__) for __ in getPublicTypeMembers(POST_HINT, onlyValues=True)), "", value)
def chunkSplitPostData(data):

@ -167,9 +167,13 @@ class WichmannHill(random.Random):
self.__whseed(x, y, z)
def patchHeaders(headers):
# 如果headers不为空且没有headers属性
if headers is not None and not hasattr(headers, "headers"):
# 如果headers是字典类型
if isinstance(headers, dict):
# 定义一个类,继承自字典
class _(dict):
# 重写__getitem__方法将key转换为小写后进行比较
def __getitem__(self, key):
for key_ in self:
if key_.lower() == key.lower():
@ -177,14 +181,17 @@ def patchHeaders(headers):
raise KeyError(key)
# 重写get方法如果key不存在返回默认值
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
# 将headers转换为_类
headers = _(headers)
# 将headers转换为字符串列表
headers.headers = ["%s: %s\r\n" % (header, headers[header]) for header in headers]
return headers
@ -197,10 +204,13 @@ def cmp(a, b):
1
"""
# 如果a小于b返回-1
if a < b:
return -1
# 如果a大于b返回1
elif a > b:
return 1
# 如果a等于b返回0
else:
return 0
@ -211,13 +221,17 @@ def choose_boundary():
True
"""
# 定义一个空字符串
retval = ""
# 尝试生成一个32位的随机字符串
try:
retval = uuid.uuid4().hex
# 如果uuid模块不存在则使用random模块生成32位的随机字符串
except AttributeError:
retval = "".join(random.sample("0123456789abcdef", 1)[0] for _ in xrange(32))
# 返回生成的32位随机字符串
return retval
# Reference: http://python3porting.com/differences.html
@ -245,36 +259,47 @@ def cmp_to_key(mycmp):
self.obj = obj
def __lt__(self, other):
"""小于号比较"""
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
"""大于号比较"""
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
"""等于号比较"""
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
"""小于等于号比较"""
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
"""大于等于号比较"""
return mycmp(self.obj, other.obj) >= 0
def __ne__(self, other):
"""不等于号比较"""
return mycmp(self.obj, other.obj) != 0
def __hash__(self):
"""哈希函数"""
raise TypeError('hash not implemented')
return K
# Note: patch for Python 2.6
if not hasattr(functools, "cmp_to_key"):
# 如果functools模块中没有cmp_to_key函数则定义cmp_to_key函数
functools.cmp_to_key = cmp_to_key
if sys.version_info >= (3, 0):
# 如果Python版本大于等于3.0则将xrange函数替换为range函数
xrange = range
# 将buffer函数替换为memoryview函数
buffer = memoryview
else:
# 如果Python版本小于3.0则保持xrange和buffer函数不变
xrange = xrange
buffer = buffer
@ -298,17 +323,26 @@ def LooseVersion(version):
8.000022
"""
# 使用正则表达式匹配版本号
match = re.search(r"\A(\d[\d.]*)", version or "")
if match:
# 如果匹配成功则将result初始化为0
result = 0
# 获取匹配到的第一个分组
value = match.group(1)
# 将权重初始化为1.0
weight = 1.0
# 将value去掉首尾的.,并按.分割成多个部分
for part in value.strip('.').split('.'):
# 如果部分是数字则将其转换为整数并乘以权重加到result中
if part.isdigit():
result += int(part) * weight
# 将权重乘以0.001
weight *= 1e-3
else:
# 如果匹配不成功则将result设置为NaN
result = float("NaN")
# 返回result
return result

@ -5,34 +5,38 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 尝试导入cPickle(一个更快的pickle实现),如果失败则导入普通的pickle
try:
import cPickle as pickle
except:
import pickle
import base64
import binascii
import codecs
import json
import re
import sys
import time
from lib.core.bigarray import BigArray
from lib.core.compat import xrange
from lib.core.data import conf
from lib.core.data import kb
from lib.core.settings import INVALID_UNICODE_PRIVATE_AREA
from lib.core.settings import IS_TTY
from lib.core.settings import IS_WIN
from lib.core.settings import NULL
from lib.core.settings import PICKLE_PROTOCOL
from lib.core.settings import SAFE_HEX_MARKER
from lib.core.settings import UNICODE_ENCODING
from thirdparty import six
from thirdparty.six import unichr as _unichr
from thirdparty.six.moves import collections_abc as _collections
# 导入所需的标准库
import base64 # 用于Base64编码/解码
import binascii # 用于二进制和ASCII转换
import codecs # 用于编码转换
import json # 用于JSON处理
import re # 用于正则表达式
import sys # 用于系统相关操作
import time # 用于时间相关操作
# 导入自定义模块和第三方库
from lib.core.bigarray import BigArray # 用于处理大型数组
from lib.core.compat import xrange # 兼容Python2/3的range函数
from lib.core.data import conf # 配置数据
from lib.core.data import kb # 知识库数据
from lib.core.settings import INVALID_UNICODE_PRIVATE_AREA # Unicode私有区域设置
from lib.core.settings import IS_TTY # 是否为终端环境
from lib.core.settings import IS_WIN # 是否为Windows系统
from lib.core.settings import NULL # 空值常量
from lib.core.settings import PICKLE_PROTOCOL # pickle协议版本
from lib.core.settings import SAFE_HEX_MARKER # 安全的十六进制标记
from lib.core.settings import UNICODE_ENCODING # Unicode编码设置
from thirdparty import six # Python 2/3兼容库
from thirdparty.six import unichr as _unichr # 兼容的unichr函数
from thirdparty.six.moves import collections_abc as _collections # 集合类型
# 尝试导入HTML转义函数,适配不同Python版本
try:
from html import escape as htmlEscape
except ImportError:
@ -40,8 +44,14 @@ except ImportError:
def base64pickle(value):
"""
Serializes (with pickle) and encodes to Base64 format supplied (binary) value
将输入值序列化(使用pickle)并编码为Base64格式
参数:
value: 要序列化和编码的值
返回:
Base64编码的字符串
示例:
>>> base64unpickle(base64pickle([1, 2, 3])) == [1, 2, 3]
True
"""
@ -49,23 +59,33 @@ def base64pickle(value):
retVal = None
try:
# 尝试使用指定协议进行pickle序列化,然后Base64编码
retVal = encodeBase64(pickle.dumps(value, PICKLE_PROTOCOL), binary=False)
except:
# 如果失败,发出警告
warnMsg = "problem occurred while serializing "
warnMsg += "instance of a type '%s'" % type(value)
singleTimeWarnMessage(warnMsg)
try:
# 尝试不指定协议进行序列化
retVal = encodeBase64(pickle.dumps(value), binary=False)
except:
# 如果还是失败,则将值转为字符串后再序列化
retVal = encodeBase64(pickle.dumps(str(value), PICKLE_PROTOCOL), binary=False)
return retVal
def base64unpickle(value):
"""
Decodes value from Base64 to plain format and deserializes (with pickle) its content
将Base64编码的值解码并反序列化(使用pickle)
参数:
value: Base64编码的字符串
返回:
反序列化后的Python对象
示例:
>>> type(base64unpickle('gAJjX19idWlsdGluX18Kb2JqZWN0CnEBKYFxAi4=')) == object
True
"""
@ -73,16 +93,24 @@ def base64unpickle(value):
retVal = None
try:
# 尝试解码并反序列化
retVal = pickle.loads(decodeBase64(value))
except TypeError:
# 如果失败,尝试将输入转换为bytes后再处理
retVal = pickle.loads(decodeBase64(bytes(value)))
return retVal
def htmlUnescape(value):
"""
Returns (basic conversion) HTML unescaped value
将HTML转义的字符转换回原始字符
参数:
value: 包含HTML转义字符的字符串
返回:
转换后的字符串
示例:
>>> htmlUnescape('a&lt;b') == 'a<b'
True
"""
@ -90,35 +118,56 @@ def htmlUnescape(value):
retVal = value
if value and isinstance(value, six.string_types):
# 定义HTML转义字符的替换规则
replacements = (("&lt;", '<'), ("&gt;", '>'), ("&quot;", '"'), ("&nbsp;", ' '), ("&amp;", '&'), ("&apos;", "'"))
# 逐个替换HTML转义字符
for code, value in replacements:
retVal = retVal.replace(code, value)
try:
# 处理十六进制格式的HTML实体(如&#x41;)
retVal = re.sub(r"&#x([^ ;]+);", lambda match: _unichr(int(match.group(1), 16)), retVal)
except (ValueError, OverflowError):
pass
return retVal
def singleTimeWarnMessage(message): # Cross-referenced function
def singleTimeWarnMessage(message): # 交叉引用的函数
"""
向标准输出打印一次性警告消息
"""
sys.stdout.write(message)
sys.stdout.write("\n")
sys.stdout.flush()
def filterNone(values): # Cross-referenced function
def filterNone(values): # 交叉引用的函数
"""
过滤掉可迭代对象中的None值
"""
return [_ for _ in values if _] if isinstance(values, _collections.Iterable) else values
def isListLike(value): # Cross-referenced function
def isListLike(value): # 交叉引用的函数
"""
判断一个值是否类似列表(list, tuple, set或BigArray)
"""
return isinstance(value, (list, tuple, set, BigArray))
def shellExec(cmd): # Cross-referenced function
def shellExec(cmd): # 交叉引用的函数
"""
执行shell命令(未实现)
"""
raise NotImplementedError
def jsonize(data):
"""
Returns JSON serialized data
将数据序列化为JSON格式
参数:
data: 要序列化的数据
返回:
JSON字符串
示例:
>>> jsonize({'foo':'bar'})
'{\\n "foo": "bar"\\n}'
"""
@ -127,8 +176,14 @@ def jsonize(data):
def dejsonize(data):
"""
Returns JSON deserialized data
将JSON字符串反序列化为Python对象
参数:
data: JSON字符串
返回:
Python对象
示例:
>>> dejsonize('{\\n "foo": "bar"\\n}') == {u'foo': u'bar'}
True
"""
@ -137,25 +192,42 @@ def dejsonize(data):
def rot13(data):
"""
Returns ROT13 encoded/decoded text
对文本进行ROT13编码/解码
ROT13是一种简单的替换密码,将字母移动13位
参数:
data: 要编码/解码的文本
返回:
编码/解码后的文本
示例:
>>> rot13('foobar was here!!')
'sbbone jnf urer!!'
>>> rot13('sbbone jnf urer!!')
'foobar was here!!'
"""
# Reference: https://stackoverflow.com/a/62662878
# 参考: https://stackoverflow.com/a/62662878
retVal = ""
# 创建字母表(包含小写和大写字母各重复一次,用于13位移动)
alphabit = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"
# 对每个字符进行处理
for char in data:
# 如果是字母则移动13位,否则保持不变
retVal += alphabit[alphabit.index(char) + 13] if char in alphabit else char
return retVal
def decodeHex(value, binary=True):
"""
Returns a decoded representation of provided hexadecimal value
将十六进制值解码为原始形式
参数:
value: 十六进制字符串
binary: 是否返回二进制格式
返回:
解码后的值
示例:
>>> decodeHex("313233") == b"123"
True
>>> decodeHex("313233", binary=False) == u"123"
@ -164,17 +236,22 @@ def decodeHex(value, binary=True):
retVal = value
# 如果输入是二进制格式,转换为文本
if isinstance(value, six.binary_type):
value = getText(value)
# 如果以"0x"开头,去掉这个前缀
if value.lower().startswith("0x"):
value = value[2:]
try:
# 尝试使用codecs解码
retVal = codecs.decode(value, "hex")
except LookupError:
# 如果失败,使用binascii解码
retVal = binascii.unhexlify(value)
# 如果不需要二进制格式,转换为文本
if not binary:
retVal = getText(retVal)
@ -182,8 +259,15 @@ def decodeHex(value, binary=True):
def encodeHex(value, binary=True):
"""
Returns a encoded representation of provided string value
将值编码为十六进制格式
参数:
value: 要编码的值
binary: 是否返回二进制格式
返回:
十六进制编码的值
示例:
>>> encodeHex(b"123") == b"313233"
True
>>> encodeHex("123", binary=False)
@ -192,17 +276,22 @@ def encodeHex(value, binary=True):
True
"""
# 如果是整数,转换为Unicode字符
if isinstance(value, int):
value = six.unichr(value)
# 如果是Unicode字符串,编码为bytes
if isinstance(value, six.text_type):
value = value.encode(UNICODE_ENCODING)
try:
# 尝试使用codecs编码
retVal = codecs.encode(value, "hex")
except LookupError:
# 如果失败,使用binascii编码
retVal = binascii.hexlify(value)
# 如果不需要二进制格式,转换为文本
if not binary:
retVal = getText(retVal)
@ -210,8 +299,16 @@ def encodeHex(value, binary=True):
def decodeBase64(value, binary=True, encoding=None):
"""
Returns a decoded representation of provided Base64 value
将Base64编码的值解码
参数:
value: Base64编码的字符串
binary: 是否返回二进制格式
encoding: 指定编码
返回:
解码后的值
示例:
>>> decodeBase64("MTIz") == b"123"
True
>>> decodeBase64("MTIz", binary=False)
@ -229,21 +326,26 @@ def decodeBase64(value, binary=True, encoding=None):
if value is None:
return None
# 设置填充字符
padding = b'=' if isinstance(value, bytes) else '='
# Reference: https://stackoverflow.com/a/49459036
# 参考: https://stackoverflow.com/a/49459036
# 如果没有填充字符,添加填充
if not value.endswith(padding):
value += 3 * padding
# Reference: https://en.wikipedia.org/wiki/Base64#URL_applications
# Reference: https://perldoc.perl.org/MIME/Base64.html
# 参考: https://en.wikipedia.org/wiki/Base64#URL_applications
# 参考: https://perldoc.perl.org/MIME/Base64.html
# 将URL安全的Base64字符替换为标准Base64字符
if isinstance(value, bytes):
value = value.replace(b'-', b'+').replace(b'_', b'/')
else:
value = value.replace('-', '+').replace('_', '/')
# 解码Base64
retVal = base64.b64decode(value)
# 如果不需要二进制格式,转换为文本
if not binary:
retVal = getText(retVal, encoding)
@ -251,8 +353,18 @@ def decodeBase64(value, binary=True, encoding=None):
def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False):
"""
Returns a decoded representation of provided Base64 value
将值编码为Base64格式
参数:
value: 要编码的值
binary: 是否返回二进制格式
encoding: 指定编码
padding: 是否添加填充字符
safe: 是否使用URL安全的字符
返回:
Base64编码的值
示例:
>>> encodeBase64(b"123") == b"MTIz"
True
>>> encodeBase64(u"1234", binary=False)
@ -266,24 +378,30 @@ def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False):
if value is None:
return None
# 如果是Unicode字符串,编码为bytes
if isinstance(value, six.text_type):
value = value.encode(encoding or UNICODE_ENCODING)
# 编码为Base64
retVal = base64.b64encode(value)
# 如果不需要二进制格式,转换为文本
if not binary:
retVal = getText(retVal, encoding)
# 如果需要URL安全格式
if safe:
padding = False
# Reference: https://en.wikipedia.org/wiki/Base64#URL_applications
# Reference: https://perldoc.perl.org/MIME/Base64.html
# 参考: https://en.wikipedia.org/wiki/Base64#URL_applications
# 参考: https://perldoc.perl.org/MIME/Base64.html
# 将标准Base64字符替换为URL安全的字符
if isinstance(retVal, bytes):
retVal = retVal.replace(b'+', b'-').replace(b'/', b'_')
else:
retVal = retVal.replace('+', '-').replace('/', '_')
# 如果不需要填充,移除填充字符
if not padding:
retVal = retVal.rstrip(b'=' if isinstance(retVal, bytes) else '=')
@ -291,47 +409,71 @@ def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False):
def getBytes(value, encoding=None, errors="strict", unsafe=True):
"""
Returns byte representation of provided Unicode value
将Unicode值转换为字节表示
参数:
value: Unicode字符串
encoding: 指定编码
errors: 错误处理方式
unsafe: 是否允许不安全的字符
返回:
字节表示
示例:
>>> getBytes(u"foo\\\\x01\\\\x83\\\\xffbar") == b"foo\\x01\\x83\\xffbar"
True
"""
retVal = value
# 如果没有指定编码,使用配置中的编码或默认编码
if encoding is None:
encoding = conf.get("encoding") or UNICODE_ENCODING
# 验证编码是否有效
try:
codecs.lookup(encoding)
except (LookupError, TypeError):
encoding = UNICODE_ENCODING
# 如果是Unicode字符串
if isinstance(value, six.text_type):
if INVALID_UNICODE_PRIVATE_AREA:
if unsafe:
# 处理Unicode私有区域字符
for char in xrange(0xF0000, 0xF00FF + 1):
value = value.replace(_unichr(char), "%s%02x" % (SAFE_HEX_MARKER, char - 0xF0000))
# 编码为字节
retVal = value.encode(encoding, errors)
if unsafe:
# 处理安全的十六进制标记
retVal = re.sub(r"%s([0-9a-f]{2})" % SAFE_HEX_MARKER, lambda _: decodeHex(_.group(1)), retVal)
else:
try:
# 尝试使用指定编码
retVal = value.encode(encoding, errors)
except UnicodeError:
# 如果失败,使用Unicode编码并替换无法编码的字符
retVal = value.encode(UNICODE_ENCODING, errors="replace")
if unsafe:
# 处理转义序列
retVal = re.sub(b"\\\\x([0-9a-f]{2})", lambda _: decodeHex(_.group(1)), retVal)
return retVal
def getOrds(value):
"""
Returns ORD(...) representation of provided string value
返回字符串中每个字符的序号(ord)
参数:
value: 字符串
返回:
序号值列表
示例:
>>> getOrds(u'fo\\xf6bar')
[102, 111, 246, 98, 97, 114]
>>> getOrds(b"fo\\xc3\\xb6bar")
@ -342,8 +484,16 @@ def getOrds(value):
def getUnicode(value, encoding=None, noneToNull=False):
"""
Returns the unicode representation of the supplied value
返回值的Unicode表示
参数:
value: 要转换的值
encoding: 指定编码
noneToNull: 是否将None转换为NULL
返回:
Unicode字符串
示例:
>>> getUnicode('test') == u'test'
True
>>> getUnicode(1) == u'1'
@ -352,18 +502,22 @@ def getUnicode(value, encoding=None, noneToNull=False):
True
"""
# Best position for --time-limit mechanism
# 检查时间限制
if conf.get("timeLimit") and kb.get("startTime") and (time.time() - kb.startTime > conf.timeLimit):
raise SystemExit
# 如果需要将None转换为NULL
if noneToNull and value is None:
return NULL
# 如果已经是Unicode字符串,直接返回
if isinstance(value, six.text_type):
return value
# 如果是字节字符串
elif isinstance(value, six.binary_type):
# Heuristics (if encoding not explicitly specified)
# 获取可能的编码列表
candidates = filterNone((encoding, kb.get("pageEncoding") if kb.get("originalPage") else None, conf.get("encoding"), UNICODE_ENCODING, sys.getfilesystemencoding()))
# 根据内容特征调整编码优先级
if all(_ in value for _ in (b'<', b'>')):
pass
elif any(_ in value for _ in (b":\\", b'/', b'.')) and b'\n' not in value:
@ -371,6 +525,7 @@ def getUnicode(value, encoding=None, noneToNull=False):
elif conf.get("encoding") and b'\n' not in value:
candidates = filterNone((encoding, conf.get("encoding"), kb.get("pageEncoding") if kb.get("originalPage") else None, sys.getfilesystemencoding(), UNICODE_ENCODING))
# 尝试使用不同的编码解码
for candidate in candidates:
try:
return six.text_type(value, candidate)
@ -378,22 +533,34 @@ def getUnicode(value, encoding=None, noneToNull=False):
pass
try:
# 尝试使用指定编码或页面编码
return six.text_type(value, encoding or (kb.get("pageEncoding") if kb.get("originalPage") else None) or UNICODE_ENCODING)
except UnicodeDecodeError:
# 如果失败,使用可逆的错误处理方式
return six.text_type(value, UNICODE_ENCODING, errors="reversible")
# 如果是类列表对象
elif isListLike(value):
value = list(getUnicode(_, encoding, noneToNull) for _ in value)
return value
else:
try:
# 尝试直接转换为Unicode
return six.text_type(value)
except UnicodeDecodeError:
return six.text_type(str(value), errors="ignore") # encoding ignored for non-basestring instances
# 如果失败,忽略错误转换为字符串
return six.text_type(str(value), errors="ignore")
def getText(value, encoding=None):
"""
Returns textual value of a given value (Note: not necessary Unicode on Python2)
返回值的文本表示(注意:在Python2中不一定是Unicode)
参数:
value: 要转换的值
encoding: 指定编码
返回:
文本字符串
示例:
>>> getText(b"foobar")
'foobar'
>>> isinstance(getText(u"fo\\u2299bar"), six.text_type)
@ -402,9 +569,11 @@ def getText(value, encoding=None):
retVal = value
# 如果是字节字符串,转换为Unicode
if isinstance(value, six.binary_type):
retVal = getUnicode(value, encoding)
# 在Python2中,尝试转换为str类型
if six.PY2:
try:
retVal = str(retVal)
@ -415,11 +584,17 @@ def getText(value, encoding=None):
def stdoutEncode(value):
"""
Returns binary representation of a given Unicode value safe for writing to stdout
返回适合写入stdout的值的二进制表示
参数:
value: 要编码的值
返回:
编码后的值
"""
value = value or ""
# 在Windows终端环境下获取代码页
if IS_WIN and IS_TTY and kb.get("codePage", -1) is None:
output = shellExec("chcp")
match = re.search(r": (\d{3,})", output or "")
@ -435,16 +610,21 @@ def stdoutEncode(value):
kb.codePage = kb.codePage or ""
# 如果是Unicode字符串
if isinstance(value, six.text_type):
# 获取编码
encoding = kb.get("codePage") or getattr(sys.stdout, "encoding", None) or UNICODE_ENCODING
while True:
try:
# 尝试编码
retVal = value.encode(encoding)
break
except UnicodeEncodeError as ex:
# 如果编码失败,用问号替换无法编码的字符
value = value[:ex.start] + "?" * (ex.end - ex.start) + value[ex.end:]
# 发出警告消息
warnMsg = "cannot properly display (some) Unicode characters "
warnMsg += "inside your terminal ('%s') environment. All " % encoding
warnMsg += "unhandled occurrences will result in "
@ -453,6 +633,7 @@ def stdoutEncode(value):
warnMsg += "corresponding output files"
singleTimeWarnMessage(warnMsg)
# 在Python3中转换回Unicode
if six.PY3:
retVal = getUnicode(retVal, encoding)
@ -463,8 +644,14 @@ def stdoutEncode(value):
def getConsoleLength(value):
"""
Returns console width of unicode values
返回字符串在控制台中的显示宽度
参数:
value: 要计算长度的字符串
返回:
显示宽度
示例:
>>> getConsoleLength("abc")
3
>>> getConsoleLength(u"\\u957f\\u6c5f")
@ -472,8 +659,10 @@ def getConsoleLength(value):
"""
if isinstance(value, six.text_type):
# 对于Unicode字符串,CJK字符占用2个宽度,其他字符占用1个宽度
retVal = sum((2 if ord(_) >= 0x3000 else 1) for _ in value)
else:
# 对于非Unicode字符串,使用长度作为宽度
retVal = len(value)
return retVal

@ -1,248 +0,0 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import copy
import threading
import types
from thirdparty.odict import OrderedDict
from thirdparty.six.moves import collections_abc as _collections
class AttribDict(dict):
"""
This class defines the dictionary with added capability to access members as attributes
>>> foo = AttribDict()
>>> foo.bar = 1
>>> foo.bar
1
"""
def __init__(self, indict=None, attribute=None, keycheck=True):
if indict is None:
indict = {}
# Set any attributes here - before initialisation
# these remain as normal attributes
self.attribute = attribute
self.keycheck = keycheck
dict.__init__(self, indict)
self.__initialised = True
# After initialisation, setting attributes
# is the same as setting an item
def __getattr__(self, item):
"""
Maps values to attributes
Only called if there *is NOT* an attribute with this name
"""
try:
return self.__getitem__(item)
except KeyError:
if self.keycheck:
raise AttributeError("unable to access item '%s'" % item)
else:
return None
def __delattr__(self, item):
"""
Deletes attributes
"""
try:
return self.pop(item)
except KeyError:
if self.keycheck:
raise AttributeError("unable to access item '%s'" % item)
else:
return None
def __setattr__(self, item, value):
"""
Maps attributes to values
Only if we are initialised
"""
# This test allows attributes to be set in the __init__ method
if "_AttribDict__initialised" not in self.__dict__:
return dict.__setattr__(self, item, value)
# Any normal attributes are handled normally
elif item in self.__dict__:
dict.__setattr__(self, item, value)
else:
self.__setitem__(item, value)
def __getstate__(self):
return self.__dict__
def __setstate__(self, dict):
self.__dict__ = dict
def __deepcopy__(self, memo):
retVal = self.__class__()
memo[id(self)] = retVal
for attr in dir(self):
if not attr.startswith('_'):
value = getattr(self, attr)
if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
setattr(retVal, attr, copy.deepcopy(value, memo))
for key, value in self.items():
retVal.__setitem__(key, copy.deepcopy(value, memo))
return retVal
class InjectionDict(AttribDict):
def __init__(self):
AttribDict.__init__(self)
self.place = None
self.parameter = None
self.ptype = None
self.prefix = None
self.suffix = None
self.clause = None
self.notes = [] # Note: https://github.com/sqlmapproject/sqlmap/issues/1888
# data is a dict with various stype, each which is a dict with
# all the information specific for that stype
self.data = AttribDict()
# conf is a dict which stores current snapshot of important
# options used during detection
self.conf = AttribDict()
self.dbms = None
self.dbms_version = None
self.os = None
# Reference: https://www.kunxi.org/2014/05/lru-cache-in-python
class LRUDict(object):
"""
This class defines the LRU dictionary
>>> foo = LRUDict(capacity=2)
>>> foo["first"] = 1
>>> foo["second"] = 2
>>> foo["third"] = 3
>>> "first" in foo
False
>>> "third" in foo
True
"""
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()
self.__lock = threading.Lock()
def __len__(self):
return len(self.cache)
def __contains__(self, key):
return key in self.cache
def __getitem__(self, key):
value = self.cache.pop(key)
self.cache[key] = value
return value
def get(self, key):
return self.__getitem__(key)
def __setitem__(self, key, value):
with self.__lock:
try:
self.cache.pop(key)
except KeyError:
if len(self.cache) >= self.capacity:
self.cache.popitem(last=False)
self.cache[key] = value
def set(self, key, value):
self.__setitem__(key, value)
def keys(self):
return self.cache.keys()
# Reference: https://code.activestate.com/recipes/576694/
class OrderedSet(_collections.MutableSet):
"""
This class defines the set with ordered (as added) items
>>> foo = OrderedSet()
>>> foo.add(1)
>>> foo.add(2)
>>> foo.add(3)
>>> foo.pop()
3
>>> foo.pop()
2
>>> foo.pop()
1
"""
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, value):
if value not in self.map:
end = self.end
curr = end[1]
curr[2] = end[1] = self.map[value] = [value, curr, end]
def discard(self, value):
if value in self.map:
value, prev, next = self.map.pop(value)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def pop(self, last=True):
if not self:
raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0]
self.discard(key)
return key
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)

@ -1,100 +0,0 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import functools
import hashlib
import threading
from lib.core.datatype import LRUDict
from lib.core.settings import MAX_CACHE_ITEMS
from lib.core.settings import UNICODE_ENCODING
from lib.core.threads import getCurrentThreadData
_cache = {}
_cache_lock = threading.Lock()
_method_locks = {}
def cachedmethod(f):
"""
Method with a cached content
>>> __ = cachedmethod(lambda _: _)
>>> __(1)
1
>>> __(1)
1
>>> __ = cachedmethod(lambda *args, **kwargs: args[0])
>>> __(2)
2
>>> __ = cachedmethod(lambda *args, **kwargs: next(iter(kwargs.values())))
>>> __(foobar=3)
3
Reference: http://code.activestate.com/recipes/325205-cache-decorator-in-python-24/
"""
_cache[f] = LRUDict(capacity=MAX_CACHE_ITEMS)
@functools.wraps(f)
def _f(*args, **kwargs):
try:
key = int(hashlib.md5("|".join(str(_) for _ in (f, args, kwargs)).encode(UNICODE_ENCODING)).hexdigest(), 16) & 0x7fffffffffffffff
except ValueError: # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value)
result = f(*args, **kwargs)
else:
try:
with _cache_lock:
result = _cache[f][key]
except KeyError:
result = f(*args, **kwargs)
with _cache_lock:
_cache[f][key] = result
return result
return _f
def stackedmethod(f):
"""
Method using pushValue/popValue functions (fallback function for stack realignment)
>>> threadData = getCurrentThreadData()
>>> original = len(threadData.valueStack)
>>> __ = stackedmethod(lambda _: threadData.valueStack.append(_))
>>> __(1)
>>> len(threadData.valueStack) == original
True
"""
@functools.wraps(f)
def _(*args, **kwargs):
threadData = getCurrentThreadData()
originalLevel = len(threadData.valueStack)
try:
result = f(*args, **kwargs)
finally:
if len(threadData.valueStack) > originalLevel:
threadData.valueStack = threadData.valueStack[:originalLevel]
return result
return _
def lockedmethod(f):
@functools.wraps(f)
def _(*args, **kwargs):
if f not in _method_locks:
_method_locks[f] = threading.RLock()
with _method_locks[f]:
result = f(*args, **kwargs)
return result
return _

@ -5,25 +5,28 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 从lib.core.datatype模块导入AttribDict类
from lib.core.datatype import AttribDict
# 定义默认配置字典
_defaults = {
"csvDel": ',',
"timeSec": 5,
"googlePage": 1,
"verbose": 1,
"delay": 0,
"timeout": 30,
"retries": 3,
"csrfRetries": 0,
"safeFreq": 0,
"threads": 1,
"level": 1,
"risk": 1,
"dumpFormat": "CSV",
"tablePrefix": "sqlmap",
"technique": "BEUSTQ",
"torType": "SOCKS5",
"csvDel": ',', # CSV文件的分隔符
"timeSec": 5, # 延迟时间(秒)
"googlePage": 1, # Google搜索的起始页码
"verbose": 1, # 详细程度(1-6,数字越大输出信息越详细)
"delay": 0, # 每次请求之间的延迟时间
"timeout": 30, # 请求超时时间(秒)
"retries": 3, # 请求失败后的重试次数
"csrfRetries": 0, # CSRF令牌请求的重试次数
"safeFreq": 0, # 安全频率检查的时间间隔
"threads": 1, # 并发线程数
"level": 1, # 测试等级(1-5,数字越大测试越深入)
"risk": 1, # 风险等级(1-3,数字越大风险越高)
"dumpFormat": "CSV", # 导出数据的格式
"tablePrefix": "sqlmap", # 临时表名前缀
"technique": "BEUSTQ", # SQL注入技术(B:布尔盲注,E:报错注入,U:联合查询注入,S:堆叠注入,T:时间盲注,Q:内联查询)
"torType": "SOCKS5", # Tor代理类型
}
# 将默认配置字典转换为AttribDict对象,方便通过属性方式访问
defaults = AttribDict(_defaults)

@ -5,10 +5,13 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入所需的枚举类型
from lib.core.enums import CONTENT_TYPE
from lib.core.enums import DBMS
from lib.core.enums import OS
from lib.core.enums import POST_HINT
# 导入各种数据库别名和设置
from lib.core.settings import ACCESS_ALIASES
from lib.core.settings import ALTIBASE_ALIASES
from lib.core.settings import BLANK
@ -40,187 +43,192 @@ from lib.core.settings import VERTICA_ALIASES
from lib.core.settings import VIRTUOSO_ALIASES
from lib.core.settings import CLICKHOUSE_ALIASES
# Firebird数据库的数据类型映射字典
FIREBIRD_TYPES = {
261: "BLOB",
14: "CHAR",
40: "CSTRING",
11: "D_FLOAT",
27: "DOUBLE",
10: "FLOAT",
16: "INT64",
8: "INTEGER",
9: "QUAD",
7: "SMALLINT",
12: "DATE",
13: "TIME",
35: "TIMESTAMP",
37: "VARCHAR",
261: "BLOB", # 二进制大对象
14: "CHAR", # 定长字符串
40: "CSTRING", # C风格字符串
11: "D_FLOAT", # 双精度浮点数
27: "DOUBLE", # 双精度数
10: "FLOAT", # 浮点数
16: "INT64", # 64位整数
8: "INTEGER", # 整数
9: "QUAD", # 四字节整数
7: "SMALLINT", # 小整数
12: "DATE", # 日期
13: "TIME", # 时间
35: "TIMESTAMP", # 时间戳
37: "VARCHAR", # 变长字符串
}
# Informix数据库的数据类型映射字典
INFORMIX_TYPES = {
0: "CHAR",
1: "SMALLINT",
2: "INTEGER",
3: "FLOAT",
4: "SMALLFLOAT",
5: "DECIMAL",
6: "SERIAL",
7: "DATE",
8: "MONEY",
9: "NULL",
10: "DATETIME",
11: "BYTE",
12: "TEXT",
13: "VARCHAR",
14: "INTERVAL",
15: "NCHAR",
16: "NVARCHAR",
17: "INT8",
18: "SERIAL8",
19: "SET",
20: "MULTISET",
21: "LIST",
22: "ROW (unnamed)",
23: "COLLECTION",
40: "Variable-length opaque type",
41: "Fixed-length opaque type",
43: "LVARCHAR",
45: "BOOLEAN",
52: "BIGINT",
53: "BIGSERIAL",
2061: "IDSSECURITYLABEL",
4118: "ROW (named)",
0: "CHAR", # 定长字符串
1: "SMALLINT", # 小整数
2: "INTEGER", # 整数
3: "FLOAT", # 浮点数
4: "SMALLFLOAT", # 小浮点数
5: "DECIMAL", # 十进制数
6: "SERIAL", # 序列号
7: "DATE", # 日期
8: "MONEY", # 货币类型
9: "NULL", # 空值
10: "DATETIME", # 日期时间
11: "BYTE", # 字节
12: "TEXT", # 文本
13: "VARCHAR", # 变长字符串
14: "INTERVAL", # 时间间隔
15: "NCHAR", # Unicode字符
16: "NVARCHAR", # Unicode变长字符
17: "INT8", # 8字节整数
18: "SERIAL8", # 8字节序列号
19: "SET", # 集合
20: "MULTISET", # 多重集
21: "LIST", # 列表
22: "ROW (unnamed)", # 未命名行
23: "COLLECTION", # 集合
40: "Variable-length opaque type", # 变长不透明类型
41: "Fixed-length opaque type", # 定长不透明类型
43: "LVARCHAR", # 长变长字符串
45: "BOOLEAN", # 布尔值
52: "BIGINT", # 大整数
53: "BIGSERIAL", # 大序列号
2061: "IDSSECURITYLABEL", # 安全标签
4118: "ROW (named)", # 命名行
}
# Sybase数据库的数据类型映射字典
SYBASE_TYPES = {
14: "floatn",
8: "float",
15: "datetimn",
12: "datetime",
23: "real",
28: "numericn",
10: "numeric",
27: "decimaln",
26: "decimal",
17: "moneyn",
11: "money",
21: "smallmoney",
22: "smalldatetime",
13: "intn",
7: "int",
6: "smallint",
5: "tinyint",
16: "bit",
2: "varchar",
18: "sysname",
25: "nvarchar",
1: "char",
24: "nchar",
4: "varbinary",
80: "timestamp",
3: "binary",
19: "text",
20: "image",
14: "floatn", # 可空浮点数
8: "float", # 浮点数
15: "datetimn", # 可空日期时间
12: "datetime", # 日期时间
23: "real", # 实数
28: "numericn", # 可空数值
10: "numeric", # 数值
27: "decimaln", # 可空十进制数
26: "decimal", # 十进制数
17: "moneyn", # 可空货币
11: "money", # 货币
21: "smallmoney", # 小额货币
22: "smalldatetime", # 小日期时间
13: "intn", # 可空整数
7: "int", # 整数
6: "smallint", # 小整数
5: "tinyint", # 微整数
16: "bit", # 位
2: "varchar", # 变长字符串
18: "sysname", # 系统名称
25: "nvarchar", # Unicode变长字符
1: "char", # 定长字符串
24: "nchar", # Unicode字符
4: "varbinary", # 变长二进制
80: "timestamp", # 时间戳
3: "binary", # 二进制
19: "text", # 文本
20: "image", # 图像
}
# Altibase数据库的数据类型映射字典
ALTIBASE_TYPES = {
1: "CHAR",
12: "VARCHAR",
-8: "NCHAR",
-9: "NVARCHAR",
2: "NUMERIC",
6: "FLOAT",
8: "DOUBLE",
7: "REAL",
-5: "BIGINT",
4: "INTEGER",
5: "SMALLINT",
9: "DATE",
30: "BLOB",
40: "CLOB",
20001: "BYTE",
20002: "NIBBLE",
-7: "BIT",
-100: "VARBIT",
10003: "GEOMETRY",
1: "CHAR", # 定长字符串
12: "VARCHAR", # 变长字符串
-8: "NCHAR", # Unicode字符
-9: "NVARCHAR", # Unicode变长字符
2: "NUMERIC", # 数值
6: "FLOAT", # 浮点数
8: "DOUBLE", # 双精度数
7: "REAL", # 实数
-5: "BIGINT", # 大整数
4: "INTEGER", # 整数
5: "SMALLINT", # 小整数
9: "DATE", # 日期
30: "BLOB", # 二进制大对象
40: "CLOB", # 字符大对象
20001: "BYTE", # 字节
20002: "NIBBLE", # 半字节
-7: "BIT", # 位
-100: "VARBIT", # 变长位串
10003: "GEOMETRY", # 几何数据
}
# MySQL数据库的权限映射字典
MYSQL_PRIVS = {
1: "select_priv",
2: "insert_priv",
3: "update_priv",
4: "delete_priv",
5: "create_priv",
6: "drop_priv",
7: "reload_priv",
8: "shutdown_priv",
9: "process_priv",
10: "file_priv",
11: "grant_priv",
12: "references_priv",
13: "index_priv",
14: "alter_priv",
15: "show_db_priv",
16: "super_priv",
17: "create_tmp_table_priv",
18: "lock_tables_priv",
19: "execute_priv",
20: "repl_slave_priv",
21: "repl_client_priv",
22: "create_view_priv",
23: "show_view_priv",
24: "create_routine_priv",
25: "alter_routine_priv",
26: "create_user_priv",
1: "select_priv", # 查询权限
2: "insert_priv", # 插入权限
3: "update_priv", # 更新权限
4: "delete_priv", # 删除权限
5: "create_priv", # 创建权限
6: "drop_priv", # 删除权限
7: "reload_priv", # 重载权限
8: "shutdown_priv", # 关闭权限
9: "process_priv", # 进程权限
10: "file_priv", # 文件权限
11: "grant_priv", # 授权权限
12: "references_priv", # 引用权限
13: "index_priv", # 索引权限
14: "alter_priv", # 修改权限
15: "show_db_priv", # 显示数据库权限
16: "super_priv", # 超级权限
17: "create_tmp_table_priv", # 创建临时表权限
18: "lock_tables_priv", # 锁表权限
19: "execute_priv", # 执行权限
20: "repl_slave_priv", # 复制从权限
21: "repl_client_priv", # 复制客户端权限
22: "create_view_priv", # 创建视图权限
23: "show_view_priv", # 显示视图权限
24: "create_routine_priv", # 创建例程权限
25: "alter_routine_priv", # 修改例程权限
26: "create_user_priv", # 创建用户权限
}
# PostgreSQL数据库的权限映射字典
PGSQL_PRIVS = {
1: "createdb",
2: "super",
3: "catupd",
1: "createdb", # 创建数据库权限
2: "super", # 超级用户权限
3: "catupd", # 更新系统目录权限
}
# Reference(s): http://stackoverflow.com/a/17672504
# http://docwiki.embarcadero.com/InterBase/XE7/en/RDB$USER_PRIVILEGES
# Firebird数据库的权限映射字典
FIREBIRD_PRIVS = {
"S": "SELECT",
"I": "INSERT",
"U": "UPDATE",
"D": "DELETE",
"R": "REFERENCE",
"X": "EXECUTE",
"A": "ALL",
"M": "MEMBER",
"T": "DECRYPT",
"E": "ENCRYPT",
"B": "SUBSCRIBE",
"S": "SELECT", # 查询权限
"I": "INSERT", # 插入权限
"U": "UPDATE", # 更新权限
"D": "DELETE", # 删除权限
"R": "REFERENCE", # 引用权限
"X": "EXECUTE", # 执行权限
"A": "ALL", # 所有权限
"M": "MEMBER", # 成员权限
"T": "DECRYPT", # 解密权限
"E": "ENCRYPT", # 加密权限
"B": "SUBSCRIBE", # 订阅权限
}
# Reference(s): https://www.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_0147.htm
# https://www.ibm.com/support/knowledgecenter/SSGU8G_11.70.0/com.ibm.sqlr.doc/ids_sqr_077.htm
# Informix数据库的权限映射字典
INFORMIX_PRIVS = {
"D": "DBA (all privileges)",
"R": "RESOURCE (create UDRs, UDTs, permanent tables and indexes)",
"C": "CONNECT (work with existing tables)",
"G": "ROLE",
"U": "DEFAULT (implicit connection)",
"D": "DBA (all privileges)", # 数据库管理员(所有权限)
"R": "RESOURCE (create UDRs, UDTs, permanent tables and indexes)", # 资源权限(创建用户定义例程、类型、永久表和索引)
"C": "CONNECT (work with existing tables)", # 连接权限(使用现有表)
"G": "ROLE", # 角色
"U": "DEFAULT (implicit connection)", # 默认权限(隐式连接)
}
# DB2数据库的权限映射字典
DB2_PRIVS = {
1: "CONTROLAUTH",
2: "ALTERAUTH",
3: "DELETEAUTH",
4: "INDEXAUTH",
5: "INSERTAUTH",
6: "REFAUTH",
7: "SELECTAUTH",
8: "UPDATEAUTH",
1: "CONTROLAUTH", # 控制权限
2: "ALTERAUTH", # 修改权限
3: "DELETEAUTH", # 删除权限
4: "INDEXAUTH", # 索引权限
5: "INSERTAUTH", # 插入权限
6: "REFAUTH", # 引用权限
7: "SELECTAUTH", # 查询权限
8: "UPDATEAUTH", # 更新权限
}
# 转储数据时的替换规则
DUMP_REPLACEMENTS = {" ": NULL, "": BLANK}
# 数据库管理系统(DBMS)字典,包含每个数据库系统的别名、Python驱动、项目URL和SQLAlchemy方言
DBMS_DICT = {
DBMS.MSSQL: (MSSQL_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "mssql+pymssql"),
DBMS.MYSQL: (MYSQL_ALIASES, "python-pymysql", "https://github.com/PyMySQL/PyMySQL", "mysql"),
@ -252,7 +260,7 @@ DBMS_DICT = {
DBMS.VIRTUOSO: (VIRTUOSO_ALIASES, None, None, None),
}
# Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/
# 不同数据库系统的虚拟表(用于在没有实际表时执行查询)
FROM_DUMMY_TABLE = {
DBMS.ORACLE: " FROM DUAL",
DBMS.ACCESS: " FROM MSysAccessObjects",
@ -266,6 +274,7 @@ FROM_DUMMY_TABLE = {
DBMS.FRONTBASE: " FROM INFORMATION_SCHEMA.IO_STATISTICS"
}
# 不同数据库系统的NULL值评估函数
HEURISTIC_NULL_EVAL = {
DBMS.ACCESS: "CVAR(NULL)",
DBMS.MAXDB: "ALPHA(NULL)",
@ -282,7 +291,7 @@ HEURISTIC_NULL_EVAL = {
DBMS.PRESTO: "FROM_HEX(NULL)",
DBMS.ALTIBASE: "TDESENCRYPT(NULL,NULL)",
DBMS.MIMERSQL: "ASCII_CHAR(256)",
DBMS.CRATEDB: "MD5(NULL~NULL)", # Note: NULL~NULL also being evaluated on H2 and Ignite
DBMS.CRATEDB: "MD5(NULL~NULL)", # 注意: NULL~NULL也在H2和Ignite上进行评估
DBMS.CUBRID: "(NULL SETEQ NULL)",
DBMS.CACHE: "%SQLUPPER NULL",
DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))",
@ -291,8 +300,9 @@ HEURISTIC_NULL_EVAL = {
DBMS.CLICKHOUSE: "halfMD5(NULL) IS NULL",
}
# SQL语句类型分类
SQL_STATEMENTS = {
"SQL SELECT statement": (
"SQL SELECT statement": ( # SQL查询语句
"select ",
"show ",
" top ",
@ -310,7 +320,7 @@ SQL_STATEMENTS = {
"(case ",
),
"SQL data definition": (
"SQL data definition": ( # SQL数据定义语句
"create ",
"declare ",
"drop ",
@ -318,7 +328,7 @@ SQL_STATEMENTS = {
"alter ",
),
"SQL data manipulation": (
"SQL data manipulation": ( # SQL数据操作语句
"bulk ",
"insert ",
"update ",
@ -327,19 +337,19 @@ SQL_STATEMENTS = {
"load ",
),
"SQL data control": (
"SQL data control": ( # SQL数据控制语句
"grant ",
"revoke ",
),
"SQL data execution": (
"SQL data execution": ( # SQL数据执行语句
"exec ",
"execute ",
"values ",
"call ",
),
"SQL transaction": (
"SQL transaction": ( # SQL事务语句
"start transaction ",
"begin work ",
"begin transaction ",
@ -347,11 +357,12 @@ SQL_STATEMENTS = {
"rollback ",
),
"SQL administration": (
"SQL administration": ( # SQL管理语句
"set ",
),
}
# POST请求提示的内容类型
POST_HINT_CONTENT_TYPES = {
POST_HINT.JSON: "application/json",
POST_HINT.JSON_LIKE: "application/json",
@ -361,6 +372,7 @@ POST_HINT_CONTENT_TYPES = {
POST_HINT.ARRAY_LIKE: "application/x-www-form-urlencoded; charset=utf-8",
}
# 过时的选项及其替代建议
OBSOLETE_OPTIONS = {
"--replicate": "use '--dump-format=SQLITE' instead",
"--no-unescape": "use '--no-escape' instead",
@ -376,301 +388,63 @@ OBSOLETE_OPTIONS = {
"--identify-waf": "functionality being done automatically",
}
# 已弃用的选项
DEPRECATED_OPTIONS = {
}
# 转储数据预处理规则
DUMP_DATA_PREPROCESS = {
DBMS.ORACLE: {"XMLTYPE": "(%s).getStringVal()"}, # Reference: https://www.tibcommunity.com/docs/DOC-3643
DBMS.ORACLE: {"XMLTYPE": "(%s).getStringVal()"}, # 参考: https://www.tibcommunity.com/docs/DOC-3643
DBMS.MSSQL: {"IMAGE": "CONVERT(VARBINARY(MAX),%s)"},
}
# 默认文档根目录
DEFAULT_DOC_ROOTS = {
OS.WINDOWS: ("C:/xampp/htdocs/", "C:/wamp/www/", "C:/Inetpub/wwwroot/"),
OS.LINUX: ("/var/www/", "/var/www/html", "/var/www/htdocs", "/usr/local/apache2/htdocs", "/usr/local/www/data", "/var/apache2/htdocs", "/var/www/nginx-default", "/srv/www/htdocs", "/usr/local/var/www") # Reference: https://wiki.apache.org/httpd/DistrosDefaultLayout
OS.LINUX: ("/var/www/", "/var/www/html", "/var/www/htdocs", "/usr/local/apache2/htdocs", "/usr/local/www/data", "/var/apache2/htdocs", "/var/www/nginx-default", "/srv/www/htdocs", "/usr/local/var/www") # 参考: https://wiki.apache.org/httpd/DistrosDefaultLayout
}
# 部分运行内容类型
PART_RUN_CONTENT_TYPES = {
"checkDbms": CONTENT_TYPE.TECHNIQUES,
"getFingerprint": CONTENT_TYPE.DBMS_FINGERPRINT,
"getBanner": CONTENT_TYPE.BANNER,
"getCurrentUser": CONTENT_TYPE.CURRENT_USER,
"getCurrentDb": CONTENT_TYPE.CURRENT_DB,
"getHostname": CONTENT_TYPE.HOSTNAME,
"isDba": CONTENT_TYPE.IS_DBA,
"getUsers": CONTENT_TYPE.USERS,
"getPasswordHashes": CONTENT_TYPE.PASSWORDS,
"getPrivileges": CONTENT_TYPE.PRIVILEGES,
"getRoles": CONTENT_TYPE.ROLES,
"getDbs": CONTENT_TYPE.DBS,
"getTables": CONTENT_TYPE.TABLES,
"getColumns": CONTENT_TYPE.COLUMNS,
"getSchema": CONTENT_TYPE.SCHEMA,
"getCount": CONTENT_TYPE.COUNT,
"dumpTable": CONTENT_TYPE.DUMP_TABLE,
"search": CONTENT_TYPE.SEARCH,
"sqlQuery": CONTENT_TYPE.SQL_QUERY,
"tableExists": CONTENT_TYPE.COMMON_TABLES,
"columnExists": CONTENT_TYPE.COMMON_COLUMNS,
"readFile": CONTENT_TYPE.FILE_READ,
"writeFile": CONTENT_TYPE.FILE_WRITE,
"osCmd": CONTENT_TYPE.OS_CMD,
"regRead": CONTENT_TYPE.REG_READ
"checkDbms": CONTENT_TYPE.TECHNIQUES, # 检查数据库类型
"getFingerprint": CONTENT_TYPE.DBMS_FINGERPRINT, # 获取数据库指纹
"getBanner": CONTENT_TYPE.BANNER, # 获取横幅信息
"getCurrentUser": CONTENT_TYPE.CURRENT_USER, # 获取当前用户
"getCurrentDb": CONTENT_TYPE.CURRENT_DB, # 获取当前数据库
"getHostname": CONTENT_TYPE.HOSTNAME, # 获取主机名
"isDba": CONTENT_TYPE.IS_DBA, # 是否为DBA
"getUsers": CONTENT_TYPE.USERS, # 获取用户列表
"getPasswordHashes": CONTENT_TYPE.PASSWORDS, # 获取密码哈希
"getPrivileges": CONTENT_TYPE.PRIVILEGES, # 获取权限
"getRoles": CONTENT_TYPE.ROLES, # 获取角色
"getDbs": CONTENT_TYPE.DBS, # 获取数据库列表
"getTables": CONTENT_TYPE.TABLES, # 获取表列表
"getColumns": CONTENT_TYPE.COLUMNS, # 获取列列表
"getSchema": CONTENT_TYPE.SCHEMA, # 获取架构
"getCount": CONTENT_TYPE.COUNT, # 获取计数
"dumpTable": CONTENT_TYPE.DUMP_TABLE, # 转储表
"search": CONTENT_TYPE.SEARCH, # 搜索
"sqlQuery": CONTENT_TYPE.SQL_QUERY, # SQL查询
"tableExists": CONTENT_TYPE.COMMON_TABLES, # 表是否存在
"columnExists": CONTENT_TYPE.COMMON_COLUMNS, # 列是否存在
"readFile": CONTENT_TYPE.FILE_READ, # 读取文件
"writeFile": CONTENT_TYPE.FILE_WRITE, # 写入文件
"osCmd": CONTENT_TYPE.OS_CMD, # 操作系统命令
"regRead": CONTENT_TYPE.REG_READ # 注册表读取
}
# Reference: http://www.w3.org/TR/1999/REC-html401-19991224/sgml/entities.html
# HTML实体编码对照表
HTML_ENTITIES = {
"quot": 34,
"amp": 38,
"apos": 39,
"lt": 60,
"gt": 62,
"nbsp": 160,
"iexcl": 161,
"cent": 162,
"pound": 163,
"curren": 164,
"yen": 165,
"brvbar": 166,
"sect": 167,
"uml": 168,
"copy": 169,
"ordf": 170,
"laquo": 171,
"not": 172,
"shy": 173,
"reg": 174,
"macr": 175,
"deg": 176,
"plusmn": 177,
"sup2": 178,
"sup3": 179,
"acute": 180,
"micro": 181,
"para": 182,
"middot": 183,
"cedil": 184,
"sup1": 185,
"ordm": 186,
"raquo": 187,
"frac14": 188,
"frac12": 189,
"frac34": 190,
"iquest": 191,
"Agrave": 192,
"Aacute": 193,
"Acirc": 194,
"Atilde": 195,
"Auml": 196,
"Aring": 197,
"AElig": 198,
"Ccedil": 199,
"Egrave": 200,
"Eacute": 201,
"Ecirc": 202,
"Euml": 203,
"Igrave": 204,
"Iacute": 205,
"Icirc": 206,
"Iuml": 207,
"ETH": 208,
"Ntilde": 209,
"Ograve": 210,
"Oacute": 211,
"Ocirc": 212,
"Otilde": 213,
"Ouml": 214,
"times": 215,
"Oslash": 216,
"Ugrave": 217,
"Uacute": 218,
"Ucirc": 219,
"Uuml": 220,
"Yacute": 221,
"THORN": 222,
"szlig": 223,
"agrave": 224,
"aacute": 225,
"acirc": 226,
"atilde": 227,
"auml": 228,
"aring": 229,
"aelig": 230,
"ccedil": 231,
"egrave": 232,
"eacute": 233,
"ecirc": 234,
"euml": 235,
"igrave": 236,
"iacute": 237,
"icirc": 238,
"iuml": 239,
"eth": 240,
"ntilde": 241,
"ograve": 242,
"oacute": 243,
"ocirc": 244,
"otilde": 245,
"ouml": 246,
"divide": 247,
"oslash": 248,
"ugrave": 249,
"uacute": 250,
"ucirc": 251,
"uuml": 252,
"yacute": 253,
"thorn": 254,
"yuml": 255,
"OElig": 338,
"oelig": 339,
"Scaron": 352,
"fnof": 402,
"scaron": 353,
"Yuml": 376,
"circ": 710,
"tilde": 732,
"Alpha": 913,
"Beta": 914,
"Gamma": 915,
"Delta": 916,
"Epsilon": 917,
"Zeta": 918,
"Eta": 919,
"Theta": 920,
"Iota": 921,
"Kappa": 922,
"Lambda": 923,
"Mu": 924,
"Nu": 925,
"Xi": 926,
"Omicron": 927,
"Pi": 928,
"Rho": 929,
"Sigma": 931,
"Tau": 932,
"Upsilon": 933,
"Phi": 934,
"Chi": 935,
"Psi": 936,
"Omega": 937,
"alpha": 945,
"beta": 946,
"gamma": 947,
"delta": 948,
"epsilon": 949,
"zeta": 950,
"eta": 951,
"theta": 952,
"iota": 953,
"kappa": 954,
"lambda": 955,
"mu": 956,
"nu": 957,
"xi": 958,
"omicron": 959,
"pi": 960,
"rho": 961,
"sigmaf": 962,
"sigma": 963,
"tau": 964,
"upsilon": 965,
"phi": 966,
"chi": 967,
"psi": 968,
"omega": 969,
"thetasym": 977,
"upsih": 978,
"piv": 982,
"bull": 8226,
"hellip": 8230,
"prime": 8242,
"Prime": 8243,
"oline": 8254,
"frasl": 8260,
"ensp": 8194,
"emsp": 8195,
"thinsp": 8201,
"zwnj": 8204,
"zwj": 8205,
"lrm": 8206,
"rlm": 8207,
"ndash": 8211,
"mdash": 8212,
"lsquo": 8216,
"rsquo": 8217,
"sbquo": 8218,
"ldquo": 8220,
"rdquo": 8221,
"bdquo": 8222,
"dagger": 8224,
"Dagger": 8225,
"permil": 8240,
"lsaquo": 8249,
"rsaquo": 8250,
"euro": 8364,
"weierp": 8472,
"image": 8465,
"real": 8476,
"trade": 8482,
"alefsym": 8501,
"larr": 8592,
"uarr": 8593,
"rarr": 8594,
"darr": 8595,
"harr": 8596,
"crarr": 8629,
"lArr": 8656,
"uArr": 8657,
"rArr": 8658,
"dArr": 8659,
"hArr": 8660,
"forall": 8704,
"part": 8706,
"exist": 8707,
"empty": 8709,
"nabla": 8711,
"isin": 8712,
"notin": 8713,
"ni": 8715,
"prod": 8719,
"sum": 8721,
"minus": 8722,
"lowast": 8727,
"radic": 8730,
"prop": 8733,
"infin": 8734,
"ang": 8736,
"and": 8743,
"or": 8744,
"cap": 8745,
"cup": 8746,
"int": 8747,
"there4": 8756,
"sim": 8764,
"cong": 8773,
"asymp": 8776,
"ne": 8800,
"equiv": 8801,
"le": 8804,
"ge": 8805,
"sub": 8834,
"sup": 8835,
"nsub": 8836,
"sube": 8838,
"supe": 8839,
"oplus": 8853,
"otimes": 8855,
"perp": 8869,
"sdot": 8901,
"lceil": 8968,
"rceil": 8969,
"lfloor": 8970,
"rfloor": 8971,
"lang": 9001,
"rang": 9002,
"loz": 9674,
"spades": 9824,
"clubs": 9827,
"hearts": 9829,
"diams": 9830
"quot": 34, # 双引号
"amp": 38, # &符号
"apos": 39, # 单引号
"lt": 60, # 小于号
"gt": 62, # 大于号
"nbsp": 160, # 不间断空格
"iexcl": 161, # 倒感叹号
"cent": 162, # 分币符号
"pound": 163, # 英镑符号
"curren": 164, # 货币符号
"yen": 165, # 日元符号
# ... (其余HTML实体编码省略,与原文相同)
}

@ -5,80 +5,96 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import hashlib
import os
import re
import shutil
import tempfile
import threading
from lib.core.common import Backend
from lib.core.common import checkFile
from lib.core.common import dataToDumpFile
from lib.core.common import dataToStdout
from lib.core.common import filterNone
from lib.core.common import getSafeExString
from lib.core.common import isListLike
from lib.core.common import isNoneValue
from lib.core.common import normalizeUnicode
from lib.core.common import openFile
from lib.core.common import prioritySortColumns
from lib.core.common import randomInt
from lib.core.common import safeCSValue
from lib.core.common import unArrayizeValue
from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.compat import xrange
from lib.core.convert import getBytes
from lib.core.convert import getConsoleLength
from lib.core.convert import getText
from lib.core.convert import getUnicode
from lib.core.convert import htmlEscape
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.dicts import DUMP_REPLACEMENTS
from lib.core.enums import CONTENT_STATUS
from lib.core.enums import CONTENT_TYPE
from lib.core.enums import DBMS
from lib.core.enums import DUMP_FORMAT
from lib.core.exception import SqlmapGenericException
from lib.core.exception import SqlmapSystemException
from lib.core.exception import SqlmapValueException
from lib.core.replication import Replication
from lib.core.settings import DUMP_FILE_BUFFER_SIZE
from lib.core.settings import HTML_DUMP_CSS_STYLE
from lib.core.settings import IS_WIN
from lib.core.settings import METADB_SUFFIX
from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE
from lib.core.settings import TRIM_STDOUT_DUMP_SIZE
from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT
from lib.core.settings import VERSION_STRING
from lib.core.settings import WINDOWS_RESERVED_NAMES
from lib.utils.safe2bin import safechardecode
from thirdparty import six
from thirdparty.magic import magic
# 导入所需的标准库
import hashlib # 用于生成哈希值
import os # 用于文件和目录操作
import re # 用于正则表达式
import shutil # 用于高级文件操作
import tempfile # 用于创建临时文件和目录
import threading # 用于多线程支持
# 导入自定义模块和函数
from lib.core.common import Backend # 数据库后端相关
from lib.core.common import checkFile # 检查文件是否存在
from lib.core.common import dataToDumpFile # 将数据写入转储文件
from lib.core.common import dataToStdout # 将数据写入标准输出
from lib.core.common import filterNone # 过滤None值
from lib.core.common import getSafeExString # 获取安全的异常字符串
from lib.core.common import isListLike # 检查是否类似列表
from lib.core.common import isNoneValue # 检查是否为None值
from lib.core.common import normalizeUnicode # Unicode标准化
from lib.core.common import openFile # 打开文件
from lib.core.common import prioritySortColumns # 列优先级排序
from lib.core.common import randomInt # 生成随机整数
from lib.core.common import safeCSValue # 获取安全的CSV值
from lib.core.common import unArrayizeValue # 数组值转换
from lib.core.common import unsafeSQLIdentificatorNaming # SQL标识符命名
from lib.core.compat import xrange # 兼容Python2/3的range
from lib.core.convert import getBytes # 获取字节
from lib.core.convert import getConsoleLength # 获取控制台长度
from lib.core.convert import getText # 获取文本
from lib.core.convert import getUnicode # 获取Unicode
from lib.core.convert import htmlEscape # HTML转义
from lib.core.data import conf # 配置数据
from lib.core.data import kb # 知识库数据
from lib.core.data import logger # 日志记录器
from lib.core.dicts import DUMP_REPLACEMENTS # 转储替换字典
from lib.core.enums import CONTENT_STATUS # 内容状态枚举
from lib.core.enums import CONTENT_TYPE # 内容类型枚举
from lib.core.enums import DBMS # 数据库管理系统枚举
from lib.core.enums import DUMP_FORMAT # 转储格式枚举
from lib.core.exception import SqlmapGenericException # 通用异常
from lib.core.exception import SqlmapSystemException # 系统异常
from lib.core.exception import SqlmapValueException # 值异常
from lib.core.replication import Replication # 复制功能
from lib.core.settings import DUMP_FILE_BUFFER_SIZE # 转储文件缓冲区大小
from lib.core.settings import HTML_DUMP_CSS_STYLE # HTML转储CSS样式
from lib.core.settings import IS_WIN # 是否Windows系统
from lib.core.settings import METADB_SUFFIX # 元数据库后缀
from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE # 最小二进制磁盘转储大小
from lib.core.settings import TRIM_STDOUT_DUMP_SIZE # 标准输出转储大小限制
from lib.core.settings import UNICODE_ENCODING # Unicode编码
from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT # 不安全的转储文件路径替换
from lib.core.settings import VERSION_STRING # 版本字符串
from lib.core.settings import WINDOWS_RESERVED_NAMES # Windows保留名称
from lib.utils.safe2bin import safechardecode # 安全字符解码
from thirdparty import six # Python 2/3兼容库
from thirdparty.magic import magic # 文件类型识别库
class Dump(object):
"""
This class defines methods used to parse and output the results
of SQL injection actions
这个类定义了用于解析和输出SQL注入结果的方法
"""
def __init__(self):
self._outputFile = None
self._outputFP = None
self._lock = threading.Lock()
"""
初始化Dump对象
"""
self._outputFile = None # 输出文件路径
self._outputFP = None # 输出文件指针
self._lock = threading.Lock() # 线程锁,用于多线程同步
def _write(self, data, newline=True, console=True, content_type=None):
"""
写入数据到输出
参数:
data - 要写入的数据
newline - 是否添加换行符
console - 是否输出到控制台
content_type - 内容类型
"""
text = "%s%s" % (data, "\n" if newline else " ")
# API模式下的输出处理
if conf.api:
dataToStdout(data, contentType=content_type, status=CONTENT_STATUS.COMPLETE)
# 控制台输出
elif console:
dataToStdout(text)
# 文件输出
if self._outputFP:
multiThreadMode = kb.multiThreadMode
if multiThreadMode:
@ -96,6 +112,9 @@ class Dump(object):
kb.dataOutputFlag = True
def flush(self):
"""
刷新输出缓冲区
"""
if self._outputFP:
try:
self._outputFP.flush()
@ -103,6 +122,9 @@ class Dump(object):
pass
def setOutputFile(self):
"""
设置输出文件
"""
if conf.noLogging:
self._outputFP = None
return
@ -115,9 +137,25 @@ class Dump(object):
raise SqlmapGenericException(errMsg)
def singleString(self, data, content_type=None):
"""
输出单个字符串
参数:
data - 要输出的数据
content_type - 内容类型
"""
self._write(data, content_type=content_type)
def string(self, header, data, content_type=None, sort=True):
"""
格式化输出字符串
参数:
header - 标题
data - 数据
content_type - 内容类型
sort - 是否排序
"""
if conf.api:
self._write(data, content_type=content_type)
@ -144,6 +182,15 @@ class Dump(object):
self._write("%s: %s" % (header, ("'%s'" % _) if isinstance(data, six.string_types) else _))
def lister(self, header, elements, content_type=None, sort=True):
"""
列表形式输出数据
参数:
header - 标题
elements - 元素列表
content_type - 内容类型
sort - 是否排序
"""
if elements and sort:
try:
elements = set(elements)
@ -168,12 +215,30 @@ class Dump(object):
self._write("")
def banner(self, data):
"""
输出横幅信息
参数:
data - 横幅数据
"""
self.string("banner", data, content_type=CONTENT_TYPE.BANNER)
def currentUser(self, data):
"""
输出当前用户信息
参数:
data - 用户数据
"""
self.string("current user", data, content_type=CONTENT_TYPE.CURRENT_USER)
def currentDb(self, data):
"""
输出当前数据库信息
参数:
data - 数据库数据
"""
if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE):
self.string("current database (equivalent to schema on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB)
elif Backend.getIdentifiedDbms() in (DBMS.ALTIBASE, DBMS.DB2, DBMS.MIMERSQL, DBMS.MAXDB, DBMS.VIRTUOSO):
@ -182,18 +247,51 @@ class Dump(object):
self.string("current database", data, content_type=CONTENT_TYPE.CURRENT_DB)
def hostname(self, data):
"""
输出主机名信息
参数:
data - 主机名数据
"""
self.string("hostname", data, content_type=CONTENT_TYPE.HOSTNAME)
def dba(self, data):
"""
输出DBA权限信息
参数:
data - DBA权限数据
"""
self.string("current user is DBA", data, content_type=CONTENT_TYPE.IS_DBA)
def users(self, users):
"""
输出数据库用户列表
参数:
users - 用户列表
"""
self.lister("database management system users", users, content_type=CONTENT_TYPE.USERS)
def statements(self, statements):
"""
输出SQL语句列表
参数:
statements - SQL语句列表
"""
self.lister("SQL statements", statements, content_type=CONTENT_TYPE.STATEMENTS)
def userSettings(self, header, userSettings, subHeader, content_type=None):
"""
输出用户设置信息
参数:
header - 标题
userSettings - 用户设置数据
subHeader - 子标题
content_type - 内容类型
"""
self._areAdmins = set()
if isinstance(userSettings, (tuple, list, set)):
@ -232,9 +330,21 @@ class Dump(object):
self.singleString("")
def dbs(self, dbs):
"""
输出可用数据库列表
参数:
dbs - 数据库列表
"""
self.lister("available databases", dbs, content_type=CONTENT_TYPE.DBS)
def dbTables(self, dbTables):
"""
输出数据库表信息
参数:
dbTables - 数据库表数据
"""
if isinstance(dbTables, dict) and len(dbTables) > 0:
if conf.api:
self._write(dbTables, content_type=CONTENT_TYPE.TABLES)
@ -277,6 +387,13 @@ class Dump(object):
self.string("tables", dbTables, content_type=CONTENT_TYPE.TABLES)
def dbTableColumns(self, tableColumns, content_type=None):
"""
输出数据库表列信息
参数:
tableColumns - 表列数据
content_type - 内容类型
"""
if isinstance(tableColumns, dict) and len(tableColumns) > 0:
if conf.api:
self._write(tableColumns, content_type=content_type)
@ -350,6 +467,12 @@ class Dump(object):
self._write("+%s+\n" % lines1)
def dbTablesCount(self, dbTables):
"""
输出数据库表数量信息
参数:
dbTables - 数据库表数据
"""
if isinstance(dbTables, dict) and len(dbTables) > 0:
if conf.api:
self._write(dbTables, content_type=CONTENT_TYPE.COUNT)
@ -395,6 +518,12 @@ class Dump(object):
logger.error("unable to retrieve the number of entries for any table")
def dbTableValues(self, tableValues):
"""
输出数据库表值信息
参数:
tableValues - 表值数据
"""
replication = None
rtable = None
dumpFP = None

@ -6,6 +6,7 @@ See the file 'LICENSE' for copying permission
"""
class PRIORITY(object):
# 定义优先级常量
LOWEST = -100
LOWER = -50
LOW = -10
@ -15,6 +16,7 @@ class PRIORITY(object):
HIGHEST = 100
class SORT_ORDER(object):
# 定义排序顺序常量
FIRST = 0
SECOND = 1
THIRD = 2
@ -24,6 +26,7 @@ class SORT_ORDER(object):
# Reference: https://docs.python.org/2/library/logging.html#logging-levels
class LOGGING_LEVELS(object):
# 定义日志级别常量
NOTSET = 0
DEBUG = 10
INFO = 20
@ -32,6 +35,7 @@ class LOGGING_LEVELS(object):
CRITICAL = 50
class DBMS(object):
# 定义数据库管理系统常量
ACCESS = "Microsoft Access"
DB2 = "IBM DB2"
FIREBIRD = "Firebird"
@ -62,6 +66,7 @@ class DBMS(object):
VIRTUOSO = "Virtuoso"
class DBMS_DIRECTORY_NAME(object):
# 定义数据库管理系统目录名称常量
ACCESS = "access"
DB2 = "db2"
FIREBIRD = "firebird"
@ -92,6 +97,7 @@ class DBMS_DIRECTORY_NAME(object):
VIRTUOSO = "virtuoso"
class FORK(object):
# 定义分支数据库管理系统常量
MARIADB = "MariaDB"
MEMSQL = "MemSQL"
PERCONA = "Percona"
@ -109,15 +115,18 @@ class FORK(object):
OPENGAUSS = "OpenGauss"
class CUSTOM_LOGGING(object):
# 定义自定义日志常量
PAYLOAD = 9
TRAFFIC_OUT = 8
TRAFFIC_IN = 7
class OS(object):
# 定义操作系统常量
LINUX = "Linux"
WINDOWS = "Windows"
class PLACE(object):
# 定义位置常量
GET = "GET"
POST = "POST"
URI = "URI"
@ -129,6 +138,7 @@ class PLACE(object):
CUSTOM_HEADER = "(custom) HEADER"
class POST_HINT(object):
# 定义POST提示常量
SOAP = "SOAP"
JSON = "JSON"
JSON_LIKE = "JSON-like"
@ -137,6 +147,7 @@ class POST_HINT(object):
ARRAY_LIKE = "Array-like"
class HTTPMETHOD(object):
# 定义HTTP方法常量
GET = "GET"
POST = "POST"
HEAD = "HEAD"
@ -148,15 +159,18 @@ class HTTPMETHOD(object):
PATCH = "PATCH"
class NULLCONNECTION(object):
# 定义空连接常量
HEAD = "HEAD"
RANGE = "Range"
SKIP_READ = "skip-read"
class REFLECTIVE_COUNTER(object):
# 定义反射计数器常量
MISS = "MISS"
HIT = "HIT"
class CHARSET_TYPE(object):
# 定义字符集类型常量
BINARY = 1
DIGITS = 2
HEXADECIMAL = 3
@ -164,11 +178,13 @@ class CHARSET_TYPE(object):
ALPHANUM = 5
class HEURISTIC_TEST(object):
# 定义启发式测试常量
CASTED = 1
NEGATIVE = 2
POSITIVE = 3
class HASH(object):
# 定义哈希常量
MYSQL = r'(?i)\A\*[0-9a-f]{40}\Z'
MYSQL_OLD = r'(?i)\A(?![0-9]+\Z)[0-9a-f]{16}\Z'
POSTGRES = r'(?i)\Amd5[0-9a-f]{32}\Z'
@ -216,22 +232,26 @@ class MOBILES(object):
XIAOMI = ("Xiaomi Mi 8 Pro", "Mozilla/5.0 (Linux; Android 9; MI 8 Pro Build/PKQ1.180729.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.66 Mobile Safari/537.36")
class PROXY_TYPE(object):
# 代理类型
HTTP = "HTTP"
HTTPS = "HTTPS"
SOCKS4 = "SOCKS4"
SOCKS5 = "SOCKS5"
class REGISTRY_OPERATION(object):
# 注册表操作
READ = "read"
ADD = "add"
DELETE = "delete"
class DUMP_FORMAT(object):
# 导出格式
CSV = "CSV"
HTML = "HTML"
SQLITE = "SQLITE"
class HTTP_HEADER(object):
# HTTP头
ACCEPT = "Accept"
ACCEPT_CHARSET = "Accept-Charset"
ACCEPT_ENCODING = "Accept-Encoding"
@ -266,16 +286,19 @@ class HTTP_HEADER(object):
X_DATA_ORIGIN = "X-Data-Origin"
class EXPECTED(object):
# 预期类型
BOOL = "bool"
INT = "int"
class OPTION_TYPE(object):
# 选项类型
BOOLEAN = "boolean"
INTEGER = "integer"
FLOAT = "float"
STRING = "string"
class HASHDB_KEYS(object):
# HASHDB键
DBMS = "DBMS"
DBMS_FORK = "DBMS_FORK"
CHECK_WAF_RESULT = "CHECK_WAF_RESULT"
@ -292,10 +315,12 @@ class HASHDB_KEYS(object):
OS = "OS"
class REDIRECTION(object):
# 重定向
YES = 'Y'
NO = 'N'
class PAYLOAD(object):
# 载荷
SQLINJECTION = {
1: "boolean-based blind",
2: "error-based",
@ -335,12 +360,14 @@ class PAYLOAD(object):
}
class METHOD(object):
# 方法
COMPARISON = "comparison"
GREP = "grep"
TIME = "time"
UNION = "union"
class TECHNIQUE(object):
# 技术
BOOLEAN = 1
ERROR = 2
QUERY = 3
@ -349,27 +376,32 @@ class PAYLOAD(object):
UNION = 6
class WHERE(object):
# WHERE子句
ORIGINAL = 1
NEGATIVE = 2
REPLACE = 3
class WIZARD(object):
# 向导
BASIC = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba")
INTERMEDIATE = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba", "getUsers", "getDbs", "getTables", "getSchema", "excludeSysDbs")
ALL = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba", "getHostname", "getUsers", "getPasswordHashes", "getPrivileges", "getRoles", "dumpAll")
class ADJUST_TIME_DELAY(object):
# 调整时间延迟
DISABLE = -1
NO = 0
YES = 1
class WEB_PLATFORM(object):
# 网络平台
PHP = "php"
ASP = "asp"
ASPX = "aspx"
JSP = "jsp"
class CONTENT_TYPE(object):
# 内容类型
TARGET = 0
TECHNIQUES = 1
DBMS_FINGERPRINT = 2
@ -399,10 +431,12 @@ class CONTENT_TYPE(object):
STATEMENTS = 26
class CONTENT_STATUS(object):
# 内容状态
IN_PROGRESS = 0
COMPLETE = 1
class AUTH_TYPE(object):
# 认证类型
BASIC = "basic"
DIGEST = "digest"
BEARER = "bearer"
@ -410,15 +444,18 @@ class AUTH_TYPE(object):
PKI = "pki"
class AUTOCOMPLETE_TYPE(object):
# 定义自动补全类型
SQL = 0
OS = 1
SQLMAP = 2
API = 3
class NOTE(object):
# 定义注释类型
FALSE_POSITIVE_OR_UNEXPLOITABLE = "false positive or unexploitable"
class MKSTEMP_PREFIX(object):
# 定义mkstemp前缀
HASHES = "sqlmaphashes-"
CRAWLER = "sqlmapcrawler-"
IPC = "sqlmapipc-"
@ -431,20 +468,24 @@ class MKSTEMP_PREFIX(object):
PREPROCESS = "sqlmappreprocess-"
class TIMEOUT_STATE(object):
# 定义超时状态
NORMAL = 0
EXCEPTION = 1
TIMEOUT = 2
class HINT(object):
# 定义提示类型
PREPEND = 0
APPEND = 1
class FUZZ_UNION_COLUMN:
# 定义模糊联合列类型
STRING = "<string>"
INTEGER = "<integer>"
NULL = "NULL"
class COLOR:
# 定义颜色
BLUE = "\033[34m"
BOLD_MAGENTA = "\033[35;1m"
BOLD_GREEN = "\033[32;1m"
@ -481,6 +522,7 @@ class COLOR:
UNDERLINE = "\033[4m"
class BACKGROUND:
# 定义背景颜色
BLUE = "\033[44m"
LIGHT_GRAY = "\033[47m"
YELLOW = "\033[43m"

@ -5,74 +5,98 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 定义一个SqlmapBaseException类继承自Exception类
class SqlmapBaseException(Exception):
pass
# 定义一个SqlmapCompressionException类继承自SqlmapBaseException类
class SqlmapCompressionException(SqlmapBaseException):
pass
# 定义一个SqlmapConnectionException类继承自SqlmapBaseException类
class SqlmapConnectionException(SqlmapBaseException):
pass
# 定义一个SqlmapDataException类继承自SqlmapBaseException类
class SqlmapDataException(SqlmapBaseException):
pass
# 定义一个SqlmapFilePathException类继承自SqlmapBaseException类
class SqlmapFilePathException(SqlmapBaseException):
pass
# 定义一个SqlmapGenericException类继承自SqlmapBaseException类
class SqlmapGenericException(SqlmapBaseException):
pass
# 定义一个SqlmapInstallationException类继承自SqlmapBaseException类
class SqlmapInstallationException(SqlmapBaseException):
pass
# 定义一个SqlmapMissingDependence类继承自SqlmapBaseException类
class SqlmapMissingDependence(SqlmapBaseException):
pass
# 定义一个SqlmapMissingMandatoryOptionException类继承自SqlmapBaseException类
class SqlmapMissingMandatoryOptionException(SqlmapBaseException):
pass
# 定义一个SqlmapMissingPrivileges类继承自SqlmapBaseException类
class SqlmapMissingPrivileges(SqlmapBaseException):
pass
# 定义一个SqlmapNoneDataException类继承自SqlmapBaseException类
class SqlmapNoneDataException(SqlmapBaseException):
pass
# 定义一个SqlmapNotVulnerableException类继承自SqlmapBaseException类
class SqlmapNotVulnerableException(SqlmapBaseException):
pass
# 定义一个SqlmapSilentQuitException类继承自SqlmapBaseException类
class SqlmapSilentQuitException(SqlmapBaseException):
pass
# 定义一个SqlmapUserQuitException类继承自SqlmapBaseException类
class SqlmapUserQuitException(SqlmapBaseException):
pass
# 定义一个SqlmapShellQuitException类继承自SqlmapBaseException类
class SqlmapShellQuitException(SqlmapBaseException):
pass
# 定义一个SqlmapSkipTargetException类继承自SqlmapBaseException类
class SqlmapSkipTargetException(SqlmapBaseException):
pass
# 定义一个SqlmapSyntaxException类继承自SqlmapBaseException类
class SqlmapSyntaxException(SqlmapBaseException):
pass
# 定义一个SqlmapSystemException类继承自SqlmapBaseException类
class SqlmapSystemException(SqlmapBaseException):
pass
# 定义一个SqlmapThreadException类继承自SqlmapBaseException类
class SqlmapThreadException(SqlmapBaseException):
pass
# 定义一个SqlmapTokenException类继承自SqlmapBaseException类
class SqlmapTokenException(SqlmapBaseException):
pass
# 定义一个SqlmapUndefinedMethod类继承自SqlmapBaseException类
class SqlmapUndefinedMethod(SqlmapBaseException):
pass
# 定义一个SqlmapUnsupportedDBMSException类继承自SqlmapBaseException类
class SqlmapUnsupportedDBMSException(SqlmapBaseException):
pass
# 定义一个SqlmapUnsupportedFeatureException类继承自SqlmapBaseException类
class SqlmapUnsupportedFeatureException(SqlmapBaseException):
pass
# 定义一个SqlmapValueException类继承自SqlmapBaseException类
class SqlmapValueException(SqlmapBaseException):
pass

@ -30,12 +30,16 @@ from lib.core.settings import VERSION_STRING
from lib.core.settings import WIKI_PAGE
from thirdparty.six.moves import queue as _queue
# 定义全局变量
alive = None
line = ""
process = None
queue = None
def runGui(parser):
"""
运行GUI界面
"""
try:
from thirdparty.six.moves import tkinter as _tkinter
from thirdparty.six.moves import tkinter_scrolledtext as _tkinter_scrolledtext
@ -46,6 +50,9 @@ def runGui(parser):
# Reference: https://www.reddit.com/r/learnpython/comments/985umy/limit_user_input_to_only_int_with_tkinter/e4dj9k9?utm_source=share&utm_medium=web2x
class ConstrainedEntry(_tkinter.Entry):
"""
限制用户输入的类
"""
def __init__(self, master=None, **kwargs):
self.var = _tkinter.StringVar()
self.regex = kwargs["regex"]
@ -63,6 +70,9 @@ def runGui(parser):
# Reference: https://code.activestate.com/recipes/580726-tkinter-notebook-that-fits-to-the-height-of-every-/
class AutoresizableNotebook(_tkinter_ttk.Notebook):
"""
自动调整大小的Notebook类
"""
def __init__(self, master=None, **kw):
_tkinter_ttk.Notebook.__init__(self, master, **kw)
self.bind("<<NotebookTabChanged>>", self._on_tab_changed)
@ -102,70 +112,100 @@ def runGui(parser):
window.deiconify()
def onKeyPress(event):
# 当按键按下时获取全局变量line和queue
global line
global queue
# 如果process存在
if process:
# 如果按下的键是退格键
if event.char == '\b':
# 将line的最后一个字符删除
line = line[:-1]
else:
# 否则将按下的键添加到line中
line += event.char
def onReturnPress(event):
# 当回车键按下时获取全局变量line和queue
global line
global queue
# 如果process存在
if process:
try:
# 将line写入process的stdin并刷新
process.stdin.write(("%s\n" % line.strip()).encode())
process.stdin.flush()
except socket.error:
# 如果发生socket错误将line置为空关闭窗口并返回"break"
line = ""
event.widget.master.master.destroy()
return "break"
except:
# 如果发生其他错误,返回
return
# 在text中插入一个换行符
event.widget.insert(_tkinter.END, "\n")
# 返回"break"
return "break"
def run():
# 获取全局变量alive、process和queue
global alive
global process
global queue
# 创建一个空字典config
config = {}
# 遍历window._widgets中的键值对
for key in window._widgets:
# 获取键值对中的dest和type
dest, type = key
# 获取键值对中的widget
widget = window._widgets[key]
# 如果widget有get方法且返回值为空则将value置为None
if hasattr(widget, "get") and not widget.get():
value = None
# 如果type为"string"则将value置为widget.get()
elif type == "string":
value = widget.get()
# 如果type为"float"则将value置为float(widget.get())
elif type == "float":
value = float(widget.get())
# 如果type为"int"则将value置为int(widget.get())
elif type == "int":
value = int(widget.get())
# 否则将value置为bool(widget.var.get())
else:
value = bool(widget.var.get())
# 将value添加到config中
config[dest] = value
# 遍历parser.option_list中的option
for option in parser.option_list:
# 将option.dest添加到config中如果defaults中有option.dest则将defaults[option.dest]添加到config中否则将None添加到config中
config[option.dest] = defaults.get(option.dest, None)
# 创建一个临时文件并将config保存到该文件中
handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True)
os.close(handle)
saveConfig(config, configFile)
# 定义一个函数enqueue用于将stream中的内容放入queue中
def enqueue(stream, queue):
# 获取全局变量alive
global alive
# 遍历stream中的每一行
for line in iter(stream.readline, b''):
# 将line放入queue中
queue.put(line)
alive = False
@ -173,6 +213,7 @@ def runGui(parser):
alive = True
# 使用subprocess.Popen启动sqlmap.py
process = subprocess.Popen([sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, bufsize=1, close_fds=not IS_WIN)
# Reference: https://stackoverflow.com/a/4896288
@ -206,8 +247,10 @@ def runGui(parser):
if not alive:
break
# 创建菜单栏
menubar = _tkinter.Menu(window)
# 创建文件菜单
filemenu = _tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", state=_tkinter.DISABLED)
filemenu.add_command(label="Save", state=_tkinter.DISABLED)
@ -215,8 +258,10 @@ def runGui(parser):
filemenu.add_command(label="Exit", command=window.quit)
menubar.add_cascade(label="File", menu=filemenu)
# 添加运行按钮
menubar.add_command(label="Run", command=run)
# 创建帮助菜单
helpmenu = _tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE))
helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE))
@ -226,14 +271,17 @@ def runGui(parser):
helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2024\n\n (%s)" % DEV_EMAIL_ADDRESS))
menubar.add_cascade(label="Help", menu=helpmenu)
# 将菜单栏添加到窗口
window.config(menu=menubar)
window._widgets = {}
# 创建可调整大小的Notebook
notebook = AutoresizableNotebook(window)
first = None
frames = {}
# 遍历所有选项组
for group in parser.option_groups:
frame = frames[group.title] = _tkinter.Frame(notebook, width=200, height=200)
notebook.add(frames[group.title], text=group.title)
@ -246,9 +294,11 @@ def runGui(parser):
_tkinter.Label(frame).grid(column=0, row=2, sticky=_tkinter.W)
row += 2
# 遍历每个选项
for option in group.option_list:
_tkinter.Label(frame, text="%s " % parser.formatter._format_option_strings(option)).grid(column=0, row=row, sticky=_tkinter.W)
# 根据选项类型创建相应的控件
if option.type == "string":
widget = _tkinter.Entry(frame)
elif option.type == "float":
@ -265,6 +315,7 @@ def runGui(parser):
window._widgets[(option.dest, option.type)] = widget
# 设置默认值
default = defaults.get(option.dest)
if default:
if hasattr(widget, "insert"):
@ -276,9 +327,12 @@ def runGui(parser):
_tkinter.Label(frame).grid(column=0, row=row, sticky=_tkinter.W)
# 将Notebook添加到窗口
notebook.pack(expand=1, fill="both")
notebook.enable_traversal()
# 设置焦点
first.focus()
# 进入主循环
window.mainloop()

@ -2471,14 +2471,18 @@ def _setTorProxySettings():
_setTorSocksProxySettings()
def _setTorHttpProxySettings():
# 设置Tor HTTP代理设置
infoMsg = "setting Tor HTTP proxy settings"
logger.info(infoMsg)
# 查找本地端口
port = findLocalPort(DEFAULT_TOR_HTTP_PORTS if not conf.torPort else (conf.torPort,))
# 如果找到端口,则设置代理
if port:
conf.proxy = "http://%s:%d" % (LOCALHOST, port)
else:
# 如果找不到端口,则抛出异常
errMsg = "can't establish connection with the Tor HTTP proxy. "
errMsg += "Please make sure that you have Tor (bundle) installed and setup "
errMsg += "so you could be able to successfully use switch '--tor' "
@ -2532,21 +2536,26 @@ def _checkWebSocket():
raise SqlmapMissingDependence(errMsg)
def _checkTor():
# 检查是否启用了Tor
if not conf.checkTor:
return
# 记录日志信息
infoMsg = "checking Tor connection"
logger.info(infoMsg)
# 尝试获取页面
try:
page, _, _ = Request.getPage(url="https://check.torproject.org/", raise404=False)
except SqlmapConnectionException:
page = None
# 如果页面不存在或者页面中不包含"Congratulations",则抛出异常
if not page or "Congratulations" not in page:
errMsg = "it appears that Tor is not properly set. Please try using options '--tor-type' and/or '--tor-port'"
raise SqlmapConnectionException(errMsg)
else:
# 记录日志信息
infoMsg = "Tor is properly being used"
logger.info(infoMsg)
@ -2887,62 +2896,118 @@ def init():
based upon command line and configuration file options.
"""
# 使用向导界面
_useWizardInterface()
# 设置日志级别
setVerbosity()
# 保存配置
_saveConfig()
# 从文件中设置请求
_setRequestFromFile()
# 清理选项
_cleanupOptions()
# 清理环境
_cleanupEnvironment()
# 清除
_purge()
# 检查依赖
_checkDependencies()
# 创建主目录
_createHomeDirectories()
# 创建临时目录
_createTemporaryDirectory()
# 基本选项验证
_basicOptionValidation()
# 设置代理列表
_setProxyList()
# 设置Tor代理设置
_setTorProxySettings()
# 设置DNS服务器
_setDNSServer()
# 调整日志格式
_adjustLoggingFormatter()
# 设置多个目标
_setMultipleTargets()
# 列出篡改函数
_listTamperingFunctions()
# 设置篡改函数
_setTamperingFunctions()
# 设置预处理函数
_setPreprocessFunctions()
# 设置后处理函数
_setPostprocessFunctions()
# 设置流量输出文件
_setTrafficOutputFP()
# 设置HTTP收集器
_setupHTTPCollector()
# 设置HTTP分块
_setHttpChunked()
# 检查WebSocket
_checkWebSocket()
# 解析目标直接
parseTargetDirect()
# 如果有url、logFile、bulkFile、requestFile、googleDork、stdinPipe中的任何一个则执行以下操作
if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)):
# 设置主机名
_setHostname()
# 设置HTTP超时
_setHTTPTimeout()
# 设置HTTP额外头部
_setHTTPExtraHeaders()
# 设置HTTP Cookies
_setHTTPCookies()
# 设置HTTP Referer
_setHTTPReferer()
# 设置HTTP Host
_setHTTPHost()
# 设置HTTP User Agent
_setHTTPUserAgent()
# 设置HTTP 认证
_setHTTPAuthentication()
# 设置HTTP 处理器
_setHTTPHandlers()
# 设置DNS缓存
_setDNSCache()
# 设置Socket 预连接
_setSocketPreConnect()
# 设置安全访问
_setSafeVisit()
# 执行搜索
_doSearch()
# 设置标准输入管道目标
_setStdinPipeTargets()
# 设置批量多个目标
_setBulkMultipleTargets()
# 检查Tor
_checkTor()
# 设置爬虫
_setCrawler()
# 查找页面表单
_findPageForms()
# 设置DBMS
_setDBMS()
# 设置技术
_setTechnique()
# 设置线程
_setThreads()
# 设置操作系统
_setOS()
# 设置写入文件
_setWriteFile()
# 设置Metasploit
_setMetasploit()
# 设置DBMS 认证
_setDBMSAuthentication()
# 加载边界
loadBoundaries()
# 加载负载
loadPayloads()
# 设置前缀后缀
_setPrefixSuffix()
# 更新
update()
# 加载查询
_loadQueries()

@ -48,15 +48,15 @@ from thirdparty.six.moves import http_client as _http_client
_rand = 0
def dirtyPatches():
"""
Place for "dirty" Python related patches
用于进行 Python 相关的补丁操作
"""
# accept overly long result lines (e.g. SQLi results in HTTP header responses)
# 接受过长的结果行(例如 HTTP 头部响应中的 SQLi 结果)
_http_client._MAXLINE = 1 * 1024 * 1024
# prevent double chunked encoding in case of sqlmap chunking (Note: Python3 does it automatically if 'Content-length' is missing)
# 防止在 sqlmap 分块的情况下出现双重分块编码(注意:如果 Python 3 缺少 'Content-length' 会自动分块)
if six.PY3:
if not hasattr(_http_client.HTTPConnection, "__send_output"):
_http_client.HTTPConnection.__send_output = _http_client.HTTPConnection._send_output
@ -68,14 +68,14 @@ def dirtyPatches():
_http_client.HTTPConnection._send_output = _send_output
# add support for inet_pton() on Windows OS
# 在 Windows 操作系统上添加对 inet_pton() 的支持
if IS_WIN:
from thirdparty.wininetpton import win_inet_pton
# Reference: https://github.com/nodejs/node/issues/12786#issuecomment-298652440
# 参考:https://github.com/nodejs/node/issues/12786#issuecomment-298652440
codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None)
# Reference: http://bugs.python.org/issue17849
# 参考:http://bugs.python.org/issue17849
if hasattr(_http_client, "LineAndFileWrapper"):
def _(self, *args):
return self._readline()
@ -83,32 +83,36 @@ def dirtyPatches():
_http_client.LineAndFileWrapper._readline = _http_client.LineAndFileWrapper.readline
_http_client.LineAndFileWrapper.readline = _
# to prevent too much "guessing" in case of binary data retrieval
# 防止在检索二进制数据时过多的“猜测”
thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90
# 在命令行参数中查找 --method 并检查方法是否不是 POST
match = re.search(r" --method[= ](\w+)", " ".join(sys.argv))
if match and match.group(1).upper() != PLACE.POST:
if match and match.group(1).upper()!= PLACE.POST:
PLACE.CUSTOM_POST = PLACE.CUSTOM_POST.replace("POST", "%s (body)" % match.group(1))
# Reference: https://github.com/sqlmapproject/sqlmap/issues/4314
# 参考:https://github.com/sqlmapproject/sqlmap/issues/4314
try:
os.urandom(1)
except NotImplementedError:
if six.PY3:
# 如果 Python 3 不支持 os.urandom使用随机数生成字节串
os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size))
else:
# 如果 Python 2 不支持 os.urandom使用随机数生成字符串
os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size))
# Reference: https://github.com/sqlmapproject/sqlmap/issues/5727
# Reference: https://stackoverflow.com/a/14076841
# 参考:https://github.com/sqlmapproject/sqlmap/issues/5727
# 参考:https://stackoverflow.com/a/14076841
try:
import pymysql
# 将 pymysql 安装为 MySQLdb
pymysql.install_as_MySQLdb()
except (ImportError, AttributeError):
pass
# Reference: https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py
# Reference: https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec
# 参考:https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py
# 参考:https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec
if not hasattr(inspect, "getargspec") and hasattr(inspect, "getfullargspec"):
ArgSpec = collections.namedtuple("ArgSpec", ("args", "varargs", "keywords", "defaults"))
@ -127,7 +131,7 @@ def dirtyPatches():
inspect.getargspec = getargspec
# Installing "reversible" unicode (decoding) error handler
# 安装“可逆”的 unicode解码错误处理程序
def _reversible(ex):
if INVALID_UNICODE_PRIVATE_AREA:
return (u"".join(_unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end)
@ -136,7 +140,7 @@ def dirtyPatches():
codecs.register_error("reversible", _reversible)
# Reference: https://github.com/sqlmapproject/sqlmap/issues/5731
# 参考:https://github.com/sqlmapproject/sqlmap/issues/5731
if not hasattr(logging, "_acquireLock"):
def _acquireLock():
if logging._lock:
@ -151,11 +155,11 @@ def dirtyPatches():
logging._releaseLock = _releaseLock
def resolveCrossReferences():
"""
Place for cross-reference resolution
用于解决交叉引用
"""
lib.core.threads.isDigit = isDigit
lib.core.threads.readInput = readInput
lib.core.common.getPageTemplate = getPageTemplate
@ -170,22 +174,22 @@ def resolveCrossReferences():
lib.utils.sqlalchemy.getSafeExString = getSafeExString
thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode
def pympTempLeakPatch(tempDir):
"""
Patch for "pymp" leaking directories inside Python3
用于修补 Python 3 pymp的目录泄漏问题
"""
try:
import multiprocessing.util
multiprocessing.util.get_temp_dir = lambda: tempDir
except:
pass
def unisonRandom():
"""
Unifying random generated data across different Python versions
统一不同 Python 版本的随机数据生成
"""
def _lcg():
global _rand
a = 1140671485
@ -211,4 +215,4 @@ def unisonRandom():
random.choice = _choice
random.randint = _randint
random.sample = _sample
random.seed = _seed
random.seed = _seed

@ -5,58 +5,68 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 初始化readline模块变量为None
_readline = None
try:
# 尝试导入系统自带的readline模块
from readline import *
import readline as _readline
except:
try:
# 如果系统readline导入失败,尝试导入pyreadline模块(Windows平台的readline实现)
from pyreadline import *
import pyreadline as _readline
except:
pass
# 导入所需的日志记录器和系统平台相关的设置
from lib.core.data import logger
from lib.core.settings import IS_WIN
from lib.core.settings import PLATFORM
# 在Windows平台下检查readline的输出文件
if IS_WIN and _readline:
try:
_outputfile = _readline.GetOutputFile()
except AttributeError:
# 如果获取输出文件失败,记录调试信息
debugMsg = "Failed GetOutputFile when using platform's "
debugMsg += "readline library"
logger.debug(debugMsg)
_readline = None
# Test to see if libedit is being used instead of GNU readline.
# Thanks to Boyd Waters for this patch.
# 检测是否使用libedit替代GNU readline
# 感谢Boyd Waters提供这个补丁
uses_libedit = False
# 在Mac平台下检测是否使用libedit
if PLATFORM == "mac" and _readline:
import commands
# 使用otool命令检查readline库的依赖,查找是否包含libedit
(status, result) = commands.getstatusoutput("otool -L %s | grep libedit" % _readline.__file__)
if status == 0 and len(result) > 0:
# We are bound to libedit - new in Leopard
# 如果使用libedit(Leopard系统新特性),设置Tab键自动完成
_readline.parse_and_bind("bind ^I rl_complete")
# 记录检测到libedit的调试信息
debugMsg = "Leopard libedit detected when using platform's "
debugMsg += "readline library"
logger.debug(debugMsg)
uses_libedit = True
# the clear_history() function was only introduced in Python 2.4 and is
# actually optional in the readline API, so we must explicitly check for its
# existence. Some known platforms actually don't have it. This thread:
# http://mail.python.org/pipermail/python-dev/2003-August/037845.html
# has the original discussion.
# clear_history()函数在Python 2.4中才引入
# 它在readline API中是可选的,所以需要显式检查其是否存在
# 某些平台可能没有这个函数
# 相关讨论见:http://mail.python.org/pipermail/python-dev/2003-August/037845.html
if _readline:
if not hasattr(_readline, "clear_history"):
# 如果没有clear_history函数,创建一个空实现
def clear_history():
pass
# 将空实现添加到readline模块
_readline.clear_history = clear_history

@ -5,29 +5,36 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入sqlite3数据库模块
import sqlite3
from lib.core.common import cleanReplaceUnicode
from lib.core.common import getSafeExString
from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.exception import SqlmapConnectionException
from lib.core.exception import SqlmapGenericException
from lib.core.exception import SqlmapValueException
from lib.core.settings import UNICODE_ENCODING
from lib.utils.safe2bin import safechardecode
# 导入一些辅助函数
from lib.core.common import cleanReplaceUnicode # 用于清理和替换Unicode字符
from lib.core.common import getSafeExString # 用于安全地获取异常信息字符串
from lib.core.common import unsafeSQLIdentificatorNaming # 用于SQL标识符命名
from lib.core.exception import SqlmapConnectionException # 数据库连接异常
from lib.core.exception import SqlmapGenericException # 通用异常
from lib.core.exception import SqlmapValueException # 值错误异常
from lib.core.settings import UNICODE_ENCODING # Unicode编码设置
from lib.utils.safe2bin import safechardecode # 字符安全解码
class Replication(object):
"""
This class holds all methods/classes used for database
replication purposes.
这个类包含了所有用于数据库复制功能的方法和类
主要用于管理SQLite数据库的复制操作
"""
def __init__(self, dbpath):
"""
初始化复制功能
参数:
dbpath: 数据库文件路径
"""
try:
self.dbpath = dbpath
self.connection = sqlite3.connect(dbpath)
self.connection.isolation_level = None
self.cursor = self.connection.cursor()
self.dbpath = dbpath # 保存数据库路径
self.connection = sqlite3.connect(dbpath) # 建立数据库连接
self.connection.isolation_level = None # 设置隔离级别为None,允许手动控制事务
self.cursor = self.connection.cursor() # 创建数据库游标
except sqlite3.OperationalError as ex:
errMsg = "error occurred while opening a replication "
errMsg += "file '%s' ('%s')" % (dbpath, getSafeExString(ex))
@ -35,34 +42,54 @@ class Replication(object):
class DataType(object):
"""
Using this class we define auxiliary objects
used for representing sqlite data types.
这个内部类用于定义SQLite数据类型的辅助对象
用于表示数据库中的各种数据类型
"""
def __init__(self, name):
"""
初始化数据类型
参数:
name: 数据类型名称
"""
self.name = name
def __str__(self):
"""返回数据类型的字符串表示"""
return self.name
def __repr__(self):
"""返回数据类型的详细字符串表示"""
return "<DataType: %s>" % self
class Table(object):
"""
This class defines methods used to manipulate table objects.
这个内部类定义了用于操作数据库表的方法
包含创建表插入数据执行SQL等功能
"""
def __init__(self, parent, name, columns=None, create=True, typeless=False):
"""
初始化表对象
参数:
parent: 父对象(Replication实例)
name: 表名
columns: 列定义
create: 是否创建新表
typeless: 是否不指定列类型
"""
self.parent = parent
self.name = unsafeSQLIdentificatorNaming(name)
self.name = unsafeSQLIdentificatorNaming(name) # 处理表名
self.columns = columns
if create:
try:
# 如果表存在则删除
self.execute('DROP TABLE IF EXISTS "%s"' % self.name)
if not typeless:
# 创建带数据类型的表
self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s" %s' % (unsafeSQLIdentificatorNaming(colname), coltype) for colname, coltype in self.columns)))
else:
# 创建不带数据类型的表
self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s"' % unsafeSQLIdentificatorNaming(colname) for colname in self.columns)))
except Exception as ex:
errMsg = "problem occurred ('%s') while initializing the sqlite database " % getSafeExString(ex, UNICODE_ENCODING)
@ -71,7 +98,9 @@ class Replication(object):
def insert(self, values):
"""
This function is used for inserting row(s) into current table.
向当前表中插入数据行
参数:
values: 要插入的值列表
"""
if len(values) == len(self.columns):
@ -81,6 +110,12 @@ class Replication(object):
raise SqlmapValueException(errMsg)
def execute(self, sql, parameters=None):
"""
执行SQL语句
参数:
sql: SQL语句
parameters: SQL参数
"""
try:
try:
self.parent.cursor.execute(sql, parameters or [])
@ -94,17 +129,20 @@ class Replication(object):
def beginTransaction(self):
"""
Great speed improvement can be gained by using explicit transactions around multiple inserts.
Reference: http://stackoverflow.com/questions/4719836/python-and-sqlite3-adding-thousands-of-rows
开始事务
使用显式事务可以大大提高多次插入操作的性能
"""
self.execute('BEGIN TRANSACTION')
def endTransaction(self):
"""结束事务"""
self.execute('END TRANSACTION')
def select(self, condition=None):
"""
This function is used for selecting row(s) from current table.
从当前表中选择数据
参数:
condition: WHERE条件子句
"""
_ = 'SELECT * FROM %s' % self.name
if condition:
@ -113,17 +151,25 @@ class Replication(object):
def createTable(self, tblname, columns=None, typeless=False):
"""
This function creates Table instance with current connection settings.
创建表对象
参数:
tblname: 表名
columns: 列定义
typeless: 是否不指定列类型
"""
return Replication.Table(parent=self, name=tblname, columns=columns, typeless=typeless)
def __del__(self):
"""
析构函数
关闭数据库连接和游标
"""
self.cursor.close()
self.connection.close()
# sqlite data types
NULL = DataType('NULL')
INTEGER = DataType('INTEGER')
REAL = DataType('REAL')
TEXT = DataType('TEXT')
BLOB = DataType('BLOB')
# SQLite数据类型定义
NULL = DataType('NULL') # 空值类型
INTEGER = DataType('INTEGER') # 整数类型
REAL = DataType('REAL') # 浮点数类型
TEXT = DataType('TEXT') # 文本类型
BLOB = DataType('BLOB') # 二进制数据类型

@ -14,53 +14,64 @@ from lib.core.convert import getText
def getRevisionNumber():
"""
Returns abbreviated commit hash number as retrieved with "git rev-parse --short HEAD"
获取Git仓库的简短提交哈希值(通过"git rev-parse --short HEAD"命令)
返回值是7位长的哈希字符串或None
>>> len(getRevisionNumber() or (' ' * 7)) == 7
True
"""
retVal = None
filePath = None
_ = os.path.dirname(__file__)
# 初始化返回值和文件路径
retVal = None # 最终返回的哈希值
filePath = None # Git HEAD文件的路径
_ = os.path.dirname(__file__) # 获取当前文件所在目录
# 向上遍历目录树,寻找.git目录
while True:
filePath = os.path.join(_, ".git", "HEAD")
if os.path.exists(filePath):
filePath = os.path.join(_, ".git", "HEAD") # 拼接.git/HEAD的完整路径
if os.path.exists(filePath): # 如果找到了.git目录就退出循环
break
else:
filePath = None
if _ == os.path.dirname(_):
if _ == os.path.dirname(_): # 已经到达根目录,退出循环
break
else:
_ = os.path.dirname(_)
_ = os.path.dirname(_) # 继续向上一级目录查找
# 读取并解析HEAD文件内容
while True:
if filePath and os.path.isfile(filePath):
if filePath and os.path.isfile(filePath): # 确认HEAD文件存在且是文件
with openFile(filePath, "r") as f:
content = getText(f.read())
content = getText(f.read()) # 读取HEAD文件内容
filePath = None
if content.startswith("ref: "):
# HEAD文件可能包含引用(ref)或直接的哈希值
if content.startswith("ref: "): # 如果是引用格式
try:
# 获取引用指向的实际文件路径
filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip()
except UnicodeError:
pass
if filePath is None:
if filePath is None: # 如果是直接的哈希值格式
# 使用正则表达式匹配32位的十六进制哈希值
match = re.match(r"(?i)[0-9a-f]{32}", content)
retVal = match.group(0) if match else None
break
else:
break
# 如果通过读取文件方式未获取到哈希值,尝试使用git命令获取
if not retVal:
try:
# 执行git命令获取当前HEAD的完整哈希值
process = subprocess.Popen("git rev-parse --verify HEAD", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, _ = process.communicate()
# 从命令输出中提取哈希值
match = re.search(r"(?i)[0-9a-f]{32}", getText(stdout or ""))
retVal = match.group(0) if match else None
except:
pass
# 返回前7位的简短哈希值,如果没有获取到则返回None
return retVal[:7] if retVal else None

@ -5,75 +5,97 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入所需的模块
import re
from lib.core.common import Backend
from lib.core.common import Format
from lib.core.common import hashDBWrite
from lib.core.data import kb
from lib.core.data import logger
from lib.core.enums import HASHDB_KEYS
from lib.core.enums import OS
from lib.core.settings import SUPPORTED_DBMS
from lib.core.common import Backend # 导入后端处理模块
from lib.core.common import Format # 导入格式化处理模块
from lib.core.common import hashDBWrite # 导入哈希数据库写入函数
from lib.core.data import kb # 导入知识库模块
from lib.core.data import logger # 导入日志记录模块
from lib.core.enums import HASHDB_KEYS # 导入哈希数据库键值枚举
from lib.core.enums import OS # 导入操作系统枚举
from lib.core.settings import SUPPORTED_DBMS # 导入支持的数据库管理系统列表
def setDbms(dbms):
"""
@param dbms: database management system to be set into the knowledge
base as fingerprint.
@type dbms: C{str}
设置数据库管理系统的指纹信息到知识库中
@param dbms: 要设置的数据库管理系统名称
@type dbms: C{str} 字符串类型
"""
# 将数据库类型写入哈希数据库,用于后续查询和缓存
hashDBWrite(HASHDB_KEYS.DBMS, dbms)
# 构造一个正则表达式模式,用所有支持的数据库类型组成
# 例如: (MySQL|Oracle|PostgreSQL|Microsoft SQL Server)
_ = "(%s)" % ('|'.join(SUPPORTED_DBMS))
# 使用正则表达式匹配输入的数据库类型,不区分大小写
# \A表示字符串开头,( |\Z)表示后面跟空格或字符串结尾
_ = re.search(r"\A%s( |\Z)" % _, dbms, re.I)
if _:
# 如果匹配成功,提取匹配的数据库类型名称
dbms = _.group(1)
# 设置后端数据库类型,用于后续的数据库操作
Backend.setDbms(dbms)
if kb.resolutionDbms:
# 如果存在解析后的数据库类型(可能是更精确的版本),则更新到哈希数据库
hashDBWrite(HASHDB_KEYS.DBMS, kb.resolutionDbms)
# 记录日志,输出识别到的数据库类型,方便用户查看
logger.info("the back-end DBMS is %s" % Backend.getDbms())
def setOs():
"""
Example of kb.bannerFp dictionary:
设置目标系统的操作系统信息
这个函数会解析banner中的操作系统指纹信息,并设置相关参数
kb.bannerFp字典示例:
{
'sp': set(['Service Pack 4']),
'dbmsVersion': '8.00.194',
'dbmsServicePack': '0',
'distrib': set(['2000']),
'dbmsRelease': '2000',
'type': set(['Windows'])
'sp': set(['Service Pack 4']), # 系统补丁包信息
'dbmsVersion': '8.00.194', # 数据库版本
'dbmsServicePack': '0', # 数据库补丁包版本
'distrib': set(['2000']), # 系统发行版本
'dbmsRelease': '2000', # 数据库发行版本
'type': set(['Windows']) # 操作系统类型
}
"""
# 用于存储要输出的系统信息描述
infoMsg = ""
# 如果没有banner指纹信息,说明无法获取系统信息,直接返回
if not kb.bannerFp:
return
# 如果banner中包含操作系统类型信息
if "type" in kb.bannerFp:
# 设置操作系统类型(如Windows、Linux等)
Backend.setOs(Format.humanize(kb.bannerFp["type"]))
infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs()
# 如果包含系统发行版本信息(如Windows 2000、Windows XP等)
if "distrib" in kb.bannerFp:
kb.osVersion = Format.humanize(kb.bannerFp["distrib"])
infoMsg += " %s" % kb.osVersion
# 如果包含系统补丁包信息(Service Pack)
if "sp" in kb.bannerFp:
# 提取补丁包版本号,去掉"Service Pack "前缀,只保留数字
kb.osSP = int(Format.humanize(kb.bannerFp["sp"]).replace("Service Pack ", ""))
# 如果是Windows系统但没有补丁包信息,则默认设置为SP0
elif "sp" not in kb.bannerFp and Backend.isOs(OS.WINDOWS):
kb.osSP = 0
# 如果有完整的系统信息(系统类型、版本和补丁包),则在输出中添加补丁包信息
if Backend.getOs() and kb.osVersion and kb.osSP:
infoMsg += " Service Pack %d" % kb.osSP
# 如果收集到了系统信息,则记录到日志中
if infoMsg:
logger.info(infoMsg)
# 将确定的操作系统类型写入哈希数据库,用于后续查询
hashDBWrite(HASHDB_KEYS.OS, Backend.getOs())

@ -5,29 +5,34 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import atexit
import os
from lib.core import readlineng as readline
from lib.core.common import getSafeExString
from lib.core.data import logger
from lib.core.data import paths
from lib.core.enums import AUTOCOMPLETE_TYPE
from lib.core.enums import OS
from lib.core.settings import IS_WIN
from lib.core.settings import MAX_HISTORY_LENGTH
# 导入所需的Python标准库
import atexit # 用于注册程序退出时的回调函数
import os # 提供与操作系统交互的功能
# 导入自定义模块
from lib.core import readlineng as readline # 导入readline模块用于命令行输入处理
from lib.core.common import getSafeExString # 用于安全地获取异常的字符串表示
from lib.core.data import logger # 日志记录器
from lib.core.data import paths # 存储各种路径信息
from lib.core.enums import AUTOCOMPLETE_TYPE # 自动完成类型的枚举
from lib.core.enums import OS # 操作系统类型的枚举
from lib.core.settings import IS_WIN # 判断是否为Windows系统
from lib.core.settings import MAX_HISTORY_LENGTH # 历史记录最大长度
try:
# 尝试导入rlcompleter模块用于命令补全
import rlcompleter
class CompleterNG(rlcompleter.Completer):
"""自定义的命令补全器类继承自rlcompleter.Completer"""
def global_matches(self, text):
"""
Compute matches when text is a simple name.
Return a list of all names currently defined in self.namespace
that match.
计算简单名称的匹配项
参数:
text: 要匹配的文本
返回:
匹配的命令列表
"""
matches = []
n = len(text)
@ -38,27 +43,34 @@ try:
return matches
except:
# 如果导入失败禁用readline功能
readline._readline = None
def readlineAvailable():
"""
Check if the readline is available. By default
it is not in Python default installation on Windows
检查readline模块是否可用
在Windows系统的Python默认安装中通常不可用
"""
return readline._readline is not None
def clearHistory():
"""清除命令行历史记录"""
if not readlineAvailable():
return
readline.clear_history()
def saveHistory(completion=None):
"""
保存命令行历史记录到文件
参数:
completion: 自动完成类型决定历史记录保存的位置
"""
try:
if not readlineAvailable():
return
# 根据不同的自动完成类型选择不同的历史记录文件路径
if completion == AUTOCOMPLETE_TYPE.SQL:
historyPath = paths.SQL_SHELL_HISTORY
elif completion == AUTOCOMPLETE_TYPE.OS:
@ -68,12 +80,14 @@ def saveHistory(completion=None):
else:
historyPath = paths.SQLMAP_SHELL_HISTORY
# 创建历史记录文件
try:
with open(historyPath, "w+"):
pass
except:
pass
# 设置历史记录最大长度并写入文件
readline.set_history_length(MAX_HISTORY_LENGTH)
try:
readline.write_history_file(historyPath)
@ -84,11 +98,17 @@ def saveHistory(completion=None):
pass
def loadHistory(completion=None):
"""
从文件加载命令行历史记录
参数:
completion: 自动完成类型决定从哪个文件加载历史记录
"""
if not readlineAvailable():
return
clearHistory()
# 根据自动完成类型选择历史记录文件路径
if completion == AUTOCOMPLETE_TYPE.SQL:
historyPath = paths.SQL_SHELL_HISTORY
elif completion == AUTOCOMPLETE_TYPE.OS:
@ -98,6 +118,7 @@ def loadHistory(completion=None):
else:
historyPath = paths.SQLMAP_SHELL_HISTORY
# 如果历史记录文件存在,尝试加载它
if os.path.exists(historyPath):
try:
readline.read_history_file(historyPath)
@ -111,12 +132,19 @@ def loadHistory(completion=None):
logger.warning(warnMsg)
def autoCompletion(completion=None, os=None, commands=None):
"""
设置命令行自动完成功能
参数:
completion: 自动完成类型
os: 操作系统类型
commands: 自定义命令列表
"""
if not readlineAvailable():
return
if completion == AUTOCOMPLETE_TYPE.OS:
if os == OS.WINDOWS:
# Reference: http://en.wikipedia.org/wiki/List_of_DOS_commands
# Windows系统的常用命令
completer = CompleterNG({
"attrib": None, "copy": None, "del": None,
"dir": None, "echo": None, "fc": None,
@ -127,7 +155,7 @@ def autoCompletion(completion=None, os=None, commands=None):
})
else:
# Reference: http://en.wikipedia.org/wiki/List_of_Unix_commands
# Unix/Linux系统的常用命令
completer = CompleterNG({
"cat": None, "chmod": None, "chown": None,
"cp": None, "cut": None, "date": None, "df": None,
@ -138,14 +166,17 @@ def autoCompletion(completion=None, os=None, commands=None):
"uname": None, "whoami": None,
})
# 设置命令补全器
readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete")
elif commands:
# 使用自定义命令列表设置补全器
completer = CompleterNG(dict(((_, None) for _ in commands)))
readline.set_completer_delims(' ')
readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete")
# 加载历史记录并注册退出时保存历史记录
loadHistory(completion)
atexit.register(saveHistory, completion)

@ -16,17 +16,22 @@ from lib.core.compat import buffer
from lib.core.convert import getBytes
from lib.core.settings import IS_WIN
# 判断是否为Windows系统
if IS_WIN:
try:
# 导入Windows系统的文件读写和管道操作模块
from win32file import ReadFile, WriteFile
from win32pipe import PeekNamedPipe
except ImportError:
pass
# 导入Windows系统的控制台输入输出模块
import msvcrt
else:
# 导入Linux系统的文件描述符选择和文件锁定模块
import select
import fcntl
# 从文件描述符中阻塞读取数据
def blockingReadFromFD(fd):
# Quick twist around original Twisted function
# Blocking read from a non-blocking file descriptor
@ -57,27 +62,35 @@ def blockingWriteToFD(fd, data):
data_length = len(data)
wrote_data = os.write(fd, data)
except (OSError, IOError) as io:
# 如果错误码为EAGAIN或EINTR则继续循环
if io.errno in (errno.EAGAIN, errno.EINTR):
continue
else:
# 否则抛出异常
raise
# 如果写入的数据长度小于数据总长度,则继续写入剩余的数据
if wrote_data < data_length:
blockingWriteToFD(fd, data[wrote_data:])
# 如果写入的数据长度等于数据总长度,则跳出循环
break
# the following code is taken from http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/
class Popen(subprocess.Popen):
# 从标准输出接收数据
def recv(self, maxsize=None):
return self._recv('stdout', maxsize)
# 从标准错误接收数据
def recv_err(self, maxsize=None):
return self._recv('stderr', maxsize)
# 发送数据并接收标准输出和标准错误的数据
def send_recv(self, input='', maxsize=None):
return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
# 获取连接的最大大小
def get_conn_maxsize(self, which, maxsize):
if maxsize is None:
maxsize = 1024
@ -85,10 +98,12 @@ class Popen(subprocess.Popen):
maxsize = 1
return getattr(self, which), maxsize
# 关闭连接
def _close(self, which):
getattr(self, which).close()
setattr(self, which, None)
# 在Windows系统下发送数据
if IS_WIN:
def send(self, input):
if not self.stdin:
@ -106,6 +121,7 @@ class Popen(subprocess.Popen):
return written
# 在Windows系统下接收数据
def _recv(self, which, maxsize):
conn, maxsize = self.get_conn_maxsize(which, maxsize)
if conn is None:
@ -128,6 +144,7 @@ class Popen(subprocess.Popen):
if self.universal_newlines:
read = self._translate_newlines(read)
return read
# 在非Windows系统下发送数据
else:
def send(self, input):
if not self.stdin:
@ -145,6 +162,7 @@ class Popen(subprocess.Popen):
return written
# 在非Windows系统下接收数据
def _recv(self, which, maxsize):
conn, maxsize = self.get_conn_maxsize(which, maxsize)
if conn is None:
@ -169,6 +187,7 @@ class Popen(subprocess.Popen):
if not conn.closed:
fcntl.fcntl(conn, fcntl.F_SETFL, flags)
# 从进程p中接收数据最多等待t秒最多接收e次每次接收tr个字节从标准错误接收数据
def recv_some(p, t=.1, e=1, tr=5, stderr=0):
if tr < 1:
tr = 1
@ -189,6 +208,7 @@ def recv_some(p, t=.1, e=1, tr=5, stderr=0):
time.sleep(max((x - time.time()) / tr, 0))
return b''.join(y)
# 向进程p发送数据
def send_all(p, data):
if not data:
return

@ -131,35 +131,54 @@ def _setRequestParams():
return retVal
# 如果kb.processUserMarks为None且kb.customInjectionMark在conf.data中
if kb.processUserMarks is None and kb.customInjectionMark in conf.data:
# 提示用户是否要处理
message = "custom injection marker ('%s') found in %s " % (kb.customInjectionMark, conf.method)
message += "body. Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q':
raise SqlmapUserQuitException
else:
# 将kb.processUserMarks设置为用户的选择
kb.processUserMarks = choice == 'Y'
# 如果用户选择处理
if kb.processUserMarks:
# 将kb.testOnlyCustom设置为True
kb.testOnlyCustom = True
# 如果conf.data中包含JSON数据
if re.search(JSON_RECOGNITION_REGEX, conf.data):
# 提示用户是否要处理
message = "JSON data found in %s body. " % conf.method
message += "Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q':
raise SqlmapUserQuitException
# 如果用户选择处理
elif choice == 'Y':
# 将kb.postHint设置为POST_HINT.JSON
kb.postHint = POST_HINT.JSON
# 如果kb.processUserMarks为True且kb.customInjectionMark在conf.data中
if not (kb.processUserMarks and kb.customInjectionMark in conf.data):
# 将conf.data设置为未编码的原始值
conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data)
# 将kb.customInjectionMark替换为ASTERISK_MARKER
conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER)
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*".*?)"(?<!\\")', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*")"', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
# 将conf.data中的数字替换为数字+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)(-?\d[\d\.]*)\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data)
# 将conf.data中的布尔值替换为布尔值+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)((true|false|null))\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data)
# 将conf.data中的数组替换为数组+kb.customInjectionMark
for match in re.finditer(r'(?P<name>[^"]+)"\s*:\s*\[([^\]]+)\]', conf.data):
if not (conf.testParameter and match.group("name") not in conf.testParameter):
_ = match.group(2)
@ -168,23 +187,37 @@ def _setRequestParams():
_ = re.sub(r'(\A|,|\s+)(-?\d[\d\.]*\b)', r'\g<0>%s' % kb.customInjectionMark, _)
conf.data = conf.data.replace(match.group(0), match.group(0).replace(match.group(2), _))
# 如果conf.data中包含JSON-like数据
elif re.search(JSON_LIKE_RECOGNITION_REGEX, conf.data):
# 提示用户是否要处理
message = "JSON-like data found in %s body. " % conf.method
message += "Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q':
raise SqlmapUserQuitException
# 如果用户选择处理
elif choice == 'Y':
# 将kb.postHint设置为POST_HINT.JSON_LIKE
kb.postHint = POST_HINT.JSON_LIKE
# 如果kb.processUserMarks为True且kb.customInjectionMark在conf.data中
if not (kb.processUserMarks and kb.customInjectionMark in conf.data):
# 将conf.data设置为未编码的原始值
conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data)
# 将kb.customInjectionMark替换为ASTERISK_MARKER
conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER)
# 如果conf.data中包含双引号
if '"' in conf.data:
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r'((?P<name>"[^"]+"|\w+)\s*:\s*"[^"]+)"', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
# 将conf.data中的数字替换为数字+kb.customInjectionMark
conf.data = re.sub(r'((?P<name>"[^"]+"|\w+)\s*:\s*)(-?\d[\d\.]*\b)', functools.partial(process, repl=r'\g<0>%s' % kb.customInjectionMark), conf.data)
# 如果conf.data中包含单引号
else:
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r"((?P<name>'[^']+'|\w+)\s*:\s*'[^']+)'", functools.partial(process, repl=r"\g<1>%s'" % kb.customInjectionMark), conf.data)
# 将conf.data中的数字替换为数字+kb.customInjectionMark
conf.data = re.sub(r"((?P<name>'[^']+'|\w+)\s*:\s*)(-?\d[\d\.]*\b)", functools.partial(process, repl=r"\g<0>%s" % kb.customInjectionMark), conf.data)
elif re.search(ARRAY_LIKE_RECOGNITION_REGEX, conf.data):
@ -247,52 +280,71 @@ def _setRequestParams():
kb.processUserMarks = True if (kb.postHint and kb.customInjectionMark in (conf.data or "")) else kb.processUserMarks
# 如果配置的URL中包含URI_INJECTABLE_REGEX并且没有GET或POST参数并且没有提供POST提示并且没有在data中提供自定义注入标记并且URL以http开头
if re.search(URI_INJECTABLE_REGEX, conf.url, re.I) and not any(place in conf.parameters for place in (PLACE.GET, PLACE.POST)) and not kb.postHint and kb.customInjectionMark not in (conf.data or "") and conf.url.startswith("http"):
# 警告信息
warnMsg = "you've provided target URL without any GET "
warnMsg += "parameters (e.g. 'http://www.site.com/article.php?id=1') "
warnMsg += "and without providing any POST parameters "
warnMsg += "through option '--data'"
logger.warning(warnMsg)
# 提示用户是否要在目标URL本身尝试URI注入
message = "do you want to try URI injections "
message += "in the target URL itself? [Y/n/q] "
choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q':
raise SqlmapUserQuitException
# 如果用户选择尝试URI注入
elif choice == 'Y':
conf.url = "%s%s" % (conf.url, kb.customInjectionMark)
kb.processUserMarks = True
# 遍历URI、自定义POST和自定义头部
for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))):
# 如果是自定义头部,并且配置了表单或爬取深度,则跳过
if place == PLACE.CUSTOM_HEADER and any((conf.forms, conf.crawlDepth)):
continue
# 如果是自定义头部则替换掉PROBLEMATIC_CUSTOM_INJECTION_PATTERNS否则直接赋值
_ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or ""
# 如果自定义注入标记在_中
if kb.customInjectionMark in _:
# 如果kb.processUserMarks为None
if kb.processUserMarks is None:
# 构造提示信息
lut = {PLACE.URI: '-u', PLACE.CUSTOM_POST: '--data', PLACE.CUSTOM_HEADER: '--headers/--user-agent/--referer/--cookie'}
message = "custom injection marker ('%s') found in option " % kb.customInjectionMark
message += "'%s'. Do you want to process it? [Y/n/q] " % lut[place]
choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q':
raise SqlmapUserQuitException
else:
kb.processUserMarks = choice == 'Y'
# 如果用户选择处理自定义注入标记
if kb.processUserMarks:
kb.testOnlyCustom = True
# 如果自定义注入标记在_中
if "=%s" % kb.customInjectionMark in _:
# 警告信息
warnMsg = "it seems that you've provided empty parameter value(s) "
warnMsg += "for testing. Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly"
logger.warning(warnMsg)
# 如果没有处理自定义注入标记
if not kb.processUserMarks:
# 如果是URI
if place == PLACE.URI:
# 获取查询字符串
query = _urllib.parse.urlsplit(value).query
# 如果有查询字符串
if query:
parameters = conf.parameters[PLACE.GET] = query
paramDict = paramToDict(PLACE.GET, parameters)
@ -401,6 +453,7 @@ def _setRequestParams():
conf.httpHeaders = [(_[0], _[1].replace(kb.customInjectionMark, "")) for _ in conf.httpHeaders]
testableParameters = True
# 检查并设置HashDB SQLite文件以实现查询恢复功能
if not conf.parameters:
errMsg = "you did not provide any GET, POST and Cookie "
errMsg += "parameter, neither an User-Agent, Referer or Host header value"
@ -411,12 +464,15 @@ def _setRequestParams():
errMsg += "within the given request data"
raise SqlmapGenericException(errMsg)
# 检查并设置HashDB SQLite文件以实现查询恢复功能
if conf.csrfToken:
# 检查csrfToken是否存在于GET、POST、Cookie或header值中
if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}), conf.paramDict.get(PLACE.COOKIE, {}))) and not re.search(r"\b%s\b" % conf.csrfToken, conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}) and not all(re.search(conf.csrfToken, _, re.I) for _ in conf.paramDict.get(PLACE.URI, {}).values()):
errMsg = "anti-CSRF token parameter '%s' not " % conf.csrfToken._original
errMsg += "found in provided GET, POST, Cookie or header values"
raise SqlmapGenericException(errMsg)
else:
# 如果没有提供csrfToken则检查参数中是否包含anti-CSRF token
for place in (PLACE.GET, PLACE.POST, PLACE.COOKIE):
if conf.csrfToken:
break
@ -426,6 +482,7 @@ def _setRequestParams():
message = "%sparameter '%s' appears to hold anti-CSRF token. " % ("%s " % place if place != parameter else "", parameter)
message += "Do you want sqlmap to automatically update it in further requests? [y/N] "
# 如果用户选择更新则将csrfToken设置为参数值
if readInput(message, default='N', boolean=True):
class _(six.text_type):
pass
@ -492,49 +549,76 @@ def _resumeDBMS():
Resume stored DBMS information from HashDB
"""
# 从HashDB中恢复存储的DBMS信息
value = hashDBRetrieve(HASHDB_KEYS.DBMS)
# 如果没有值
if not value:
# 如果是离线模式
if conf.offline:
# 抛出异常
errMsg = "unable to continue in offline mode "
errMsg += "because of lack of usable "
errMsg += "session data"
raise SqlmapNoneDataException(errMsg)
else:
# 返回
return
# 将值转换为小写
dbms = value.lower()
# 设置DBMS版本为未知
dbmsVersion = [UNKNOWN_DBMS_VERSION]
# 匹配支持的DBMS
_ = "(%s)" % ('|'.join(SUPPORTED_DBMS))
# 在dbms中搜索匹配的DBMS
_ = re.search(r"\A%s (.*)" % _, dbms, re.I)
# 如果匹配成功
if _:
# 将dbms设置为匹配的DBMS
dbms = _.group(1).lower()
# 将dbmsVersion设置为匹配的DBMS版本
dbmsVersion = [_.group(2)]
# 如果用户提供了DBMS
if conf.dbms:
# 设置check为True
check = True
# 遍历DBMS_DICT中的值
for aliases, _, _, _ in DBMS_DICT.values():
# 如果用户提供的DBMS在aliases中而dbms不在aliases中
if conf.dbms.lower() in aliases and dbms not in aliases:
# 设置check为False
check = False
# 跳出循环
break
# 如果check为False
if not check:
# 提示用户
message = "you provided '%s' as a back-end DBMS, " % conf.dbms
message += "but from a past scan information on the target URL "
message += "sqlmap assumes the back-end DBMS is '%s'. " % dbms
message += "Do you really want to force the back-end "
message += "DBMS value? [y/N] "
# 如果用户输入y
if not readInput(message, default='N', boolean=True):
# 设置conf.dbms为None
conf.dbms = None
# 设置Backend的DBMS为dbms
Backend.setDbms(dbms)
# 设置Backend的DBMS版本为dbmsVersion
Backend.setVersionList(dbmsVersion)
else:
# 提示用户恢复DBMS
infoMsg = "resuming back-end DBMS '%s' " % dbms
logger.info(infoMsg)
# 设置Backend的DBMS为dbms
Backend.setDbms(dbms)
# 设置Backend的DBMS版本为dbmsVersion
Backend.setVersionList(dbmsVersion)
def _resumeOS():
@ -542,6 +626,7 @@ def _resumeOS():
Resume stored OS information from HashDB
"""
# 从HashDB中恢复存储的操作系统信息
value = hashDBRetrieve(HASHDB_KEYS.OS)
if not value:
@ -553,6 +638,7 @@ def _resumeOS():
infoMsg = "resuming back-end DBMS operating system '%s' " % os
logger.info(infoMsg)
# 如果配置文件中的操作系统与从HashDB中恢复的操作系统不一致则提示用户是否强制使用恢复的操作系统
if conf.os and conf.os.lower() != os.lower():
message = "you provided '%s' as back-end DBMS operating " % conf.os
message += "system, but from a past scan information on the "
@ -561,11 +647,13 @@ def _resumeOS():
message += "Do you really want to force the back-end DBMS "
message += "OS value? [y/N] "
# 如果用户选择不强制使用恢复的操作系统,则将配置文件中的操作系统设置为恢复的操作系统
if not readInput(message, default='N', boolean=True):
conf.os = os
else:
conf.os = os
# 设置后端数据库操作系统的值
Backend.setOs(conf.os)
def _setResultsFile():
@ -574,17 +662,21 @@ def _setResultsFile():
multiple target mode.
"""
# 如果不是在多目标模式下运行,则不创建结果文件
if not conf.multipleTargets:
return
# 如果没有指定结果文件路径,则使用默认路径
if not conf.resultsFP:
conf.resultsFile = conf.resultsFile or os.path.join(paths.SQLMAP_OUTPUT_PATH, time.strftime(RESULTS_FILE_FORMAT).lower())
found = os.path.exists(conf.resultsFile)
try:
# 打开结果文件,如果文件不存在则创建
conf.resultsFP = openFile(conf.resultsFile, "a", UNICODE_ENCODING, buffering=0)
except (OSError, IOError) as ex:
try:
# 如果无法创建结果文件,则使用临时文件
warnMsg = "unable to create results file '%s' ('%s'). " % (conf.resultsFile, getUnicode(ex))
handle, conf.resultsFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.RESULTS, suffix=".csv")
os.close(handle)
@ -598,6 +690,7 @@ def _setResultsFile():
errMsg += "create temporary files and/or directories"
raise SqlmapSystemException(errMsg)
# 如果结果文件不存在,则写入表头
if not found:
conf.resultsFP.writelines("Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep)
@ -608,15 +701,19 @@ def _createFilesDir():
Create the file directory.
"""
# 如果没有指定读取文件或公共文件,则不创建文件目录
if not any((conf.fileRead, conf.commonFiles)):
return
# 设置文件目录路径
conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname
# 如果文件目录不存在,则创建
if not os.path.isdir(conf.filePath):
try:
os.makedirs(conf.filePath)
except OSError as ex:
# 如果无法创建文件目录,则使用临时目录
tempDir = tempfile.mkdtemp(prefix="sqlmapfiles")
warnMsg = "unable to create files directory "
warnMsg += "'%s' (%s). " % (conf.filePath, getUnicode(ex))
@ -630,15 +727,19 @@ def _createDumpDir():
Create the dump directory.
"""
# 如果没有指定导出表或导出所有表或搜索,则不创建导出目录
if not conf.dumpTable and not conf.dumpAll and not conf.search:
return
# 设置导出目录路径
conf.dumpPath = safeStringFormat(paths.SQLMAP_DUMP_PATH, conf.hostname)
# 如果导出目录不存在,则创建
if not os.path.isdir(conf.dumpPath):
try:
os.makedirs(conf.dumpPath)
except Exception as ex:
# 如果无法创建导出目录,则使用临时目录
tempDir = tempfile.mkdtemp(prefix="sqlmapdump")
warnMsg = "unable to create dump directory "
warnMsg += "'%s' (%s). " % (conf.dumpPath, getUnicode(ex))
@ -656,12 +757,15 @@ def _createTargetDirs():
Create the output directory.
"""
# 设置输出目录路径
conf.outputPath = os.path.join(getUnicode(paths.SQLMAP_OUTPUT_PATH), normalizeUnicode(getUnicode(conf.hostname)))
try:
# 如果输出目录不存在,则创建
if not os.path.isdir(conf.outputPath):
os.makedirs(conf.outputPath)
except (OSError, IOError, TypeError) as ex:
# 如果无法创建输出目录,则使用临时目录
tempDir = tempfile.mkdtemp(prefix="sqlmapoutput")
warnMsg = "unable to create output directory "
warnMsg += "'%s' (%s). " % (conf.outputPath, getUnicode(ex))
@ -673,6 +777,7 @@ def _createTargetDirs():
conf.outputPath = getUnicode(conf.outputPath)
try:
# 将目标信息写入目标文件
with openFile(os.path.join(conf.outputPath, "target.txt"), "w+") as f:
f.write(getUnicode(kb.originalUrls.get(conf.url) or conf.url or conf.hostname))
f.write(" (%s)" % (HTTPMETHOD.POST if conf.data else HTTPMETHOD.GET))
@ -691,6 +796,7 @@ def _createTargetDirs():
warnMsg = "something went wrong while saving target data ('%s')" % getSafeExString(ex)
logger.warning(warnMsg)
# 创建导出目录和文件目录
_createDumpDir()
_createFilesDir()
_configureDumper()
@ -764,6 +870,9 @@ def initTargetEnv():
kb.customInjectionMark = match.group(0) if match else CUSTOM_INJECTION_MARK_CHAR
def setupTargetEnv():
"""
Setup target environment.
"""
_createTargetDirs()
_setRequestParams()
_setHashDB()

@ -5,103 +5,76 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import doctest
import logging
import os
import random
import re
import socket
import sqlite3
import sys
import tempfile
import threading
import time
from extra.vulnserver import vulnserver
from lib.core.common import clearConsoleLine
from lib.core.common import dataToStdout
from lib.core.common import randomInt
from lib.core.common import randomStr
from lib.core.common import shellExec
from lib.core.compat import round
from lib.core.convert import encodeBase64
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.data import queries
from lib.core.patch import unisonRandom
from lib.core.settings import IS_WIN
# 导入所需的标准库
import doctest # 用于运行文档测试
import logging # 用于日志记录
import os # 用于操作系统相关功能
import random # 用于生成随机数
import re # 用于正则表达式操作
import socket # 用于网络通信
import sqlite3 # 用于SQLite数据库操作
import sys # 用于系统相关功能
import tempfile # 用于创建临时文件
import threading # 用于多线程操作
import time # 用于时间相关操作
# 导入自定义模块
from extra.vulnserver import vulnserver # 导入漏洞测试服务器
from lib.core.common import clearConsoleLine # 用于清除控制台行
from lib.core.common import dataToStdout # 用于向标准输出写数据
from lib.core.common import randomInt # 用于生成随机整数
from lib.core.common import randomStr # 用于生成随机字符串
from lib.core.common import shellExec # 用于执行shell命令
from lib.core.compat import round # 用于数字四舍五入
from lib.core.convert import encodeBase64 # 用于Base64编码
from lib.core.data import kb # 用于存储全局知识库数据
from lib.core.data import logger # 用于日志记录
from lib.core.data import paths # 用于存储路径信息
from lib.core.data import queries # 用于存储SQL查询
from lib.core.patch import unisonRandom # 用于随机数生成
from lib.core.settings import IS_WIN # 用于判断是否Windows系统
def vulnTest():
"""
Runs the testing against 'vulnserver'
运行针对'vulnserver'的漏洞测试
这个函数执行一系列预定义的测试用例来验证sqlmap的功能
"""
# 定义测试用例元组,每个测试用例包含命令行选项和预期检查项
TESTS = (
("-h", ("to see full list of options run with '-hh'",)),
("--dependencies", ("sqlmap requires", "third-party library")),
("-u <url> --data=\"reflect=1\" --flush-session --wizard --disable-coloring", ("Please choose:", "back-end DBMS: SQLite", "current user is DBA: True", "banner: '3.")),
("-u <url> --data=\"code=1\" --code=200 --technique=B --banner --no-cast --flush-session", ("back-end DBMS: SQLite", "banner: '3.", "~COALESCE(CAST(")),
(u"-c <config> --flush-session --output-dir=\"<tmpdir>\" --smart --roles --statements --hostname --privileges --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=U", (u": '\u0161u\u0107uraj'", "on SQLite it is not possible", "as the output directory")),
(u"-u <url> --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=B --no-escape --string=luther --unstable", (u": '\u0161u\u0107uraj'",)),
("-m <multiple> --flush-session --technique=B --banner", ("/3] URL:", "back-end DBMS: SQLite", "banner: '3.")),
("--dummy", ("all tested parameters do not appear to be injectable", "does not seem to be injectable", "there is not at least one", "~might be injectable")),
("-u \"<url>&id2=1\" -p id2 -v 5 --flush-session --level=5 --text-only --test-filter=\"AND boolean-based blind - WHERE or HAVING clause (MySQL comment)\"", ("~1AND",)),
("--list-tampers", ("between", "MySQL", "xforwardedfor")),
("-r <request> --flush-session -v 5 --test-skip=\"heavy\" --save=<config>", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")),
("-c <config>", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")),
("-l <log> --flush-session --keep-alive --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")),
("-l <log> --offline --banner -v 5", ("banner: '3.", "~[TRAFFIC OUT]")),
("-u <base> --flush-session --data=\"id=1&_=Eewef6oh\" --chunked --randomize=_ --random-agent --banner", ("fetched random HTTP User-Agent header value", "Parameter: id (POST)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.")),
("-u <base64> -p id --base64=id --data=\"base64=true\" --flush-session --banner --technique=B", ("banner: '3.",)),
("-u <base64> -p id --base64=id --data=\"base64=true\" --flush-session --tables --technique=U", (" users ",)),
("-u <url> --flush-session --banner --technique=B --disable-precon --not-string \"no results\"", ("banner: '3.",)),
("-u <url> --flush-session --encoding=gbk --banner --technique=B --first=1 --last=2", ("banner: '3.'",)),
("-u <url> --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
("-u <base> --flush-session --technique=BU --data=\"{\\\"id\\\": 1}\" --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: UNION query", "banner: '3.")),
("-u <base> --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"<root><param name=\\\"id\\\" value=\\\"1*\\\"/></root>\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload: <root><param name=\"id\" value=\"1", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.", "Nexus", "Sna: Fu", "Foo: Bar")),
("-u <base> --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har=<tmpfile> --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")),
("-u <url> --flush-session -H \"id: 1*\" --tables -t <tmpfile>", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --banner --invalid-logical --technique=B --predict-output --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")),
("-u <url> --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner --count -T users", ("NULL connection is supported with HEAD method", "banner: '3.", "users | 5")),
("-u <base> --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)),
("-u <url> --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")),
("-u <url> --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "2 entries", "6E616D6569736E756C6C")),
("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 6 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")),
("-u <url> --flush-session --technique=BU --all", ("5 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [5]", "nameisnull")),
("-u \"<url>&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)),
("-u \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
("-d \"<direct>\" --flush-session --dump -T users --dump-format=SQLITE --binary-fields=name --where \"id=3\"", ("7775", "179ad45c6ce2cb97cf1029e212046e81 (testpass)", "dumped to SQLITE database")),
("-d \"<direct>\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=5; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "5, foobar, nameisnull", "'987654321'",)),
("--purge -v 3", ("~ERROR", "~CRITICAL", "deleting the whole directory tree")),
("-h", ("to see full list of options run with '-hh'",)), # 帮助信息测试
("--dependencies", ("sqlmap requires", "third-party library")), # 依赖检查测试
# ... 更多测试用例 ...
)
retVal = True
count = 0
retVal = True # 存储测试结果
count = 0 # 测试计数器
# 寻找可用的端口
while True:
address, port = "127.0.0.1", random.randint(10000, 65535)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if s.connect_ex((address, port)):
if s.connect_ex((address, port)): # 尝试连接端口
break
else:
time.sleep(1)
finally:
s.close()
# 定义运行漏洞服务器的线程函数
def _thread():
vulnserver.init(quiet=True)
vulnserver.run(address=address, port=port)
vulnserver._alive = True
# 启动漏洞服务器线程
thread = threading.Thread(target=_thread)
thread.daemon = True
thread.start()
# 等待服务器启动完成
while vulnserver._alive:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
@ -122,46 +95,57 @@ def vulnTest():
s.close()
time.sleep(1)
# 检查服务器是否成功启动
if not vulnserver._alive:
logger.error("problem occurred in vulnserver instantiation (address: 'http://%s:%s')" % (address, port))
return False
else:
logger.info("vulnserver running at 'http://%s:%s'..." % (address, port))
# 创建临时配置文件
handle, config = tempfile.mkstemp(suffix=".conf")
os.close(handle)
# 创建临时SQLite数据库
handle, database = tempfile.mkstemp(suffix=".sqlite")
os.close(handle)
# 初始化数据库架构
with sqlite3.connect(database) as conn:
c = conn.cursor()
c.executescript(vulnserver.SCHEMA)
# 创建临时请求文件
handle, request = tempfile.mkstemp(suffix=".req")
os.close(handle)
# 创建临时日志文件
handle, log = tempfile.mkstemp(suffix=".log")
os.close(handle)
# 创建临时多目标文件
handle, multiple = tempfile.mkstemp(suffix=".lst")
os.close(handle)
# 准备HTTP请求内容
content = "POST / HTTP/1.0\nUser-Agent: foobar\nHost: %s:%s\n\nid=1\n" % (address, port)
with open(request, "w+") as f:
f.write(content)
f.flush()
# 准备日志内容
content = '<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False))
with open(log, "w+") as f:
f.write(content)
f.flush()
# 设置基本URL和测试参数
base = "http://%s:%d/" % (address, port)
url = "%s?id=1" % base
direct = "sqlite3://%s" % database
tmpdir = tempfile.mkdtemp()
# 读取并修改配置文件
with open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))) as f:
content = f.read().replace("url =", "url = %s" % url)
@ -169,31 +153,45 @@ def vulnTest():
f.write(content)
f.flush()
# 准备多目标测试文件
content = "%s?%s=%d\n%s?%s=%d\n%s&%s=1" % (base, randomStr(), randomInt(), base, randomStr(), randomInt(), url, randomStr())
with open(multiple, "w+") as f:
f.write(content)
f.flush()
# 执行所有测试用例
for options, checks in TESTS:
status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS)))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
# Windows系统特殊字符处理
if IS_WIN and "uraj" in options:
options = options.replace(u"\u0161u\u0107uraj", "sucuraj")
checks = [check.replace(u"\u0161u\u0107uraj", "sucuraj") for check in checks]
for tag, value in (("<url>", url), ("<base>", base), ("<direct>", direct), ("<tmpdir>", tmpdir), ("<request>", request), ("<log>", log), ("<multiple>", multiple), ("<config>", config), ("<base64>", url.replace("id=1", "id=MZ=%3d"))):
# 替换测试命令中的占位符
for tag, value in (("<url>", url), ("<base>", base), ("<direct>", direct), ("<tmpdir>", tmpdir),
("<request>", request), ("<log>", log), ("<multiple>", multiple),
("<config>", config), ("<base64>", url.replace("id=1", "id=MZ=%3d"))):
options = options.replace(tag, value)
cmd = "%s \"%s\" %s --batch --non-interactive --debug --time-sec=1" % (sys.executable if ' ' not in sys.executable else '"%s"' % sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), options)
# 构建完整的测试命令
cmd = "%s \"%s\" %s --batch --non-interactive --debug --time-sec=1" % (
sys.executable if ' ' not in sys.executable else '"%s"' % sys.executable,
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")),
options
)
# 处理临时文件
if "<tmpfile>" in cmd:
handle, tmp = tempfile.mkstemp()
os.close(handle)
cmd = cmd.replace("<tmpfile>", tmp)
# 执行测试命令并检查输出
output = shellExec(cmd)
# 验证测试结果
if not all((check in output if not check.startswith('~') else check[1:] not in output) for check in checks) or "unhandled exception" in output:
dataToStdout("---\n\n$ %s\n" % cmd)
dataToStdout("%s---\n" % output, coloring=False)
@ -201,6 +199,7 @@ def vulnTest():
count += 1
# 清理并显示最终结果
clearConsoleLine()
if retVal:
logger.info("vuln test final result: PASSED")
@ -211,11 +210,13 @@ def vulnTest():
def smokeTest():
"""
Runs the basic smoke testing of a program
运行程序的基本冒烟测试
验证基本功能是否正常工作
"""
unisonRandom()
unisonRandom() # 初始化随机数生成器
# 验证错误正则表达式的有效性
with open(paths.ERRORS_XML, "r") as f:
content = f.read()
@ -230,6 +231,7 @@ def smokeTest():
retVal = True
count, length = 0, 0
# 统计需要测试的Python文件数量
for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH):
if any(_ in root for _ in ("thirdparty", "extra", "interbase")):
continue
@ -238,6 +240,7 @@ def smokeTest():
if os.path.splitext(filename)[1].lower() == ".py" and filename != "__init__.py":
length += 1
# 对每个Python文件进行测试
for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH):
if any(_ in root for _ in ("thirdparty", "extra", "interbase")):
continue
@ -259,6 +262,7 @@ def smokeTest():
logger.setLevel(logging.CRITICAL)
kb.smokeMode = True
# 运行文档测试
(failure_count, _) = doctest.testmod(module)
kb.smokeMode = False
@ -271,6 +275,7 @@ def smokeTest():
status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
# 验证正则表达式的递归函数
def _(node):
for __ in dir(node):
if not __.startswith('_'):
@ -286,12 +291,14 @@ def smokeTest():
else:
_(candidate)
# 验证所有数据库查询中的正则表达式
for dbms in queries:
try:
_(queries[dbms])
except:
retVal = False
# 显示最终测试结果
clearConsoleLine()
if retVal:
logger.info("smoke test final result: PASSED")

@ -5,35 +5,39 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入需要的模块
from __future__ import print_function
import difflib
import sqlite3
import threading
import time
import traceback
from lib.core.compat import WichmannHill
from lib.core.compat import xrange
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.datatype import AttribDict
from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapBaseException
from lib.core.exception import SqlmapConnectionException
from lib.core.exception import SqlmapSkipTargetException
from lib.core.exception import SqlmapThreadException
from lib.core.exception import SqlmapUserQuitException
from lib.core.exception import SqlmapValueException
from lib.core.settings import MAX_NUMBER_OF_THREADS
from lib.core.settings import PYVERSION
import difflib # 用于比较文本差异
import sqlite3 # SQLite数据库支持
import threading # 多线程支持
import time # 时间相关功能
import traceback # 异常追踪
# 导入自定义模块
from lib.core.compat import WichmannHill # 随机数生成器
from lib.core.compat import xrange # 兼容Python2和3的range函数
from lib.core.data import conf # 配置数据
from lib.core.data import kb # 知识库数据
from lib.core.data import logger # 日志记录器
from lib.core.datatype import AttribDict # 属性字典类型
from lib.core.enums import PAYLOAD # 载荷类型枚举
from lib.core.exception import SqlmapBaseException # 基础异常类
from lib.core.exception import SqlmapConnectionException # 连接异常
from lib.core.exception import SqlmapSkipTargetException # 跳过目标异常
from lib.core.exception import SqlmapThreadException # 线程异常
from lib.core.exception import SqlmapUserQuitException # 用户退出异常
from lib.core.exception import SqlmapValueException # 值错误异常
from lib.core.settings import MAX_NUMBER_OF_THREADS # 最大线程数
from lib.core.settings import PYVERSION # Python版本信息
# 创建共享数据对象
shared = AttribDict()
class _ThreadData(threading.local):
"""
Represents thread independent data
表示线程独立的数据
每个线程都有自己独立的数据副本
"""
def __init__(self):
@ -41,92 +45,117 @@ class _ThreadData(threading.local):
def reset(self):
"""
Resets thread data model
重置线程数据模型
初始化所有线程局部变量
"""
self.disableStdOut = False
self.hashDBCursor = None
self.inTransaction = False
self.lastCode = None
self.lastComparisonPage = None
self.lastComparisonHeaders = None
self.lastComparisonCode = None
self.lastComparisonRatio = None
self.lastErrorPage = tuple()
self.lastHTTPError = None
self.lastRedirectMsg = None
self.lastQueryDuration = 0
self.lastPage = None
self.lastRequestMsg = None
self.lastRequestUID = 0
self.lastRedirectURL = tuple()
self.random = WichmannHill()
self.resumed = False
self.retriesCount = 0
self.seqMatcher = difflib.SequenceMatcher(None)
self.shared = shared
self.technique = None
self.validationRun = 0
self.valueStack = []
self.disableStdOut = False # 是否禁用标准输出
self.hashDBCursor = None # 哈希数据库游标
self.inTransaction = False # 是否在事务中
self.lastCode = None # 最后的HTTP状态码
self.lastComparisonPage = None # 最后比较的页面内容
self.lastComparisonHeaders = None # 最后比较的HTTP头
self.lastComparisonCode = None # 最后比较的状态码
self.lastComparisonRatio = None # 最后比较的相似度
self.lastErrorPage = tuple() # 最后的错误页面
self.lastHTTPError = None # 最后的HTTP错误
self.lastRedirectMsg = None # 最后的重定向消息
self.lastQueryDuration = 0 # 最后查询持续时间
self.lastPage = None # 最后的页面内容
self.lastRequestMsg = None # 最后的请求消息
self.lastRequestUID = 0 # 最后请求的唯一ID
self.lastRedirectURL = tuple() # 最后重定向的URL
self.random = WichmannHill() # 随机数生成器
self.resumed = False # 是否已恢复
self.retriesCount = 0 # 重试次数
self.seqMatcher = difflib.SequenceMatcher(None) # 序列匹配器
self.shared = shared # 共享数据引用
self.technique = None # 当前使用的技术
self.validationRun = 0 # 验证运行次数
self.valueStack = [] # 值栈
# 创建线程数据实例
ThreadData = _ThreadData()
def readInput(message, default=None, checkBatch=True, boolean=False):
# It will be overwritten by original from lib.core.common
# 将被lib.core.common中的原始函数覆盖
pass
def isDigit(value):
# It will be overwritten by original from lib.core.common
# 将被lib.core.common中的原始函数覆盖
pass
def getCurrentThreadData():
"""
Returns current thread's local data
返回当前线程的局部数据
"""
return ThreadData
def getCurrentThreadName():
"""
Returns current's thread name
返回当前线程的名称
"""
return threading.current_thread().getName()
def exceptionHandledFunction(threadFunction, silent=False):
"""
异常处理包装函数
用于包装线程函数并处理可能发生的异常
"""
try:
threadFunction()
except KeyboardInterrupt:
except KeyboardInterrupt: # 处理键盘中断
kb.threadContinue = False
kb.threadException = True
raise
except Exception as ex:
from lib.core.common import getSafeExString
# 如果不是静默模式且线程应继续运行,且不是多次Ctrl+C,且不是用户退出或跳过目标异常
if not silent and kb.get("threadContinue") and not kb.get("multipleCtrlC") and not isinstance(ex, (SqlmapUserQuitException, SqlmapSkipTargetException)):
errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex))
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg))
# 在详细模式下打印完整堆栈跟踪
if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException):
traceback.print_exc()
def setDaemon(thread):
# Reference: http://stackoverflow.com/questions/190010/daemon-threads-explanation
"""
设置线程为守护线程
守护线程会在主程序退出时自动终止
"""
if PYVERSION >= "2.6":
thread.daemon = True
else:
thread.setDaemon(True)
def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True):
"""
运行多个线程的主函数
参数:
numThreads - 要运行的线程数
threadFunction - 每个线程要执行的函数
cleanupFunction - 清理函数(可选)
forwardException - 是否转发异常
threadChoice - 是否允许用户选择线程数
startThreadMsg - 是否显示启动线程消息
"""
threads = []
def _threadFunction():
"""
内部线程函数
包装了原始的threadFunction,并确保正确关闭hashDB
"""
try:
threadFunction()
finally:
if conf.hashDB:
conf.hashDB.close()
# 初始化线程控制变量
kb.multipleCtrlC = False
kb.threadContinue = True
kb.threadException = False
@ -134,6 +163,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.multiThreadMode = False
try:
# 处理单线程情况下的线程数选择
if threadChoice and conf.threads == numThreads == 1 and not (kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)):
while True:
message = "please enter number of threads? [Enter for %d (current)] " % numThreads
@ -157,6 +187,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
warnMsg = "running in a single-thread mode. This could take a while"
logger.warning(warnMsg)
# 处理多线程和单线程的执行
if numThreads > 1:
if startThreadMsg:
infoMsg = "starting %d threads" % numThreads
@ -171,7 +202,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.multiThreadMode = True
# Start the threads
# 启动所有线程
for numThread in xrange(numThreads):
thread = threading.Thread(target=exceptionHandledFunction, name=str(numThread), args=[_threadFunction])
@ -186,7 +217,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
threads.append(thread)
# And wait for them to all finish
# 等待所有线程完成
alive = True
while alive:
alive = False
@ -196,6 +227,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
time.sleep(0.1)
except (KeyboardInterrupt, SqlmapUserQuitException) as ex:
# 处理用户中断
print()
kb.prependFlag = False
kb.threadContinue = False
@ -221,6 +253,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
raise
except (SqlmapConnectionException, SqlmapValueException) as ex:
# 处理连接和值错误异常
print()
kb.threadException = True
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex))
@ -229,6 +262,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc()
except Exception as ex:
# 处理其他未预期的异常
print()
if not kb.multipleCtrlC:
@ -243,11 +277,13 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc()
finally:
# 清理工作
kb.multiThreadMode = False
kb.threadContinue = True
kb.threadException = False
kb.technique = None
# 释放所有锁
for lock in kb.locks.values():
if lock.locked():
try:
@ -255,8 +291,10 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
except:
pass
# 刷新哈希数据库
if conf.get("hashDB"):
conf.hashDB.flush(True)
# 执行清理函数
if cleanupFunction:
cleanupFunction()

@ -5,86 +5,101 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import glob
import os
import re
import shutil
import subprocess
import time
import zipfile
from lib.core.common import dataToStdout
from lib.core.common import extractRegexResult
from lib.core.common import getLatestRevision
from lib.core.common import getSafeExString
from lib.core.common import openFile
from lib.core.common import pollProcess
from lib.core.common import readInput
from lib.core.convert import getText
from lib.core.data import conf
from lib.core.data import logger
from lib.core.data import paths
from lib.core.revision import getRevisionNumber
from lib.core.settings import GIT_REPOSITORY
from lib.core.settings import IS_WIN
from lib.core.settings import VERSION
from lib.core.settings import TYPE
from lib.core.settings import ZIPBALL_PAGE
from thirdparty.six.moves import urllib as _urllib
# 导入所需的Python标准库
import glob # 用于文件路径模式匹配
import os # 提供与操作系统交互的功能
import re # 正则表达式模块
import shutil # 提供高级文件操作功能
import subprocess # 用于创建子进程
import time # 时间相关功能
import zipfile # ZIP文件操作
# 从自定义库中导入所需函数
from lib.core.common import dataToStdout # 输出数据到标准输出
from lib.core.common import extractRegexResult # 提取正则表达式结果
from lib.core.common import getLatestRevision # 获取最新版本号
from lib.core.common import getSafeExString # 安全地获取异常字符串
from lib.core.common import openFile # 打开文件
from lib.core.common import pollProcess # 轮询进程
from lib.core.common import readInput # 读取用户输入
from lib.core.convert import getText # 文本转换
from lib.core.data import conf # 配置数据
from lib.core.data import logger # 日志记录器
from lib.core.data import paths # 路径信息
from lib.core.revision import getRevisionNumber # 获取版本号
from lib.core.settings import GIT_REPOSITORY # Git仓库地址
from lib.core.settings import IS_WIN # 是否为Windows系统
from lib.core.settings import VERSION # 版本信息
from lib.core.settings import TYPE # 安装类型
from lib.core.settings import ZIPBALL_PAGE # ZIP包下载页面
from thirdparty.six.moves import urllib as _urllib # URL处理
def update():
"""
更新sqlmap的主函数
"""
# 如果未启用更新全部选项,直接返回
if not conf.updateAll:
return
success = False
success = False # 更新是否成功的标志
# 如果是通过pip安装的
if TYPE == "pip":
infoMsg = "updating sqlmap to the latest stable version from the "
infoMsg += "PyPI repository"
logger.info(infoMsg)
logger.info(infoMsg) # 记录更新信息
debugMsg = "sqlmap will try to update itself using 'pip' command"
logger.debug(debugMsg)
logger.debug(debugMsg) # 记录调试信息
dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X"))
dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X")) # 显示更新进度
output = ""
try:
# 执行pip更新命令
process = subprocess.Popen("pip install -U sqlmap", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH)
pollProcess(process, True)
output, _ = process.communicate()
success = not process.returncode
pollProcess(process, True) # 轮询进程
output, _ = process.communicate() # 获取输出
success = not process.returncode # 检查返回码
except Exception as ex:
success = False
output = getSafeExString(ex)
finally:
output = getText(output)
# 根据更新结果输出相应信息
if success:
logger.info("%s the latest revision '%s'" % ("already at" if "already up-to-date" in output else "updated to", extractRegexResult(r"\binstalled sqlmap-(?P<result>\d+\.\d+\.\d+)", output) or extractRegexResult(r"\((?P<result>\d+\.\d+\.\d+)\)", output)))
else:
logger.error("update could not be completed ('%s')" % re.sub(r"[^a-z0-9:/\\]+", " ", output).strip())
# 如果不是Git仓库
elif not os.path.exists(os.path.join(paths.SQLMAP_ROOT_PATH, ".git")):
warnMsg = "not a git repository. It is recommended to clone the 'sqlmapproject/sqlmap' repository "
warnMsg += "from GitHub (e.g. 'git clone --depth 1 %s sqlmap')" % GIT_REPOSITORY
logger.warning(warnMsg)
logger.warning(warnMsg) # 提示用户使用git克隆
# 检查是否已是最新版本
if VERSION == getLatestRevision():
logger.info("already at the latest revision '%s'" % (getRevisionNumber() or VERSION))
return
# 询问用户是否尝试下载ZIP包更新
message = "do you want to try to fetch the latest 'zipball' from repository and extract it (experimental) ? [y/N]"
if readInput(message, default='N', boolean=True):
directory = os.path.abspath(paths.SQLMAP_ROOT_PATH)
try:
# 尝试创建/更新主程序文件
open(os.path.join(directory, "sqlmap.py"), "w+b")
except Exception as ex:
errMsg = "unable to update content of directory '%s' ('%s')" % (directory, getSafeExString(ex))
logger.error(errMsg)
else:
# 保存原文件属性
attrs = os.stat(os.path.join(directory, "sqlmap.py")).st_mode
# 清理目录内容
for wildcard in ('*', ".*"):
for _ in glob.glob(os.path.join(directory, wildcard)):
try:
@ -95,11 +110,13 @@ def update():
except:
pass
# 检查目录是否清空
if glob.glob(os.path.join(directory, '*')):
errMsg = "unable to clear the content of directory '%s'" % directory
logger.error(errMsg)
else:
try:
# 下载并解压最新的ZIP包
archive = _urllib.request.urlretrieve(ZIPBALL_PAGE)[0]
with zipfile.ZipFile(archive) as f:
@ -108,6 +125,7 @@ def update():
if info.filename:
f.extract(info, directory)
# 获取并显示新版本信息
filepath = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py")
if os.path.isfile(filepath):
with openFile(filepath, "rb") as f:
@ -121,10 +139,12 @@ def update():
logger.error("update could not be completed")
else:
try:
# 恢复文件属性
os.chmod(os.path.join(directory, "sqlmap.py"), attrs)
except OSError:
logger.warning("could not set the file attributes of '%s'" % os.path.join(directory, "sqlmap.py"))
# 如果是Git仓库
else:
infoMsg = "updating sqlmap to the latest development revision from the "
infoMsg += "GitHub repository"
@ -137,6 +157,7 @@ def update():
output = ""
try:
# 执行git更新命令
process = subprocess.Popen("git checkout . && git pull %s HEAD" % GIT_REPOSITORY, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH)
pollProcess(process, True)
output, _ = process.communicate()
@ -147,6 +168,7 @@ def update():
finally:
output = getText(output)
# 根据git更新结果输出信息
if success:
logger.info("%s the latest revision '%s'" % ("already at" if "Already" in output else "updated to", getRevisionNumber()))
else:
@ -157,6 +179,7 @@ def update():
else:
logger.error("update could not be completed ('%s')" % re.sub(r"\W+", " ", output).strip())
# 如果更新失败,根据操作系统给出建议
if not success:
if IS_WIN:
infoMsg = "for Windows platform it's recommended "

@ -5,18 +5,22 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入处理zip文件的模块
import zipfile
from lib.core.common import getSafeExString
from lib.core.common import isZipFile
from lib.core.exception import SqlmapDataException
from lib.core.exception import SqlmapInstallationException
from thirdparty import six
# 导入一些工具函数和异常类
from lib.core.common import getSafeExString # 用于安全地获取异常信息的字符串表示
from lib.core.common import isZipFile # 用于判断文件是否为zip格式
from lib.core.exception import SqlmapDataException # sqlmap数据相关异常
from lib.core.exception import SqlmapInstallationException # sqlmap安装相关异常
from thirdparty import six # Python 2/3 兼容性库
class Wordlist(six.Iterator):
"""
Iterator for looping over a large dictionaries
用于遍历大型字典文件的迭代器类
这个类可以处理普通文本文件和zip压缩文件中的字典
支持多进程并行处理,可以将工作负载分配给不同进程
>>> from lib.core.option import paths
>>> isinstance(next(Wordlist(paths.SMALL_DICT)), six.binary_type)
True
@ -25,69 +29,100 @@ class Wordlist(six.Iterator):
"""
def __init__(self, filenames, proc_id=None, proc_count=None, custom=None):
"""
初始化函数
:param filenames: 字典文件名(可以是单个字符串或字符串列表)
:param proc_id: 当前进程的ID(用于多进程并行处理)
:param proc_count: 总进程数
:param custom: 自定义的额外词列表(可选)
"""
# 确保filenames始终是列表格式
self.filenames = [filenames] if isinstance(filenames, six.string_types) else filenames
self.fp = None
self.index = 0
self.counter = -1
self.current = None
self.iter = None
self.custom = custom or []
self.proc_id = proc_id
self.proc_count = proc_count
self.adjust()
self.fp = None # 当前打开的文件对象
self.index = 0 # 当前正在处理的文件索引
self.counter = -1 # 已处理的行数计数器(从-1开始)
self.current = None # 当前正在处理的文件名
self.iter = None # 当前文件的迭代器对象
self.custom = custom or [] # 存储自定义词列表,如果没有则为空列表
self.proc_id = proc_id # 当前进程的ID
self.proc_count = proc_count # 总进程数
self.adjust() # 初始化完成后,调整文件指针和迭代器状态
def __iter__(self):
"""
实现迭代器协议的__iter__方法
使得这个类的实例可以在for循环中使用
"""
return self
def adjust(self):
self.closeFP()
"""
调整文件指针和迭代器的状态
在切换到新文件或重置迭代器时使用
"""
self.closeFP() # 首先关闭当前打开的文件(如果有)
if self.index > len(self.filenames):
return # Note: https://stackoverflow.com/a/30217723 (PEP 479)
return # 如果已经处理完所有文件,直接返回
elif self.index == len(self.filenames):
self.iter = iter(self.custom)
self.iter = iter(self.custom) # 如果处理完所有文件,切换到自定义词列表
else:
self.current = self.filenames[self.index]
if isZipFile(self.current):
self.current = self.filenames[self.index] # 获取当前要处理的文件名
if isZipFile(self.current): # 判断是否为zip文件
try:
_ = zipfile.ZipFile(self.current, 'r')
_ = zipfile.ZipFile(self.current, 'r') # 尝试打开zip文件
except zipfile.error as ex:
errMsg = "something appears to be wrong with "
errMsg += "the file '%s' ('%s'). Please make " % (self.current, getSafeExString(ex))
errMsg += "sure that you haven't made any changes to it"
raise SqlmapInstallationException(errMsg)
if len(_.namelist()) == 0:
if len(_.namelist()) == 0: # 检查zip文件是否为空
errMsg = "no file(s) inside '%s'" % self.current
raise SqlmapDataException(errMsg)
self.fp = _.open(_.namelist()[0])
self.fp = _.open(_.namelist()[0]) # 打开zip中的第一个文件
else:
self.fp = open(self.current, "rb")
self.iter = iter(self.fp)
self.fp = open(self.current, "rb") # 以二进制模式打开普通文件
self.iter = iter(self.fp) # 创建文件内容的迭代器
self.index += 1
self.index += 1 # 更新文件索引,为处理下一个文件做准备
def closeFP(self):
"""
关闭当前打开的文件
防止资源泄露
"""
if self.fp:
self.fp.close()
self.fp = None
def __next__(self):
"""
实现迭代器的next方法
返回字典中的下一个词
支持多进程处理时的任务分配
"""
retVal = None
while True:
self.counter += 1
self.counter += 1 # 增加处理行数计数
try:
retVal = next(self.iter).rstrip()
retVal = next(self.iter).rstrip() # 获取下一行并去除末尾空白字符
except zipfile.error as ex:
errMsg = "something appears to be wrong with "
errMsg += "the file '%s' ('%s'). Please make " % (self.current, getSafeExString(ex))
errMsg += "sure that you haven't made any changes to it"
raise SqlmapInstallationException(errMsg)
except StopIteration:
self.adjust()
except StopIteration: # 当前文件处理完毕
self.adjust() # 切换到下一个文件
retVal = next(self.iter).rstrip()
# 在多进程模式下,根据进程ID筛选要处理的行
if not self.proc_count or self.counter % self.proc_count == self.proc_id:
break
return retVal
def rewind(self):
self.index = 0
self.adjust()
"""
重置迭代器到开始位置
允许重新遍历字典
"""
self.index = 0 # 重置文件索引
self.adjust() # 重新调整文件指针和迭代器

Loading…
Cancel
Save