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): def payloadDirect(self, query):
# This method replaces the affected parameter with the SQL
# injection statement to request
query = self.cleanupPayload(query) query = self.cleanupPayload(query)
# If the query starts with "AND ", replace it with "SELECT "
if query.upper().startswith("AND "): if query.upper().startswith("AND "):
query = re.sub(r"(?i)AND ", "SELECT ", query, 1) query = re.sub(r"(?i)AND ", "SELECT ", query, 1)
# If the query starts with " UNION ALL ", remove it
elif query.upper().startswith(" UNION ALL "): elif query.upper().startswith(" UNION ALL "):
query = re.sub(r"(?i) UNION ALL ", "", query, 1) query = re.sub(r"(?i) UNION ALL ", "", query, 1)
# If the query starts with "; ", remove it
elif query.startswith("; "): elif query.startswith("; "):
query = query.replace("; ", "", 1) 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) 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) _, _, _, _, _, _, fieldsToCastStr, _ = self.getFields(query)
for field in fieldsToCastStr.split(','): for field in fieldsToCastStr.split(','):
query = query.replace(field, self.nullAndCastField(field)) query = query.replace(field, self.nullAndCastField(field))
# If tamper functions are defined, apply them to the query
if kb.tamperFunctions: if kb.tamperFunctions:
for function in kb.tamperFunctions: for function in kb.tamperFunctions:
query = function(payload=query) query = function(payload=query)
@ -92,38 +99,53 @@ class Agent(object):
injection statement to request injection statement to request
""" """
# 如果配置了直接注入则调用payloadDirect方法
if conf.direct: if conf.direct:
return self.payloadDirect(newValue) return self.payloadDirect(newValue)
retVal = "" retVal = ""
# 如果配置了强制where则使用配置的where
if kb.forceWhere: if kb.forceWhere:
where = kb.forceWhere where = kb.forceWhere
# 如果没有配置强制where且当前技术可用则使用当前技术的where
elif where is None and isTechniqueAvailable(getTechnique()): elif where is None and isTechniqueAvailable(getTechnique()):
where = getTechniqueData().where where = getTechniqueData().where
# 如果kb中注入的place不为空则使用kb中的place
if kb.injection.place is not None: if kb.injection.place is not None:
place = kb.injection.place place = kb.injection.place
# 如果kb中注入的parameter不为空则使用kb中的parameter
if kb.injection.parameter is not None: if kb.injection.parameter is not None:
parameter = kb.injection.parameter parameter = kb.injection.parameter
# 获取参数字符串和参数字典
paramString = conf.parameters[place] paramString = conf.parameters[place]
paramDict = conf.paramDict[place] paramDict = conf.paramDict[place]
# 获取原始值
origValue = getUnicode(paramDict[parameter]) origValue = getUnicode(paramDict[parameter])
# 如果有新的值则转换为unicode
newValue = getUnicode(newValue) if newValue else newValue newValue = getUnicode(newValue) if newValue else newValue
# 判断参数是否为base64编码
base64Encoding = re.sub(r" \(.+", "", parameter) in conf.base64Parameter base64Encoding = re.sub(r" \(.+", "", parameter) in conf.base64Parameter
# 如果place为URI或者原始值中包含BOUNDED_INJECTION_MARKER则处理URI
if place == PLACE.URI or BOUNDED_INJECTION_MARKER in origValue: if place == PLACE.URI or BOUNDED_INJECTION_MARKER in origValue:
paramString = origValue paramString = origValue
# 如果place为URI则获取URI中的参数
if place == PLACE.URI: if place == PLACE.URI:
origValue = origValue.split(kb.customInjectionMark)[0] origValue = origValue.split(kb.customInjectionMark)[0]
# 否则,获取原始值中的参数
else: else:
origValue = filterNone(re.search(_, origValue.split(BOUNDED_INJECTION_MARKER)[0]) for _ in (r"\w+\Z", r"[^\"'><]+\Z", r"[^ ]+\Z"))[0].group(0) 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:] origValue = origValue[origValue.rfind('/') + 1:]
# 去除参数名中的特殊字符
for char in ('?', '=', ':', ',', '&'): for char in ('?', '=', ':', ',', '&'):
if char in origValue: if char in origValue:
origValue = origValue[origValue.rfind(char) + 1:] origValue = origValue[origValue.rfind(char) + 1:]
# 如果place为CUSTOM_POST则处理POST
elif place == PLACE.CUSTOM_POST: elif place == PLACE.CUSTOM_POST:
paramString = origValue paramString = origValue
origValue = origValue.split(kb.customInjectionMark)[0] 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 See the file 'LICENSE' for copying permission
""" """
# 尝试导入cPickle模块如果失败则导入pickle模块
try: try:
import cPickle as pickle import cPickle as pickle
except: except:
@ -16,36 +17,47 @@ import sys
import tempfile import tempfile
import zlib import zlib
# 从lib.core.compat模块中导入xrange函数
from lib.core.compat import xrange from lib.core.compat import xrange
# 从lib.core.enums模块中导入MKSTEMP_PREFIX枚举
from lib.core.enums import MKSTEMP_PREFIX from lib.core.enums import MKSTEMP_PREFIX
# 从lib.core.exception模块中导入SqlmapSystemException异常
from lib.core.exception import 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_CHUNK_SIZE
from lib.core.settings import BIGARRAY_COMPRESS_LEVEL from lib.core.settings import BIGARRAY_COMPRESS_LEVEL
# 尝试获取object()对象的大小如果失败则默认大小为16字节
try: try:
DEFAULT_SIZE_OF = sys.getsizeof(object()) DEFAULT_SIZE_OF = sys.getsizeof(object())
except TypeError: except TypeError:
DEFAULT_SIZE_OF = 16 DEFAULT_SIZE_OF = 16
# 定义一个函数,用于返回给定实例/对象的总大小(以字节为单位)
def _size_of(instance): def _size_of(instance):
""" """
Returns total size of a given instance / object (in bytes) Returns total size of a given instance / object (in bytes)
""" """
# 获取实例/对象的大小
retval = sys.getsizeof(instance, DEFAULT_SIZE_OF) retval = sys.getsizeof(instance, DEFAULT_SIZE_OF)
# 如果实例/对象是字典类型,则递归计算字典中所有元素的大小
if isinstance(instance, dict): if isinstance(instance, dict):
retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items())) retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items()))
# 如果实例/对象是可迭代类型,则递归计算可迭代对象中所有元素的大小
elif hasattr(instance, "__iter__"): elif hasattr(instance, "__iter__"):
retval += sum(_size_of(_) for _ in instance if _ != instance) retval += sum(_size_of(_) for _ in instance if _ != instance)
return retval return retval
# 定义一个辅助类,用于存储缓存块
class Cache(object): class Cache(object):
""" """
Auxiliary class used for storing cached chunks Auxiliary class used for storing cached chunks
""" """
# 初始化函数,接收三个参数:索引、数据和脏标记
def __init__(self, index, data, dirty): def __init__(self, index, data, dirty):
self.index = index self.index = index
self.data = data self.data = data
@ -94,9 +106,11 @@ class BigArray(list):
return self return self
# 添加元素到BigArray中
def append(self, value): def append(self, value):
self.chunks[-1].append(value) self.chunks[-1].append(value)
# 如果当前chunk的大小超过了设定的chunk大小则将当前chunk写入临时文件并创建一个新的chunk
if self.chunk_length == sys.maxsize: if self.chunk_length == sys.maxsize:
self._size_counter += _size_of(value) self._size_counter += _size_of(value)
if self._size_counter >= BIGARRAY_CHUNK_SIZE: if self._size_counter >= BIGARRAY_CHUNK_SIZE:
@ -108,10 +122,12 @@ class BigArray(list):
self.chunks[-1] = filename self.chunks[-1] = filename
self.chunks.append([]) self.chunks.append([])
# 扩展BigArray
def extend(self, value): def extend(self, value):
for _ in value: for _ in value:
self.append(_) self.append(_)
# 从BigArray中弹出元素
def pop(self): def pop(self):
if len(self.chunks[-1]) < 1: if len(self.chunks[-1]) < 1:
self.chunks.pop() self.chunks.pop()
@ -125,6 +141,7 @@ class BigArray(list):
return self.chunks[-1].pop() return self.chunks[-1].pop()
# 在BigArray中查找元素
def index(self, value): def index(self, value):
for index in xrange(len(self)): for index in xrange(len(self)):
if self[index] == value: if self[index] == value:
@ -132,6 +149,7 @@ class BigArray(list):
return ValueError, "%s is not in list" % value return ValueError, "%s is not in list" % value
# 将chunk写入临时文件
def _dump(self, chunk): def _dump(self, chunk):
try: try:
handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY) handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY)
@ -148,6 +166,7 @@ class BigArray(list):
errMsg += "writeable by the current user" errMsg += "writeable by the current user"
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 检查缓存
def _checkcache(self, index): def _checkcache(self, index):
if (self.cache and self.cache.index != index and self.cache.dirty): if (self.cache and self.cache.index != index and self.cache.dirty):
filename = self._dump(self.cache.data) filename = self._dump(self.cache.data)
@ -162,13 +181,16 @@ class BigArray(list):
errMsg += "from a temporary file ('%s')" % ex errMsg += "from a temporary file ('%s')" % ex
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 将BigArray序列化
def __getstate__(self): def __getstate__(self):
return self.chunks, self.filenames return self.chunks, self.filenames
# 将BigArray反序列化
def __setstate__(self, state): def __setstate__(self, state):
self.__init__() self.__init__()
self.chunks, self.filenames = state self.chunks, self.filenames = state
# 获取BigArray中指定索引的元素
def __getitem__(self, y): def __getitem__(self, y):
while y < 0: while y < 0:
y += len(self) y += len(self)
@ -183,6 +205,7 @@ class BigArray(list):
self._checkcache(index) self._checkcache(index)
return self.cache.data[offset] return self.cache.data[offset]
# 设置BigArray中指定索引的元素
def __setitem__(self, y, value): def __setitem__(self, y, value):
index = y // self.chunk_length index = y // self.chunk_length
offset = y % self.chunk_length offset = y % self.chunk_length
@ -195,9 +218,11 @@ class BigArray(list):
self.cache.data[offset] = value self.cache.data[offset] = value
self.cache.dirty = True self.cache.dirty = True
# 返回BigArray的字符串表示
def __repr__(self): def __repr__(self):
return "%s%s" % ("..." if len(self.chunks) > 1 else "", self.chunks[-1].__repr__()) return "%s%s" % ("..." if len(self.chunks) > 1 else "", self.chunks[-1].__repr__())
# 返回BigArray的迭代器
def __iter__(self): def __iter__(self):
for i in xrange(len(self)): for i in xrange(len(self)):
try: try:
@ -205,5 +230,6 @@ class BigArray(list):
except IndexError: except IndexError:
break break
# 返回BigArray的长度
def __len__(self): 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]) 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} @rtype: C{str}
""" """
# Initialize the htmlParsed variable to None
htmlParsed = 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: if len(kb.htmlFp) == 0 or kb.heuristicTest != HEURISTIC_TEST.POSITIVE:
pass pass
# If the knowledge base htmlFp list has only one element, set htmlParsed to that element
elif len(kb.htmlFp) == 1: elif len(kb.htmlFp) == 1:
htmlParsed = kb.htmlFp[0] 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: elif len(kb.htmlFp) > 1:
htmlParsed = " or ".join(kb.htmlFp) htmlParsed = " or ".join(kb.htmlFp)
# Return the htmlParsed variable
return htmlParsed return htmlParsed
@staticmethod @staticmethod
@ -385,34 +390,42 @@ class Backend(object):
@staticmethod @staticmethod
def setVersion(version): def setVersion(version):
# 如果version是字符串类型则将kb.dbmsVersion设置为version
if isinstance(version, six.string_types): if isinstance(version, six.string_types):
kb.dbmsVersion = [version] kb.dbmsVersion = [version]
# 返回kb.dbmsVersion
return kb.dbmsVersion return kb.dbmsVersion
@staticmethod @staticmethod
def setVersionList(versionsList): def setVersionList(versionsList):
# 如果versionsList是列表类型则将kb.dbmsVersion设置为versionsList
if isinstance(versionsList, list): if isinstance(versionsList, list):
kb.dbmsVersion = versionsList kb.dbmsVersion = versionsList
# 如果versionsList是字符串类型则调用Backend.setVersion方法
elif isinstance(versionsList, six.string_types): elif isinstance(versionsList, six.string_types):
Backend.setVersion(versionsList) Backend.setVersion(versionsList)
# 否则,记录错误信息
else: else:
logger.error("invalid format of versionsList") logger.error("invalid format of versionsList")
@staticmethod @staticmethod
def forceDbms(dbms, sticky=False): def forceDbms(dbms, sticky=False):
# 如果kb.stickyDBMS为False则将kb.forcedDbms设置为aliasToDbmsEnum(dbms)并将kb.stickyDBMS设置为sticky
if not kb.stickyDBMS: if not kb.stickyDBMS:
kb.forcedDbms = aliasToDbmsEnum(dbms) kb.forcedDbms = aliasToDbmsEnum(dbms)
kb.stickyDBMS = sticky kb.stickyDBMS = sticky
@staticmethod @staticmethod
def flushForcedDbms(force=False): def flushForcedDbms(force=False):
# 如果kb.stickyDBMS为False或者force为True则将kb.forcedDbms设置为None并将kb.stickyDBMS设置为False
if not kb.stickyDBMS or force: if not kb.stickyDBMS or force:
kb.forcedDbms = None kb.forcedDbms = None
kb.stickyDBMS = False kb.stickyDBMS = False
@staticmethod @staticmethod
def setOs(os): def setOs(os):
# 如果os为None则返回None
if os is None: if os is None:
return None return None
@ -510,26 +523,36 @@ class Backend(object):
dbms = None dbms = None
# 如果kb为空则不执行任何操作
if not kb: if not kb:
pass pass
# 如果kb中没有testMode并且dbmsHandler存在并且dbmsHandler._dbms存在则将dbms赋值为dbmsHandler._dbms
elif not kb.get("testMode") and conf.get("dbmsHandler") and getattr(conf.dbmsHandler, "_dbms", None): elif not kb.get("testMode") and conf.get("dbmsHandler") and getattr(conf.dbmsHandler, "_dbms", None):
dbms = conf.dbmsHandler._dbms dbms = conf.dbmsHandler._dbms
# 如果Backend.getForcedDbms()不为空则将dbms赋值为Backend.getForcedDbms()
elif Backend.getForcedDbms() is not None: elif Backend.getForcedDbms() is not None:
dbms = Backend.getForcedDbms() dbms = Backend.getForcedDbms()
# 如果Backend.getDbms()不为空则将dbms赋值为Backend.getDbms()
elif Backend.getDbms() is not None: elif Backend.getDbms() is not None:
dbms = Backend.getDbms() dbms = Backend.getDbms()
# 如果kb中有injection并且kb.injection.dbms存在则将dbms赋值为kb.injection.dbms
elif kb.get("injection") and kb.injection.dbms: elif kb.get("injection") and kb.injection.dbms:
dbms = unArrayizeValue(kb.injection.dbms) dbms = unArrayizeValue(kb.injection.dbms)
# 如果Backend.getErrorParsedDBMSes()存在则将dbms赋值为Backend.getErrorParsedDBMSes()
elif Backend.getErrorParsedDBMSes(): elif Backend.getErrorParsedDBMSes():
dbms = unArrayizeValue(Backend.getErrorParsedDBMSes()) dbms = unArrayizeValue(Backend.getErrorParsedDBMSes())
# 如果conf中有dbms则将dbms赋值为conf.get("dbms")
elif conf.get("dbms"): elif conf.get("dbms"):
dbms = conf.get("dbms") dbms = conf.get("dbms")
# 将dbms转换为dbmsEnum类型并返回
return aliasToDbmsEnum(dbms) return aliasToDbmsEnum(dbms)
@staticmethod @staticmethod
def getVersion(): 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 = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
# 如果versions不为空则返回versions的第一个元素否则返回None
if not isNoneValue(versions): if not isNoneValue(versions):
return versions[0] return versions[0]
else: else:
@ -537,7 +560,9 @@ class Backend(object):
@staticmethod @staticmethod
def getVersionList(): 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 = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
# 如果versions不为空则返回versions否则返回None
if not isNoneValue(versions): if not isNoneValue(versions):
return versions return versions
else: else:
@ -618,35 +643,48 @@ def paramToDict(place, parameters=None):
testableParameters = OrderedDict() testableParameters = OrderedDict()
# 如果place在conf.parameters中并且parameters为空则将parameters设置为conf.parameters[place]
if place in conf.parameters and not parameters: if place in conf.parameters and not parameters:
parameters = conf.parameters[place] 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) 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: if place == PLACE.COOKIE:
splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER) splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER)
else: else:
splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER) splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER)
# 遍历分割后的参数
for element in splitParams: 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) element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element)
# 将参数按照=进行分割
parts = element.split("=") parts = element.split("=")
# 如果分割后的参数长度大于等于2
if len(parts) >= 2: if len(parts) >= 2:
# 对参数进行url解码
parameter = urldecode(parts[0].replace(" ", "")) parameter = urldecode(parts[0].replace(" ", ""))
# 如果参数为空,则跳过
if not parameter: if not parameter:
continue continue
# 如果conf.paramDel为\n则去掉参数的最后一个字符
if conf.paramDel and conf.paramDel == '\n': if conf.paramDel and conf.paramDel == '\n':
parts[-1] = parts[-1].rstrip() parts[-1] = parts[-1].rstrip()
# 判断参数是否在conf.testParameter中或者参数是否在conf.testParameter中或者参数是否在PLACE.COOKIE中
condition = not conf.testParameter condition = not conf.testParameter
condition |= conf.testParameter is not None and parameter in 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 condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0
# 如果满足条件则将参数和值添加到testableParameters中
if condition: if condition:
value = "=".join(parts[1:]) value = "=".join(parts[1:])
# 如果参数在conf.base64Parameter中则进行base64解码
if parameter in (conf.base64Parameter or []): if parameter in (conf.base64Parameter or []):
try: try:
kb.base64Originals[parameter] = oldValue = value kb.base64Originals[parameter] = oldValue = value
@ -660,8 +698,10 @@ def paramToDict(place, parameters=None):
testableParameters[parameter] = value testableParameters[parameter] = value
# 如果没有设置conf.multipleTargets并且参数不是conf.csrfToken则进行警告
if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)): if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)):
_ = urldecode(testableParameters[parameter], convall=True) _ = 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): 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 = "it appears that you have provided tainted parameter values "
warnMsg += "('%s') with most likely leftover " % element 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] " 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): if not readInput(message, default='N', boolean=True):
raise SqlmapSilentQuitException raise SqlmapSilentQuitException
# 如果参数值为空,则进行警告
elif not _: elif not _:
warnMsg = "provided value for parameter '%s' is empty. " % parameter warnMsg = "provided value for parameter '%s' is empty. " % parameter
warnMsg += "Please, always use only valid parameter values " warnMsg += "Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly" warnMsg += "so sqlmap could be able to run properly"
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果place是PLACE.POST或PLACE.GET则进行警告
if place in (PLACE.POST, PLACE.GET): if place in (PLACE.POST, PLACE.GET):
for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"): for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"):
match = re.search(regex, testableParameters[parameter]) match = re.search(regex, testableParameters[parameter])
@ -687,15 +730,19 @@ def paramToDict(place, parameters=None):
try: try:
candidates = OrderedDict() candidates = OrderedDict()
# 遍历参数值
def walk(head, current=None): def walk(head, current=None):
if current is None: if current is None:
current = head current = head
# 如果current是列表则遍历列表
if isListLike(current): if isListLike(current):
for _ in current: for _ in current:
walk(head, _) walk(head, _)
# 如果current是字典则遍历字典
elif isinstance(current, dict): elif isinstance(current, dict):
for key in current.keys(): for key in current.keys():
value = current[key] 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, []): if isinstance(value, (bool, int, float, six.string_types)) or value in (None, []):
original = current[key] original = current[key]
if isinstance(value, bool): if isinstance(value, bool):
@ -708,6 +755,7 @@ def paramToDict(place, parameters=None):
current[key] = "%s%s" % (value, BOUNDED_INJECTION_MARKER) 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) 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 current[key] = original
# 如果value是列表、元组、集合、字典则进行递归
elif isinstance(value, (list, tuple, set, dict)): elif isinstance(value, (list, tuple, set, dict)):
if value: if value:
walk(head, value) walk(head, value)
@ -736,32 +784,42 @@ def paramToDict(place, parameters=None):
except Exception: except Exception:
pass pass
# 使用正则表达式替换testableParameters[parameter]中的匹配项
_ = re.sub(regex, r"\g<1>%s\g<%d>" % (kb.customInjectionMark, len(match.groups())), 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 = "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(_) message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_)
# 读取用户输入如果用户选择注入则替换testableParameters[parameter]中的匹配项
if readInput(message, default='N', boolean=True): 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) 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 break
# 如果配置了测试参数
if conf.testParameter: if conf.testParameter:
# 如果没有可测试的参数
if not testableParameters: if not testableParameters:
paramStr = ", ".join(test for test in conf.testParameter) paramStr = ", ".join(test for test in conf.testParameter)
# 如果测试参数数量大于1
if len(conf.testParameter) > 1: if len(conf.testParameter) > 1:
warnMsg = "provided parameters '%s' " % paramStr warnMsg = "provided parameters '%s' " % paramStr
warnMsg += "are not inside the %s" % place warnMsg += "are not inside the %s" % place
logger.warning(warnMsg) logger.warning(warnMsg)
else: else:
# 如果测试参数数量为1
parameter = conf.testParameter[0] parameter = conf.testParameter[0]
# 如果测试参数不在USER_AGENT_ALIASES、REFERER_ALIASES、HOST_ALIASES中
if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True): if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True):
debugMsg = "provided parameter '%s' " % paramStr debugMsg = "provided parameter '%s' " % paramStr
debugMsg += "is not inside the %s" % place debugMsg += "is not inside the %s" % place
logger.debug(debugMsg) logger.debug(debugMsg)
# 如果测试参数数量不等于可测试参数数量
elif len(conf.testParameter) != len(testableParameters): elif len(conf.testParameter) != len(testableParameters):
for parameter in conf.testParameter: for parameter in conf.testParameter:
# 如果测试参数不在可测试参数中
if parameter not in testableParameters: if parameter not in testableParameters:
debugMsg = "provided parameter '%s' " % parameter debugMsg = "provided parameter '%s' " % parameter
debugMsg += "is not inside the %s" % place debugMsg += "is not inside the %s" % place
@ -817,13 +875,16 @@ def getManualDirectories():
directories = normalizePath(directories) directories = normalizePath(directories)
# 如果配置文件中有webRoot则使用webRoot作为web服务器文档根目录
if conf.webRoot: if conf.webRoot:
directories = [conf.webRoot] directories = [conf.webRoot]
infoMsg = "using '%s' as web server document root" % conf.webRoot infoMsg = "using '%s' as web server document root" % conf.webRoot
logger.info(infoMsg) logger.info(infoMsg)
# 如果directories有值则使用directories作为web服务器文档根目录
elif directories: elif directories:
infoMsg = "retrieved the web server document root: '%s'" % directories infoMsg = "retrieved the web server document root: '%s'" % directories
logger.info(infoMsg) logger.info(infoMsg)
# 如果以上两种情况都不满足则提示无法自动获取web服务器文档根目录
else: else:
warnMsg = "unable to automatically retrieve the web server " warnMsg = "unable to automatically retrieve the web server "
warnMsg += "document root" warnMsg += "document root"
@ -831,6 +892,7 @@ def getManualDirectories():
directories = [] directories = []
# 提示用户选择可写目录
message = "what do you want to use for writable directory?\n" 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 += "[1] common location(s) ('%s') (default)\n" % ", ".join(root for root in defaultDocRoot)
message += "[2] custom location(s)\n" message += "[2] custom location(s)\n"
@ -1639,48 +1701,64 @@ def parseTargetDirect():
break break
# 如果kb.smokeMode为True则直接返回
if kb.smokeMode: if kb.smokeMode:
return return
# 如果details为空则抛出SqlmapSyntaxException异常
if not details: if not details:
errMsg = "invalid target details, valid syntax is for instance " errMsg = "invalid target details, valid syntax is for instance "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' " errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH'" errMsg += "or 'access://DATABASE_FILEPATH'"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 遍历DBMS_DICT字典
for dbmsName, data in DBMS_DICT.items(): 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]: if dbmsName == conf.dbms or conf.dbms.lower() in data[0]:
try: try:
# 将conf.dbms设置为dbmsName
conf.dbms = dbmsName conf.dbms = dbmsName
# 如果dbmsName在(DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD)中,则执行以下操作
if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD): if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD):
# 如果remote为True则抛出警告信息
if remote: if remote:
warnMsg = "direct connection over the network for " warnMsg = "direct connection over the network for "
warnMsg += "%s DBMS is not supported" % dbmsName warnMsg += "%s DBMS is not supported" % dbmsName
logger.warning(warnMsg) logger.warning(warnMsg)
# 将conf.hostname设置为localhostconf.port设置为0
conf.hostname = "localhost" conf.hostname = "localhost"
conf.port = 0 conf.port = 0
# 如果remote为False则抛出SqlmapSyntaxException异常
elif not remote: elif not remote:
errMsg = "missing remote connection details (e.g. " errMsg = "missing remote connection details (e.g. "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' " errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH')" errMsg += "or 'access://DATABASE_FILEPATH')"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 如果dbmsName在(DBMS.MSSQL, DBMS.SYBASE)中,则执行以下操作
if dbmsName in (DBMS.MSSQL, DBMS.SYBASE): if dbmsName in (DBMS.MSSQL, DBMS.SYBASE):
# 导入_mssql模块
__import__("_mssql") __import__("_mssql")
# 导入pymssql模块
pymssql = __import__("pymssql") pymssql = __import__("pymssql")
# 如果pymssql没有__version__属性或者pymssql.__version__小于1.0.2则抛出SqlmapMissingDependence异常
if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2": if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2":
errMsg = "'%s' third-party library must be " % data[1] errMsg = "'%s' third-party library must be " % data[1]
errMsg += "version >= 1.0.2 to work properly. " errMsg += "version >= 1.0.2 to work properly. "
errMsg += "Download from '%s'" % data[2] errMsg += "Download from '%s'" % data[2]
raise SqlmapMissingDependence(errMsg) raise SqlmapMissingDependence(errMsg)
# 如果dbmsName等于DBMS.MYSQL则导入pymysql模块
elif dbmsName == DBMS.MYSQL: elif dbmsName == DBMS.MYSQL:
__import__("pymysql") __import__("pymysql")
# 如果dbmsName等于DBMS.PGSQL则导入psycopg2模块
elif dbmsName == DBMS.PGSQL: elif dbmsName == DBMS.PGSQL:
__import__("psycopg2") __import__("psycopg2")
# 如果dbmsName等于DBMS.ORACLE则导入cx_Oracle模块
elif dbmsName == DBMS.ORACLE: elif dbmsName == DBMS.ORACLE:
__import__("cx_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) filename = os.path.join(paths.SQLMAP_PROCS_PATH, DBMS_DIRECTORY_DICT[dbms], sfile if sfile.endswith('.sql') else "%s.sql" % sfile)
checkFile(filename) checkFile(filename)
# 读取缓存文件内容
retVal = readCachedFileContent(filename) retVal = readCachedFileContent(filename)
# 删除注释
retVal = re.sub(r"#.+", "", retVal) retVal = re.sub(r"#.+", "", retVal)
# 将分号替换为"; "
retVal = re.sub(r";\s+", "; ", retVal).strip("\r\n") retVal = re.sub(r";\s+", "; ", retVal).strip("\r\n")
# 替换变量
for _ in variables: for _ in variables:
retVal = re.sub(r"%%%s%%" % _, variables[_].replace('\\', r'\\'), retVal) retVal = re.sub(r"%%%s%%" % _, variables[_].replace('\\', r'\\'), retVal)
# 替换随机字符串
for _ in re.findall(r"%RANDSTR\d+%", retVal, re.I): for _ in re.findall(r"%RANDSTR\d+%", retVal, re.I):
retVal = retVal.replace(_, randomStr()) retVal = retVal.replace(_, randomStr())
# 替换随机整数
for _ in re.findall(r"%RANDINT\d+%", retVal, re.I): for _ in re.findall(r"%RANDINT\d+%", retVal, re.I):
retVal = retVal.replace(_, randomInt()) retVal = retVal.replace(_, randomInt())
# 查找未解析的变量
variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I) variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I)
# 如果有未解析的变量,则提示用户输入替换值
if variables: if variables:
errMsg = "unresolved variable%s '%s' in SQL file '%s'" % ("s" if len(variables) > 1 else "", ", ".join(variables), sfile) errMsg = "unresolved variable%s '%s' in SQL file '%s'" % ("s" if len(variables) > 1 else "", ", ".join(variables), sfile)
logger.error(errMsg) logger.error(errMsg)
@ -2474,6 +2560,7 @@ def readCachedFileContent(filename, mode="rb"):
True True
""" """
# 如果文件不在缓存中,则读取文件内容并缓存
if filename not in kb.cache.content: if filename not in kb.cache.content:
with kb.locks.cache: with kb.locks.cache:
if filename not in kb.cache.content: if filename not in kb.cache.content:
@ -3935,6 +4022,7 @@ def fetchRandomAgent():
True True
""" """
# 如果kb.userAgents为空则从文件中加载HTTP User-Agent header值
if not kb.userAgents: if not kb.userAgents:
debugMsg = "loading random HTTP User-Agent header(s) from " debugMsg = "loading random HTTP User-Agent header(s) from "
debugMsg += "file '%s'" % paths.USER_AGENTS debugMsg += "file '%s'" % paths.USER_AGENTS
@ -3947,6 +4035,7 @@ def fetchRandomAgent():
errMsg += "file '%s'" % paths.USER_AGENTS errMsg += "file '%s'" % paths.USER_AGENTS
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 从kb.userAgents中随机选择一个User-Agent header值并返回
return random.sample(kb.userAgents, 1)[0] return random.sample(kb.userAgents, 1)[0]
def createGithubIssue(errMsg, excMsg): def createGithubIssue(errMsg, excMsg):
@ -3954,6 +4043,7 @@ def createGithubIssue(errMsg, excMsg):
Automatically create a Github issue with unhandled exception information Automatically create a Github issue with unhandled exception information
""" """
# 从文件中获取已创建的Github issue列表
try: try:
issues = getFileItems(paths.GITHUB_HISTORY, unique=True) issues = getFileItems(paths.GITHUB_HISTORY, unique=True)
except: except:
@ -3961,6 +4051,7 @@ def createGithubIssue(errMsg, excMsg):
finally: finally:
issues = set(issues) issues = set(issues)
# 对异常信息进行处理,去除不必要的字符
_ = re.sub(r"'[^']+'", "''", excMsg) _ = re.sub(r"'[^']+'", "''", excMsg)
_ = re.sub(r"\s+line \d+", "", _) _ = re.sub(r"\s+line \d+", "", _)
_ = re.sub(r'File ".+?/(\w+\.py)', r"\g<1>", _) _ = 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"(Unicode[^:]*Error:).+", r"\g<1>", _)
_ = re.sub(r"= _", "= ", _) _ = re.sub(r"= _", "= ", _)
# 计算异常信息的MD5值并取前8位作为key
key = hashlib.md5(getBytes(_)).hexdigest()[:8] key = hashlib.md5(getBytes(_)).hexdigest()[:8]
# 如果key已经在已创建的Github issue列表中则返回
if key in issues: if key in issues:
return return
# 提示用户是否要自动创建一个新的Github issue
msg = "\ndo you want to automatically create a new (anonymized) issue " msg = "\ndo you want to automatically create a new (anonymized) issue "
msg += "with the unhandled exception information at " msg += "with the unhandled exception information at "
msg += "the official Github repository? [y/N] " msg += "the official Github repository? [y/N] "
@ -3981,10 +4075,12 @@ def createGithubIssue(errMsg, excMsg):
except: except:
choice = None choice = None
# 如果用户选择创建新的Github issue则进行后续操作
if choice: if choice:
_excMsg = None _excMsg = None
errMsg = errMsg[errMsg.find("\n"):] 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()}) 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: try:
@ -4071,9 +4167,11 @@ def listToStrValue(value):
'1, 2, 3' '1, 2, 3'
""" """
# 如果value是set、tuple或types.GeneratorType类型将其转换为list
if isinstance(value, (set, tuple, types.GeneratorType)): if isinstance(value, (set, tuple, types.GeneratorType)):
value = list(value) value = list(value)
# 如果value是list类型将其转换为字符串并去掉首尾的方括号
if isinstance(value, list): if isinstance(value, list):
retVal = value.__str__().lstrip('[').rstrip(']') retVal = value.__str__().lstrip('[').rstrip(']')
else: else:
@ -4146,62 +4244,97 @@ def removeReflectiveValues(content, payload, suppressWarning=False):
value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX) value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX)
return value return value
# 将payload中的PAYLOAD_DELIMITER替换为空字符串并使用urldecode解码然后使用getUnicode转换为Unicode编码
payload = getUnicode(urldecode(payload.replace(PAYLOAD_DELIMITER, ""), convall=True)) 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 = _(filterStringValue(payload, r"[A-Za-z0-9]", encodeStringEscape(REFLECTED_REPLACEMENT_REGEX)))
# 如果regex不等于payload
if 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 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) parts = regex.split(REFLECTED_REPLACEMENT_REGEX)
# Note: naive approach # Note: naive approach
# 将content中的payload替换为REFLECTED_VALUE_MARKER
retVal = content.replace(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) 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 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:]))) 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)) parts = filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))
# 如果regex以REFLECTED_REPLACEMENT_REGEX开头
if regex.startswith(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):]) regex = r"%s%s" % (REFLECTED_BORDER_REGEX, regex[len(REFLECTED_REPLACEMENT_REGEX):])
else: else:
# 使用\b和regex进行连接
regex = r"\b%s" % regex regex = r"\b%s" % regex
# 如果regex以REFLECTED_REPLACEMENT_REGEX结尾
if regex.endswith(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) regex = r"%s%s" % (regex[:-len(REFLECTED_REPLACEMENT_REGEX)], REFLECTED_BORDER_REGEX)
else: else:
# 使用regex和\b进行连接
regex = r"%s\b" % regex regex = r"%s\b" % regex
# 创建一个列表用于存储retVal
_retVal = [retVal] _retVal = [retVal]
# 定义一个函数用于替换retVal中的regex
def _thread(regex): def _thread(regex):
try: try:
# 使用re.sub函数替换retVal中的regex并使用REFLECTED_VALUE_MARKER进行替换
_retVal[0] = re.sub(r"(?i)%s" % regex, REFLECTED_VALUE_MARKER, _retVal[0]) _retVal[0] = re.sub(r"(?i)%s" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
# 如果分割后的字符串长度大于2
if len(parts) > 2: if len(parts) > 2:
# 使用REFLECTED_REPLACEMENT_REGEX进行分割并使用join函数进行连接
regex = REFLECTED_REPLACEMENT_REGEX.join(parts[1:]) 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]) _retVal[0] = re.sub(r"(?i)\b%s\b" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except: except:
pass pass
# 创建一个线程用于执行_thread函数
thread = threading.Thread(target=_thread, args=(regex,)) thread = threading.Thread(target=_thread, args=(regex,))
# 设置线程为守护线程
thread.daemon = True thread.daemon = True
# 启动线程
thread.start() thread.start()
# 等待线程执行完毕超时时间为REFLECTED_REPLACEMENT_TIMEOUT
thread.join(REFLECTED_REPLACEMENT_TIMEOUT) thread.join(REFLECTED_REPLACEMENT_TIMEOUT)
# 如果线程还在运行
if thread.is_alive(): if thread.is_alive():
# 将kb.reflectiveMechanism设置为False
kb.reflectiveMechanism = False kb.reflectiveMechanism = False
# 将retVal设置为content
retVal = content retVal = content
# 如果不抑制警告
if not suppressWarning: if not suppressWarning:
# 打印debugMsg
debugMsg = "turning off reflection removal mechanism (because of timeouts)" debugMsg = "turning off reflection removal mechanism (because of timeouts)"
logger.debug(debugMsg) logger.debug(debugMsg)
else: else:
# 将retVal设置为_retVal[0]
retVal = _retVal[0] retVal = _retVal[0]
# 如果retVal不等于content
if retVal != content: if retVal != content:
# 将kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT]加1
kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1 kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1
# 如果不抑制警告
if not suppressWarning: if not suppressWarning:
warnMsg = "reflective value(s) found and filtering out" warnMsg = "reflective value(s) found and filtering out"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
@ -4367,6 +4500,7 @@ def isNullValue(value):
False False
""" """
# 判断value是否具有upper()方法并且value的大写等于NULL
return hasattr(value, "upper") and value.upper() == NULL return hasattr(value, "upper") and value.upper() == NULL
def expandMnemonics(mnemonics, parser, args): def expandMnemonics(mnemonics, parser, args):
@ -4374,19 +4508,23 @@ def expandMnemonics(mnemonics, parser, args):
Expands mnemonic options Expands mnemonic options
""" """
# 定义一个MnemonicNode类用于存储选项
class MnemonicNode(object): class MnemonicNode(object):
def __init__(self): def __init__(self):
self.next = {} self.next = {}
self.current = [] self.current = []
# 初始化头节点和指针
head = MnemonicNode() head = MnemonicNode()
pointer = None pointer = None
# 遍历parser中的option_groups
for group in parser.option_groups: for group in parser.option_groups:
for option in group.option_list: for option in group.option_list:
for opt in option._long_opts + option._short_opts: for opt in option._long_opts + option._short_opts:
pointer = head pointer = head
# 遍历opt中的每个字符
for char in opt: for char in opt:
if char == "-": if char == "-":
continue continue
@ -4396,12 +4534,14 @@ def expandMnemonics(mnemonics, parser, args):
pointer = pointer.next[char] pointer = pointer.next[char]
pointer.current.append(option) pointer.current.append(option)
# 遍历mnemonics中的每个选项
for mnemonic in (mnemonics or "").split(','): for mnemonic in (mnemonics or "").split(','):
found = None found = None
name = mnemonic.split('=')[0].replace('-', "").strip() name = mnemonic.split('=')[0].replace('-', "").strip()
value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None
pointer = head pointer = head
# 遍历name中的每个字符
for char in name: for char in name:
if char in pointer.next: if char in pointer.next:
pointer = pointer.next[char] pointer = pointer.next[char]
@ -4409,10 +4549,12 @@ def expandMnemonics(mnemonics, parser, args):
pointer = None pointer = None
break break
# 如果pointer为None或head则抛出异常
if pointer in (None, head): if pointer in (None, head):
errMsg = "mnemonic '%s' can't be resolved to any parameter name" % name errMsg = "mnemonic '%s' can't be resolved to any parameter name" % name
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 如果pointer.current的长度大于1则说明有多个选项需要进行解析
elif len(pointer.current) > 1: elif len(pointer.current) > 1:
options = {} options = {}
@ -4422,26 +4564,32 @@ def expandMnemonics(mnemonics, parser, args):
if opt.startswith(name): if opt.startswith(name):
options[opt] = option options[opt] = option
# 如果options为空则说明没有找到对应的选项进行警告
if not options: if not options:
warnMsg = "mnemonic '%s' can't be resolved" % name warnMsg = "mnemonic '%s' can't be resolved" % name
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果name在options中则说明找到了对应的选项进行调试
elif name in options: elif name in options:
found = name found = name
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found) debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
logger.debug(debugMsg) logger.debug(debugMsg)
# 否则,说明有多个选项,进行警告,并选择最短的选项
else: else:
found = sorted(options.keys(), key=len)[0] 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 = "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 warnMsg += "Resolved to shortest of those ('%s')" % found
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果找到了对应的选项,则进行赋值
if found: if found:
found = options[found] found = options[found]
# 如果pointer.current的长度等于1则说明只有一个选项进行调试
else: else:
found = pointer.current[0] found = pointer.current[0]
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found) debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
logger.debug(debugMsg) logger.debug(debugMsg)
# 如果找到了对应的选项,则进行赋值
if found: if found:
try: try:
value = found.convert_value(found, value) value = found.convert_value(found, value)
@ -4468,13 +4616,18 @@ def safeCSValue(value):
'foobar' 'foobar'
""" """
# 初始化返回值
retVal = value retVal = value
# 如果value不为空并且是字符串类型
if retVal and isinstance(retVal, six.string_types): if retVal and isinstance(retVal, six.string_types):
# 如果value的第一个字符和最后一个字符不是双引号并且value中包含csv分隔符、双引号或换行符
if not (retVal[0] == retVal[-1] == '"'): if not (retVal[0] == retVal[-1] == '"'):
if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')): if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')):
# 将value中的双引号替换为两个双引号并在value的两端加上双引号
retVal = '"%s"' % retVal.replace('"', '""') retVal = '"%s"' % retVal.replace('"', '""')
# 返回处理后的value
return retVal return retVal
def filterPairValues(values): def filterPairValues(values):
@ -4485,11 +4638,15 @@ def filterPairValues(values):
[[1, 2], [4, 5]] [[1, 2], [4, 5]]
""" """
# 初始化返回值
retVal = [] retVal = []
# 如果values不为空并且是可迭代的
if not isNoneValue(values) and hasattr(values, '__iter__'): 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] retVal = [value for value in values if isinstance(value, (tuple, list, set)) and len(value) == 2]
# 返回处理后的values
return retVal return retVal
def randomizeParameterValue(value): def randomizeParameterValue(value):
@ -5517,6 +5674,7 @@ def unsafeVariableNaming(value):
True True
""" """
# 如果value以EVALCODE_ENCODED_PREFIX开头则解码value
if value.startswith(EVALCODE_ENCODED_PREFIX): if value.startswith(EVALCODE_ENCODED_PREFIX):
value = decodeHex(value[len(EVALCODE_ENCODED_PREFIX):], binary=False) value = decodeHex(value[len(EVALCODE_ENCODED_PREFIX):], binary=False)
@ -5532,6 +5690,7 @@ def firstNotNone(*args):
retVal = None retVal = None
# 遍历args找到第一个不为None的值
for _ in args: for _ in args:
if _ is not None: if _ is not None:
retVal = _ retVal = _
@ -5549,6 +5708,7 @@ def removePostHintPrefix(value):
'id' 'id'
""" """
# 使用正则表达式去除value中的POST提示前缀
return re.sub(r"\A(%s) " % '|'.join(re.escape(__) for __ in getPublicTypeMembers(POST_HINT, onlyValues=True)), "", value) return re.sub(r"\A(%s) " % '|'.join(re.escape(__) for __ in getPublicTypeMembers(POST_HINT, onlyValues=True)), "", value)
def chunkSplitPostData(data): def chunkSplitPostData(data):

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

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

@ -5,10 +5,13 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的枚举类型
from lib.core.enums import CONTENT_TYPE from lib.core.enums import CONTENT_TYPE
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import OS from lib.core.enums import OS
from lib.core.enums import POST_HINT from lib.core.enums import POST_HINT
# 导入各种数据库别名和设置
from lib.core.settings import ACCESS_ALIASES from lib.core.settings import ACCESS_ALIASES
from lib.core.settings import ALTIBASE_ALIASES from lib.core.settings import ALTIBASE_ALIASES
from lib.core.settings import BLANK 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 VIRTUOSO_ALIASES
from lib.core.settings import CLICKHOUSE_ALIASES from lib.core.settings import CLICKHOUSE_ALIASES
# Firebird数据库的数据类型映射字典
FIREBIRD_TYPES = { FIREBIRD_TYPES = {
261: "BLOB", 261: "BLOB", # 二进制大对象
14: "CHAR", 14: "CHAR", # 定长字符串
40: "CSTRING", 40: "CSTRING", # C风格字符串
11: "D_FLOAT", 11: "D_FLOAT", # 双精度浮点数
27: "DOUBLE", 27: "DOUBLE", # 双精度数
10: "FLOAT", 10: "FLOAT", # 浮点数
16: "INT64", 16: "INT64", # 64位整数
8: "INTEGER", 8: "INTEGER", # 整数
9: "QUAD", 9: "QUAD", # 四字节整数
7: "SMALLINT", 7: "SMALLINT", # 小整数
12: "DATE", 12: "DATE", # 日期
13: "TIME", 13: "TIME", # 时间
35: "TIMESTAMP", 35: "TIMESTAMP", # 时间戳
37: "VARCHAR", 37: "VARCHAR", # 变长字符串
} }
# Informix数据库的数据类型映射字典
INFORMIX_TYPES = { INFORMIX_TYPES = {
0: "CHAR", 0: "CHAR", # 定长字符串
1: "SMALLINT", 1: "SMALLINT", # 小整数
2: "INTEGER", 2: "INTEGER", # 整数
3: "FLOAT", 3: "FLOAT", # 浮点数
4: "SMALLFLOAT", 4: "SMALLFLOAT", # 小浮点数
5: "DECIMAL", 5: "DECIMAL", # 十进制数
6: "SERIAL", 6: "SERIAL", # 序列号
7: "DATE", 7: "DATE", # 日期
8: "MONEY", 8: "MONEY", # 货币类型
9: "NULL", 9: "NULL", # 空值
10: "DATETIME", 10: "DATETIME", # 日期时间
11: "BYTE", 11: "BYTE", # 字节
12: "TEXT", 12: "TEXT", # 文本
13: "VARCHAR", 13: "VARCHAR", # 变长字符串
14: "INTERVAL", 14: "INTERVAL", # 时间间隔
15: "NCHAR", 15: "NCHAR", # Unicode字符
16: "NVARCHAR", 16: "NVARCHAR", # Unicode变长字符
17: "INT8", 17: "INT8", # 8字节整数
18: "SERIAL8", 18: "SERIAL8", # 8字节序列号
19: "SET", 19: "SET", # 集合
20: "MULTISET", 20: "MULTISET", # 多重集
21: "LIST", 21: "LIST", # 列表
22: "ROW (unnamed)", 22: "ROW (unnamed)", # 未命名行
23: "COLLECTION", 23: "COLLECTION", # 集合
40: "Variable-length opaque type", 40: "Variable-length opaque type", # 变长不透明类型
41: "Fixed-length opaque type", 41: "Fixed-length opaque type", # 定长不透明类型
43: "LVARCHAR", 43: "LVARCHAR", # 长变长字符串
45: "BOOLEAN", 45: "BOOLEAN", # 布尔值
52: "BIGINT", 52: "BIGINT", # 大整数
53: "BIGSERIAL", 53: "BIGSERIAL", # 大序列号
2061: "IDSSECURITYLABEL", 2061: "IDSSECURITYLABEL", # 安全标签
4118: "ROW (named)", 4118: "ROW (named)", # 命名行
} }
# Sybase数据库的数据类型映射字典
SYBASE_TYPES = { SYBASE_TYPES = {
14: "floatn", 14: "floatn", # 可空浮点数
8: "float", 8: "float", # 浮点数
15: "datetimn", 15: "datetimn", # 可空日期时间
12: "datetime", 12: "datetime", # 日期时间
23: "real", 23: "real", # 实数
28: "numericn", 28: "numericn", # 可空数值
10: "numeric", 10: "numeric", # 数值
27: "decimaln", 27: "decimaln", # 可空十进制数
26: "decimal", 26: "decimal", # 十进制数
17: "moneyn", 17: "moneyn", # 可空货币
11: "money", 11: "money", # 货币
21: "smallmoney", 21: "smallmoney", # 小额货币
22: "smalldatetime", 22: "smalldatetime", # 小日期时间
13: "intn", 13: "intn", # 可空整数
7: "int", 7: "int", # 整数
6: "smallint", 6: "smallint", # 小整数
5: "tinyint", 5: "tinyint", # 微整数
16: "bit", 16: "bit", # 位
2: "varchar", 2: "varchar", # 变长字符串
18: "sysname", 18: "sysname", # 系统名称
25: "nvarchar", 25: "nvarchar", # Unicode变长字符
1: "char", 1: "char", # 定长字符串
24: "nchar", 24: "nchar", # Unicode字符
4: "varbinary", 4: "varbinary", # 变长二进制
80: "timestamp", 80: "timestamp", # 时间戳
3: "binary", 3: "binary", # 二进制
19: "text", 19: "text", # 文本
20: "image", 20: "image", # 图像
} }
# Altibase数据库的数据类型映射字典
ALTIBASE_TYPES = { ALTIBASE_TYPES = {
1: "CHAR", 1: "CHAR", # 定长字符串
12: "VARCHAR", 12: "VARCHAR", # 变长字符串
-8: "NCHAR", -8: "NCHAR", # Unicode字符
-9: "NVARCHAR", -9: "NVARCHAR", # Unicode变长字符
2: "NUMERIC", 2: "NUMERIC", # 数值
6: "FLOAT", 6: "FLOAT", # 浮点数
8: "DOUBLE", 8: "DOUBLE", # 双精度数
7: "REAL", 7: "REAL", # 实数
-5: "BIGINT", -5: "BIGINT", # 大整数
4: "INTEGER", 4: "INTEGER", # 整数
5: "SMALLINT", 5: "SMALLINT", # 小整数
9: "DATE", 9: "DATE", # 日期
30: "BLOB", 30: "BLOB", # 二进制大对象
40: "CLOB", 40: "CLOB", # 字符大对象
20001: "BYTE", 20001: "BYTE", # 字节
20002: "NIBBLE", 20002: "NIBBLE", # 半字节
-7: "BIT", -7: "BIT", # 位
-100: "VARBIT", -100: "VARBIT", # 变长位串
10003: "GEOMETRY", 10003: "GEOMETRY", # 几何数据
} }
# MySQL数据库的权限映射字典
MYSQL_PRIVS = { MYSQL_PRIVS = {
1: "select_priv", 1: "select_priv", # 查询权限
2: "insert_priv", 2: "insert_priv", # 插入权限
3: "update_priv", 3: "update_priv", # 更新权限
4: "delete_priv", 4: "delete_priv", # 删除权限
5: "create_priv", 5: "create_priv", # 创建权限
6: "drop_priv", 6: "drop_priv", # 删除权限
7: "reload_priv", 7: "reload_priv", # 重载权限
8: "shutdown_priv", 8: "shutdown_priv", # 关闭权限
9: "process_priv", 9: "process_priv", # 进程权限
10: "file_priv", 10: "file_priv", # 文件权限
11: "grant_priv", 11: "grant_priv", # 授权权限
12: "references_priv", 12: "references_priv", # 引用权限
13: "index_priv", 13: "index_priv", # 索引权限
14: "alter_priv", 14: "alter_priv", # 修改权限
15: "show_db_priv", 15: "show_db_priv", # 显示数据库权限
16: "super_priv", 16: "super_priv", # 超级权限
17: "create_tmp_table_priv", 17: "create_tmp_table_priv", # 创建临时表权限
18: "lock_tables_priv", 18: "lock_tables_priv", # 锁表权限
19: "execute_priv", 19: "execute_priv", # 执行权限
20: "repl_slave_priv", 20: "repl_slave_priv", # 复制从权限
21: "repl_client_priv", 21: "repl_client_priv", # 复制客户端权限
22: "create_view_priv", 22: "create_view_priv", # 创建视图权限
23: "show_view_priv", 23: "show_view_priv", # 显示视图权限
24: "create_routine_priv", 24: "create_routine_priv", # 创建例程权限
25: "alter_routine_priv", 25: "alter_routine_priv", # 修改例程权限
26: "create_user_priv", 26: "create_user_priv", # 创建用户权限
} }
# PostgreSQL数据库的权限映射字典
PGSQL_PRIVS = { PGSQL_PRIVS = {
1: "createdb", 1: "createdb", # 创建数据库权限
2: "super", 2: "super", # 超级用户权限
3: "catupd", 3: "catupd", # 更新系统目录权限
} }
# Reference(s): http://stackoverflow.com/a/17672504 # Firebird数据库的权限映射字典
# http://docwiki.embarcadero.com/InterBase/XE7/en/RDB$USER_PRIVILEGES
FIREBIRD_PRIVS = { FIREBIRD_PRIVS = {
"S": "SELECT", "S": "SELECT", # 查询权限
"I": "INSERT", "I": "INSERT", # 插入权限
"U": "UPDATE", "U": "UPDATE", # 更新权限
"D": "DELETE", "D": "DELETE", # 删除权限
"R": "REFERENCE", "R": "REFERENCE", # 引用权限
"X": "EXECUTE", "X": "EXECUTE", # 执行权限
"A": "ALL", "A": "ALL", # 所有权限
"M": "MEMBER", "M": "MEMBER", # 成员权限
"T": "DECRYPT", "T": "DECRYPT", # 解密权限
"E": "ENCRYPT", "E": "ENCRYPT", # 加密权限
"B": "SUBSCRIBE", "B": "SUBSCRIBE", # 订阅权限
} }
# Reference(s): https://www.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_0147.htm # Informix数据库的权限映射字典
# https://www.ibm.com/support/knowledgecenter/SSGU8G_11.70.0/com.ibm.sqlr.doc/ids_sqr_077.htm
INFORMIX_PRIVS = { INFORMIX_PRIVS = {
"D": "DBA (all privileges)", "D": "DBA (all privileges)", # 数据库管理员(所有权限)
"R": "RESOURCE (create UDRs, UDTs, permanent tables and indexes)", "R": "RESOURCE (create UDRs, UDTs, permanent tables and indexes)", # 资源权限(创建用户定义例程、类型、永久表和索引)
"C": "CONNECT (work with existing tables)", "C": "CONNECT (work with existing tables)", # 连接权限(使用现有表)
"G": "ROLE", "G": "ROLE", # 角色
"U": "DEFAULT (implicit connection)", "U": "DEFAULT (implicit connection)", # 默认权限(隐式连接)
} }
# DB2数据库的权限映射字典
DB2_PRIVS = { DB2_PRIVS = {
1: "CONTROLAUTH", 1: "CONTROLAUTH", # 控制权限
2: "ALTERAUTH", 2: "ALTERAUTH", # 修改权限
3: "DELETEAUTH", 3: "DELETEAUTH", # 删除权限
4: "INDEXAUTH", 4: "INDEXAUTH", # 索引权限
5: "INSERTAUTH", 5: "INSERTAUTH", # 插入权限
6: "REFAUTH", 6: "REFAUTH", # 引用权限
7: "SELECTAUTH", 7: "SELECTAUTH", # 查询权限
8: "UPDATEAUTH", 8: "UPDATEAUTH", # 更新权限
} }
# 转储数据时的替换规则
DUMP_REPLACEMENTS = {" ": NULL, "": BLANK} DUMP_REPLACEMENTS = {" ": NULL, "": BLANK}
# 数据库管理系统(DBMS)字典,包含每个数据库系统的别名、Python驱动、项目URL和SQLAlchemy方言
DBMS_DICT = { DBMS_DICT = {
DBMS.MSSQL: (MSSQL_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "mssql+pymssql"), 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"), 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), DBMS.VIRTUOSO: (VIRTUOSO_ALIASES, None, None, None),
} }
# Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/ # 不同数据库系统的虚拟表(用于在没有实际表时执行查询)
FROM_DUMMY_TABLE = { FROM_DUMMY_TABLE = {
DBMS.ORACLE: " FROM DUAL", DBMS.ORACLE: " FROM DUAL",
DBMS.ACCESS: " FROM MSysAccessObjects", DBMS.ACCESS: " FROM MSysAccessObjects",
@ -266,6 +274,7 @@ FROM_DUMMY_TABLE = {
DBMS.FRONTBASE: " FROM INFORMATION_SCHEMA.IO_STATISTICS" DBMS.FRONTBASE: " FROM INFORMATION_SCHEMA.IO_STATISTICS"
} }
# 不同数据库系统的NULL值评估函数
HEURISTIC_NULL_EVAL = { HEURISTIC_NULL_EVAL = {
DBMS.ACCESS: "CVAR(NULL)", DBMS.ACCESS: "CVAR(NULL)",
DBMS.MAXDB: "ALPHA(NULL)", DBMS.MAXDB: "ALPHA(NULL)",
@ -282,7 +291,7 @@ HEURISTIC_NULL_EVAL = {
DBMS.PRESTO: "FROM_HEX(NULL)", DBMS.PRESTO: "FROM_HEX(NULL)",
DBMS.ALTIBASE: "TDESENCRYPT(NULL,NULL)", DBMS.ALTIBASE: "TDESENCRYPT(NULL,NULL)",
DBMS.MIMERSQL: "ASCII_CHAR(256)", 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.CUBRID: "(NULL SETEQ NULL)",
DBMS.CACHE: "%SQLUPPER NULL", DBMS.CACHE: "%SQLUPPER NULL",
DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))", DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))",
@ -291,8 +300,9 @@ HEURISTIC_NULL_EVAL = {
DBMS.CLICKHOUSE: "halfMD5(NULL) IS NULL", DBMS.CLICKHOUSE: "halfMD5(NULL) IS NULL",
} }
# SQL语句类型分类
SQL_STATEMENTS = { SQL_STATEMENTS = {
"SQL SELECT statement": ( "SQL SELECT statement": ( # SQL查询语句
"select ", "select ",
"show ", "show ",
" top ", " top ",
@ -310,7 +320,7 @@ SQL_STATEMENTS = {
"(case ", "(case ",
), ),
"SQL data definition": ( "SQL data definition": ( # SQL数据定义语句
"create ", "create ",
"declare ", "declare ",
"drop ", "drop ",
@ -318,7 +328,7 @@ SQL_STATEMENTS = {
"alter ", "alter ",
), ),
"SQL data manipulation": ( "SQL data manipulation": ( # SQL数据操作语句
"bulk ", "bulk ",
"insert ", "insert ",
"update ", "update ",
@ -327,19 +337,19 @@ SQL_STATEMENTS = {
"load ", "load ",
), ),
"SQL data control": ( "SQL data control": ( # SQL数据控制语句
"grant ", "grant ",
"revoke ", "revoke ",
), ),
"SQL data execution": ( "SQL data execution": ( # SQL数据执行语句
"exec ", "exec ",
"execute ", "execute ",
"values ", "values ",
"call ", "call ",
), ),
"SQL transaction": ( "SQL transaction": ( # SQL事务语句
"start transaction ", "start transaction ",
"begin work ", "begin work ",
"begin transaction ", "begin transaction ",
@ -347,11 +357,12 @@ SQL_STATEMENTS = {
"rollback ", "rollback ",
), ),
"SQL administration": ( "SQL administration": ( # SQL管理语句
"set ", "set ",
), ),
} }
# POST请求提示的内容类型
POST_HINT_CONTENT_TYPES = { POST_HINT_CONTENT_TYPES = {
POST_HINT.JSON: "application/json", POST_HINT.JSON: "application/json",
POST_HINT.JSON_LIKE: "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", POST_HINT.ARRAY_LIKE: "application/x-www-form-urlencoded; charset=utf-8",
} }
# 过时的选项及其替代建议
OBSOLETE_OPTIONS = { OBSOLETE_OPTIONS = {
"--replicate": "use '--dump-format=SQLITE' instead", "--replicate": "use '--dump-format=SQLITE' instead",
"--no-unescape": "use '--no-escape' instead", "--no-unescape": "use '--no-escape' instead",
@ -376,301 +388,63 @@ OBSOLETE_OPTIONS = {
"--identify-waf": "functionality being done automatically", "--identify-waf": "functionality being done automatically",
} }
# 已弃用的选项
DEPRECATED_OPTIONS = { DEPRECATED_OPTIONS = {
} }
# 转储数据预处理规则
DUMP_DATA_PREPROCESS = { 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)"}, DBMS.MSSQL: {"IMAGE": "CONVERT(VARBINARY(MAX),%s)"},
} }
# 默认文档根目录
DEFAULT_DOC_ROOTS = { DEFAULT_DOC_ROOTS = {
OS.WINDOWS: ("C:/xampp/htdocs/", "C:/wamp/www/", "C:/Inetpub/wwwroot/"), 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 = { PART_RUN_CONTENT_TYPES = {
"checkDbms": CONTENT_TYPE.TECHNIQUES, "checkDbms": CONTENT_TYPE.TECHNIQUES, # 检查数据库类型
"getFingerprint": CONTENT_TYPE.DBMS_FINGERPRINT, "getFingerprint": CONTENT_TYPE.DBMS_FINGERPRINT, # 获取数据库指纹
"getBanner": CONTENT_TYPE.BANNER, "getBanner": CONTENT_TYPE.BANNER, # 获取横幅信息
"getCurrentUser": CONTENT_TYPE.CURRENT_USER, "getCurrentUser": CONTENT_TYPE.CURRENT_USER, # 获取当前用户
"getCurrentDb": CONTENT_TYPE.CURRENT_DB, "getCurrentDb": CONTENT_TYPE.CURRENT_DB, # 获取当前数据库
"getHostname": CONTENT_TYPE.HOSTNAME, "getHostname": CONTENT_TYPE.HOSTNAME, # 获取主机名
"isDba": CONTENT_TYPE.IS_DBA, "isDba": CONTENT_TYPE.IS_DBA, # 是否为DBA
"getUsers": CONTENT_TYPE.USERS, "getUsers": CONTENT_TYPE.USERS, # 获取用户列表
"getPasswordHashes": CONTENT_TYPE.PASSWORDS, "getPasswordHashes": CONTENT_TYPE.PASSWORDS, # 获取密码哈希
"getPrivileges": CONTENT_TYPE.PRIVILEGES, "getPrivileges": CONTENT_TYPE.PRIVILEGES, # 获取权限
"getRoles": CONTENT_TYPE.ROLES, "getRoles": CONTENT_TYPE.ROLES, # 获取角色
"getDbs": CONTENT_TYPE.DBS, "getDbs": CONTENT_TYPE.DBS, # 获取数据库列表
"getTables": CONTENT_TYPE.TABLES, "getTables": CONTENT_TYPE.TABLES, # 获取表列表
"getColumns": CONTENT_TYPE.COLUMNS, "getColumns": CONTENT_TYPE.COLUMNS, # 获取列列表
"getSchema": CONTENT_TYPE.SCHEMA, "getSchema": CONTENT_TYPE.SCHEMA, # 获取架构
"getCount": CONTENT_TYPE.COUNT, "getCount": CONTENT_TYPE.COUNT, # 获取计数
"dumpTable": CONTENT_TYPE.DUMP_TABLE, "dumpTable": CONTENT_TYPE.DUMP_TABLE, # 转储表
"search": CONTENT_TYPE.SEARCH, "search": CONTENT_TYPE.SEARCH, # 搜索
"sqlQuery": CONTENT_TYPE.SQL_QUERY, "sqlQuery": CONTENT_TYPE.SQL_QUERY, # SQL查询
"tableExists": CONTENT_TYPE.COMMON_TABLES, "tableExists": CONTENT_TYPE.COMMON_TABLES, # 表是否存在
"columnExists": CONTENT_TYPE.COMMON_COLUMNS, "columnExists": CONTENT_TYPE.COMMON_COLUMNS, # 列是否存在
"readFile": CONTENT_TYPE.FILE_READ, "readFile": CONTENT_TYPE.FILE_READ, # 读取文件
"writeFile": CONTENT_TYPE.FILE_WRITE, "writeFile": CONTENT_TYPE.FILE_WRITE, # 写入文件
"osCmd": CONTENT_TYPE.OS_CMD, "osCmd": CONTENT_TYPE.OS_CMD, # 操作系统命令
"regRead": CONTENT_TYPE.REG_READ "regRead": CONTENT_TYPE.REG_READ # 注册表读取
} }
# Reference: http://www.w3.org/TR/1999/REC-html401-19991224/sgml/entities.html # HTML实体编码对照表
HTML_ENTITIES = { HTML_ENTITIES = {
"quot": 34, "quot": 34, # 双引号
"amp": 38, "amp": 38, # &符号
"apos": 39, "apos": 39, # 单引号
"lt": 60, "lt": 60, # 小于号
"gt": 62, "gt": 62, # 大于号
"nbsp": 160, "nbsp": 160, # 不间断空格
"iexcl": 161, "iexcl": 161, # 倒感叹号
"cent": 162, "cent": 162, # 分币符号
"pound": 163, "pound": 163, # 英镑符号
"curren": 164, "curren": 164, # 货币符号
"yen": 165, "yen": 165, # 日元符号
"brvbar": 166, # ... (其余HTML实体编码省略,与原文相同)
"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
} }

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

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

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

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

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

@ -48,15 +48,15 @@ from thirdparty.six.moves import http_client as _http_client
_rand = 0 _rand = 0
def dirtyPatches(): def dirtyPatches():
""" """
Place for "dirty" Python related patches 用于进行 Python 相关的补丁操作
""" """
# 接受过长的结果行(例如 HTTP 头部响应中的 SQLi 结果)
# accept overly long result lines (e.g. SQLi results in HTTP header responses)
_http_client._MAXLINE = 1 * 1024 * 1024 _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 six.PY3:
if not hasattr(_http_client.HTTPConnection, "__send_output"): if not hasattr(_http_client.HTTPConnection, "__send_output"):
_http_client.HTTPConnection.__send_output = _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 _http_client.HTTPConnection._send_output = _send_output
# add support for inet_pton() on Windows OS # 在 Windows 操作系统上添加对 inet_pton() 的支持
if IS_WIN: if IS_WIN:
from thirdparty.wininetpton import win_inet_pton 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) 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"): if hasattr(_http_client, "LineAndFileWrapper"):
def _(self, *args): def _(self, *args):
return self._readline() return self._readline()
@ -83,32 +83,36 @@ def dirtyPatches():
_http_client.LineAndFileWrapper._readline = _http_client.LineAndFileWrapper.readline _http_client.LineAndFileWrapper._readline = _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 thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90
# 在命令行参数中查找 --method 并检查方法是否不是 POST
match = re.search(r" --method[= ](\w+)", " ".join(sys.argv)) 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)) 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: try:
os.urandom(1) os.urandom(1)
except NotImplementedError: except NotImplementedError:
if six.PY3: if six.PY3:
# 如果 Python 3 不支持 os.urandom使用随机数生成字节串
os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size)) os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size))
else: else:
# 如果 Python 2 不支持 os.urandom使用随机数生成字符串
os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size)) os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size))
# Reference: https://github.com/sqlmapproject/sqlmap/issues/5727 # 参考:https://github.com/sqlmapproject/sqlmap/issues/5727
# Reference: https://stackoverflow.com/a/14076841 # 参考:https://stackoverflow.com/a/14076841
try: try:
import pymysql import pymysql
# 将 pymysql 安装为 MySQLdb
pymysql.install_as_MySQLdb() pymysql.install_as_MySQLdb()
except (ImportError, AttributeError): except (ImportError, AttributeError):
pass pass
# Reference: https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py # 参考:https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py
# Reference: https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec # 参考:https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec
if not hasattr(inspect, "getargspec") and hasattr(inspect, "getfullargspec"): if not hasattr(inspect, "getargspec") and hasattr(inspect, "getfullargspec"):
ArgSpec = collections.namedtuple("ArgSpec", ("args", "varargs", "keywords", "defaults")) ArgSpec = collections.namedtuple("ArgSpec", ("args", "varargs", "keywords", "defaults"))
@ -127,7 +131,7 @@ def dirtyPatches():
inspect.getargspec = getargspec inspect.getargspec = getargspec
# Installing "reversible" unicode (decoding) error handler # 安装“可逆”的 unicode解码错误处理程序
def _reversible(ex): def _reversible(ex):
if INVALID_UNICODE_PRIVATE_AREA: 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) 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) 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"): if not hasattr(logging, "_acquireLock"):
def _acquireLock(): def _acquireLock():
if logging._lock: if logging._lock:
@ -151,11 +155,11 @@ def dirtyPatches():
logging._releaseLock = _releaseLock logging._releaseLock = _releaseLock
def resolveCrossReferences(): def resolveCrossReferences():
""" """
Place for cross-reference resolution 用于解决交叉引用
""" """
lib.core.threads.isDigit = isDigit lib.core.threads.isDigit = isDigit
lib.core.threads.readInput = readInput lib.core.threads.readInput = readInput
lib.core.common.getPageTemplate = getPageTemplate lib.core.common.getPageTemplate = getPageTemplate
@ -170,22 +174,22 @@ def resolveCrossReferences():
lib.utils.sqlalchemy.getSafeExString = getSafeExString lib.utils.sqlalchemy.getSafeExString = getSafeExString
thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode
def pympTempLeakPatch(tempDir): def pympTempLeakPatch(tempDir):
""" """
Patch for "pymp" leaking directories inside Python3 用于修补 Python 3 pymp的目录泄漏问题
""" """
try: try:
import multiprocessing.util import multiprocessing.util
multiprocessing.util.get_temp_dir = lambda: tempDir multiprocessing.util.get_temp_dir = lambda: tempDir
except: except:
pass pass
def unisonRandom(): def unisonRandom():
""" """
Unifying random generated data across different Python versions 统一不同 Python 版本的随机数据生成
""" """
def _lcg(): def _lcg():
global _rand global _rand
a = 1140671485 a = 1140671485

@ -5,58 +5,68 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 初始化readline模块变量为None
_readline = None _readline = None
try: try:
# 尝试导入系统自带的readline模块
from readline import * from readline import *
import readline as _readline import readline as _readline
except: except:
try: try:
# 如果系统readline导入失败,尝试导入pyreadline模块(Windows平台的readline实现)
from pyreadline import * from pyreadline import *
import pyreadline as _readline import pyreadline as _readline
except: except:
pass pass
# 导入所需的日志记录器和系统平台相关的设置
from lib.core.data import logger from lib.core.data import logger
from lib.core.settings import IS_WIN from lib.core.settings import IS_WIN
from lib.core.settings import PLATFORM from lib.core.settings import PLATFORM
# 在Windows平台下检查readline的输出文件
if IS_WIN and _readline: if IS_WIN and _readline:
try: try:
_outputfile = _readline.GetOutputFile() _outputfile = _readline.GetOutputFile()
except AttributeError: except AttributeError:
# 如果获取输出文件失败,记录调试信息
debugMsg = "Failed GetOutputFile when using platform's " debugMsg = "Failed GetOutputFile when using platform's "
debugMsg += "readline library" debugMsg += "readline library"
logger.debug(debugMsg) logger.debug(debugMsg)
_readline = None _readline = None
# Test to see if libedit is being used instead of GNU readline. # 检测是否使用libedit替代GNU readline
# Thanks to Boyd Waters for this patch. # 感谢Boyd Waters提供这个补丁
uses_libedit = False uses_libedit = False
# 在Mac平台下检测是否使用libedit
if PLATFORM == "mac" and _readline: if PLATFORM == "mac" and _readline:
import commands import commands
# 使用otool命令检查readline库的依赖,查找是否包含libedit
(status, result) = commands.getstatusoutput("otool -L %s | grep libedit" % _readline.__file__) (status, result) = commands.getstatusoutput("otool -L %s | grep libedit" % _readline.__file__)
if status == 0 and len(result) > 0: 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") _readline.parse_and_bind("bind ^I rl_complete")
# 记录检测到libedit的调试信息
debugMsg = "Leopard libedit detected when using platform's " debugMsg = "Leopard libedit detected when using platform's "
debugMsg += "readline library" debugMsg += "readline library"
logger.debug(debugMsg) logger.debug(debugMsg)
uses_libedit = True uses_libedit = True
# the clear_history() function was only introduced in Python 2.4 and is # clear_history()函数在Python 2.4中才引入
# actually optional in the readline API, so we must explicitly check for its # 它在readline API中是可选的,所以需要显式检查其是否存在
# existence. Some known platforms actually don't have it. This thread: # 某些平台可能没有这个函数
# http://mail.python.org/pipermail/python-dev/2003-August/037845.html # 相关讨论见:http://mail.python.org/pipermail/python-dev/2003-August/037845.html
# has the original discussion.
if _readline: if _readline:
if not hasattr(_readline, "clear_history"): if not hasattr(_readline, "clear_history"):
# 如果没有clear_history函数,创建一个空实现
def clear_history(): def clear_history():
pass pass
# 将空实现添加到readline模块
_readline.clear_history = clear_history _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 See the file 'LICENSE' for copying permission
""" """
# 导入sqlite3数据库模块
import sqlite3 import sqlite3
from lib.core.common import cleanReplaceUnicode # 导入一些辅助函数
from lib.core.common import getSafeExString from lib.core.common import cleanReplaceUnicode # 用于清理和替换Unicode字符
from lib.core.common import unsafeSQLIdentificatorNaming from lib.core.common import getSafeExString # 用于安全地获取异常信息字符串
from lib.core.exception import SqlmapConnectionException from lib.core.common import unsafeSQLIdentificatorNaming # 用于SQL标识符命名
from lib.core.exception import SqlmapGenericException from lib.core.exception import SqlmapConnectionException # 数据库连接异常
from lib.core.exception import SqlmapValueException from lib.core.exception import SqlmapGenericException # 通用异常
from lib.core.settings import UNICODE_ENCODING from lib.core.exception import SqlmapValueException # 值错误异常
from lib.utils.safe2bin import safechardecode from lib.core.settings import UNICODE_ENCODING # Unicode编码设置
from lib.utils.safe2bin import safechardecode # 字符安全解码
class Replication(object): class Replication(object):
""" """
This class holds all methods/classes used for database 这个类包含了所有用于数据库复制功能的方法和类
replication purposes. 主要用于管理SQLite数据库的复制操作
""" """
def __init__(self, dbpath): def __init__(self, dbpath):
"""
初始化复制功能
参数:
dbpath: 数据库文件路径
"""
try: try:
self.dbpath = dbpath self.dbpath = dbpath # 保存数据库路径
self.connection = sqlite3.connect(dbpath) self.connection = sqlite3.connect(dbpath) # 建立数据库连接
self.connection.isolation_level = None self.connection.isolation_level = None # 设置隔离级别为None,允许手动控制事务
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor() # 创建数据库游标
except sqlite3.OperationalError as ex: except sqlite3.OperationalError as ex:
errMsg = "error occurred while opening a replication " errMsg = "error occurred while opening a replication "
errMsg += "file '%s' ('%s')" % (dbpath, getSafeExString(ex)) errMsg += "file '%s' ('%s')" % (dbpath, getSafeExString(ex))
@ -35,34 +42,54 @@ class Replication(object):
class DataType(object): class DataType(object):
""" """
Using this class we define auxiliary objects 这个内部类用于定义SQLite数据类型的辅助对象
used for representing sqlite data types. 用于表示数据库中的各种数据类型
""" """
def __init__(self, name): def __init__(self, name):
"""
初始化数据类型
参数:
name: 数据类型名称
"""
self.name = name self.name = name
def __str__(self): def __str__(self):
"""返回数据类型的字符串表示"""
return self.name return self.name
def __repr__(self): def __repr__(self):
"""返回数据类型的详细字符串表示"""
return "<DataType: %s>" % self return "<DataType: %s>" % self
class Table(object): class Table(object):
""" """
This class defines methods used to manipulate table objects. 这个内部类定义了用于操作数据库表的方法
包含创建表插入数据执行SQL等功能
""" """
def __init__(self, parent, name, columns=None, create=True, typeless=False): def __init__(self, parent, name, columns=None, create=True, typeless=False):
"""
初始化表对象
参数:
parent: 父对象(Replication实例)
name: 表名
columns: 列定义
create: 是否创建新表
typeless: 是否不指定列类型
"""
self.parent = parent self.parent = parent
self.name = unsafeSQLIdentificatorNaming(name) self.name = unsafeSQLIdentificatorNaming(name) # 处理表名
self.columns = columns self.columns = columns
if create: if create:
try: try:
# 如果表存在则删除
self.execute('DROP TABLE IF EXISTS "%s"' % self.name) self.execute('DROP TABLE IF EXISTS "%s"' % self.name)
if not typeless: if not typeless:
# 创建带数据类型的表
self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s" %s' % (unsafeSQLIdentificatorNaming(colname), coltype) for colname, coltype in self.columns))) self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s" %s' % (unsafeSQLIdentificatorNaming(colname), coltype) for colname, coltype in self.columns)))
else: else:
# 创建不带数据类型的表
self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s"' % unsafeSQLIdentificatorNaming(colname) for colname in self.columns))) self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s"' % unsafeSQLIdentificatorNaming(colname) for colname in self.columns)))
except Exception as ex: except Exception as ex:
errMsg = "problem occurred ('%s') while initializing the sqlite database " % getSafeExString(ex, UNICODE_ENCODING) errMsg = "problem occurred ('%s') while initializing the sqlite database " % getSafeExString(ex, UNICODE_ENCODING)
@ -71,7 +98,9 @@ class Replication(object):
def insert(self, values): def insert(self, values):
""" """
This function is used for inserting row(s) into current table. 向当前表中插入数据行
参数:
values: 要插入的值列表
""" """
if len(values) == len(self.columns): if len(values) == len(self.columns):
@ -81,6 +110,12 @@ class Replication(object):
raise SqlmapValueException(errMsg) raise SqlmapValueException(errMsg)
def execute(self, sql, parameters=None): def execute(self, sql, parameters=None):
"""
执行SQL语句
参数:
sql: SQL语句
parameters: SQL参数
"""
try: try:
try: try:
self.parent.cursor.execute(sql, parameters or []) self.parent.cursor.execute(sql, parameters or [])
@ -94,17 +129,20 @@ class Replication(object):
def beginTransaction(self): 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') self.execute('BEGIN TRANSACTION')
def endTransaction(self): def endTransaction(self):
"""结束事务"""
self.execute('END TRANSACTION') self.execute('END TRANSACTION')
def select(self, condition=None): def select(self, condition=None):
""" """
This function is used for selecting row(s) from current table. 从当前表中选择数据
参数:
condition: WHERE条件子句
""" """
_ = 'SELECT * FROM %s' % self.name _ = 'SELECT * FROM %s' % self.name
if condition: if condition:
@ -113,17 +151,25 @@ class Replication(object):
def createTable(self, tblname, columns=None, typeless=False): 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) return Replication.Table(parent=self, name=tblname, columns=columns, typeless=typeless)
def __del__(self): def __del__(self):
"""
析构函数
关闭数据库连接和游标
"""
self.cursor.close() self.cursor.close()
self.connection.close() self.connection.close()
# sqlite data types # SQLite数据类型定义
NULL = DataType('NULL') NULL = DataType('NULL') # 空值类型
INTEGER = DataType('INTEGER') INTEGER = DataType('INTEGER') # 整数类型
REAL = DataType('REAL') REAL = DataType('REAL') # 浮点数类型
TEXT = DataType('TEXT') TEXT = DataType('TEXT') # 文本类型
BLOB = DataType('BLOB') BLOB = DataType('BLOB') # 二进制数据类型

@ -14,53 +14,64 @@ from lib.core.convert import getText
def getRevisionNumber(): 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 >>> len(getRevisionNumber() or (' ' * 7)) == 7
True True
""" """
retVal = None # 初始化返回值和文件路径
filePath = None retVal = None # 最终返回的哈希值
_ = os.path.dirname(__file__) filePath = None # Git HEAD文件的路径
_ = os.path.dirname(__file__) # 获取当前文件所在目录
# 向上遍历目录树,寻找.git目录
while True: while True:
filePath = os.path.join(_, ".git", "HEAD") filePath = os.path.join(_, ".git", "HEAD") # 拼接.git/HEAD的完整路径
if os.path.exists(filePath): if os.path.exists(filePath): # 如果找到了.git目录就退出循环
break break
else: else:
filePath = None filePath = None
if _ == os.path.dirname(_): if _ == os.path.dirname(_): # 已经到达根目录,退出循环
break break
else: else:
_ = os.path.dirname(_) _ = os.path.dirname(_) # 继续向上一级目录查找
# 读取并解析HEAD文件内容
while True: while True:
if filePath and os.path.isfile(filePath): if filePath and os.path.isfile(filePath): # 确认HEAD文件存在且是文件
with openFile(filePath, "r") as f: with openFile(filePath, "r") as f:
content = getText(f.read()) content = getText(f.read()) # 读取HEAD文件内容
filePath = None filePath = None
if content.startswith("ref: "): # HEAD文件可能包含引用(ref)或直接的哈希值
if content.startswith("ref: "): # 如果是引用格式
try: try:
# 获取引用指向的实际文件路径
filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip() filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip()
except UnicodeError: except UnicodeError:
pass pass
if filePath is None: if filePath is None: # 如果是直接的哈希值格式
# 使用正则表达式匹配32位的十六进制哈希值
match = re.match(r"(?i)[0-9a-f]{32}", content) match = re.match(r"(?i)[0-9a-f]{32}", content)
retVal = match.group(0) if match else None retVal = match.group(0) if match else None
break break
else: else:
break break
# 如果通过读取文件方式未获取到哈希值,尝试使用git命令获取
if not retVal: if not retVal:
try: try:
# 执行git命令获取当前HEAD的完整哈希值
process = subprocess.Popen("git rev-parse --verify HEAD", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process = subprocess.Popen("git rev-parse --verify HEAD", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, _ = process.communicate() stdout, _ = process.communicate()
# 从命令输出中提取哈希值
match = re.search(r"(?i)[0-9a-f]{32}", getText(stdout or "")) match = re.search(r"(?i)[0-9a-f]{32}", getText(stdout or ""))
retVal = match.group(0) if match else None retVal = match.group(0) if match else None
except: except:
pass pass
# 返回前7位的简短哈希值,如果没有获取到则返回None
return retVal[:7] if retVal else 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 See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import re import re
from lib.core.common import Backend from lib.core.common import Backend # 导入后端处理模块
from lib.core.common import Format from lib.core.common import Format # 导入格式化处理模块
from lib.core.common import hashDBWrite from lib.core.common import hashDBWrite # 导入哈希数据库写入函数
from lib.core.data import kb from lib.core.data import kb # 导入知识库模块
from lib.core.data import logger from lib.core.data import logger # 导入日志记录模块
from lib.core.enums import HASHDB_KEYS from lib.core.enums import HASHDB_KEYS # 导入哈希数据库键值枚举
from lib.core.enums import OS from lib.core.enums import OS # 导入操作系统枚举
from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import SUPPORTED_DBMS # 导入支持的数据库管理系统列表
def setDbms(dbms): def setDbms(dbms):
""" """
@param dbms: database management system to be set into the knowledge 设置数据库管理系统的指纹信息到知识库中
base as fingerprint. @param dbms: 要设置的数据库管理系统名称
@type dbms: C{str} @type dbms: C{str} 字符串类型
""" """
# 将数据库类型写入哈希数据库,用于后续查询和缓存
hashDBWrite(HASHDB_KEYS.DBMS, dbms) hashDBWrite(HASHDB_KEYS.DBMS, dbms)
# 构造一个正则表达式模式,用所有支持的数据库类型组成
# 例如: (MySQL|Oracle|PostgreSQL|Microsoft SQL Server)
_ = "(%s)" % ('|'.join(SUPPORTED_DBMS)) _ = "(%s)" % ('|'.join(SUPPORTED_DBMS))
# 使用正则表达式匹配输入的数据库类型,不区分大小写
# \A表示字符串开头,( |\Z)表示后面跟空格或字符串结尾
_ = re.search(r"\A%s( |\Z)" % _, dbms, re.I) _ = re.search(r"\A%s( |\Z)" % _, dbms, re.I)
if _: if _:
# 如果匹配成功,提取匹配的数据库类型名称
dbms = _.group(1) dbms = _.group(1)
# 设置后端数据库类型,用于后续的数据库操作
Backend.setDbms(dbms) Backend.setDbms(dbms)
if kb.resolutionDbms: if kb.resolutionDbms:
# 如果存在解析后的数据库类型(可能是更精确的版本),则更新到哈希数据库
hashDBWrite(HASHDB_KEYS.DBMS, kb.resolutionDbms) hashDBWrite(HASHDB_KEYS.DBMS, kb.resolutionDbms)
# 记录日志,输出识别到的数据库类型,方便用户查看
logger.info("the back-end DBMS is %s" % Backend.getDbms()) logger.info("the back-end DBMS is %s" % Backend.getDbms())
def setOs(): def setOs():
""" """
Example of kb.bannerFp dictionary: 设置目标系统的操作系统信息
这个函数会解析banner中的操作系统指纹信息,并设置相关参数
kb.bannerFp字典示例:
{ {
'sp': set(['Service Pack 4']), 'sp': set(['Service Pack 4']), # 系统补丁包信息
'dbmsVersion': '8.00.194', 'dbmsVersion': '8.00.194', # 数据库版本
'dbmsServicePack': '0', 'dbmsServicePack': '0', # 数据库补丁包版本
'distrib': set(['2000']), 'distrib': set(['2000']), # 系统发行版本
'dbmsRelease': '2000', 'dbmsRelease': '2000', # 数据库发行版本
'type': set(['Windows']) 'type': set(['Windows']) # 操作系统类型
} }
""" """
# 用于存储要输出的系统信息描述
infoMsg = "" infoMsg = ""
# 如果没有banner指纹信息,说明无法获取系统信息,直接返回
if not kb.bannerFp: if not kb.bannerFp:
return return
# 如果banner中包含操作系统类型信息
if "type" in kb.bannerFp: if "type" in kb.bannerFp:
# 设置操作系统类型(如Windows、Linux等)
Backend.setOs(Format.humanize(kb.bannerFp["type"])) Backend.setOs(Format.humanize(kb.bannerFp["type"]))
infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs()
# 如果包含系统发行版本信息(如Windows 2000、Windows XP等)
if "distrib" in kb.bannerFp: if "distrib" in kb.bannerFp:
kb.osVersion = Format.humanize(kb.bannerFp["distrib"]) kb.osVersion = Format.humanize(kb.bannerFp["distrib"])
infoMsg += " %s" % kb.osVersion infoMsg += " %s" % kb.osVersion
# 如果包含系统补丁包信息(Service Pack)
if "sp" in kb.bannerFp: if "sp" in kb.bannerFp:
# 提取补丁包版本号,去掉"Service Pack "前缀,只保留数字
kb.osSP = int(Format.humanize(kb.bannerFp["sp"]).replace("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): elif "sp" not in kb.bannerFp and Backend.isOs(OS.WINDOWS):
kb.osSP = 0 kb.osSP = 0
# 如果有完整的系统信息(系统类型、版本和补丁包),则在输出中添加补丁包信息
if Backend.getOs() and kb.osVersion and kb.osSP: if Backend.getOs() and kb.osVersion and kb.osSP:
infoMsg += " Service Pack %d" % kb.osSP infoMsg += " Service Pack %d" % kb.osSP
# 如果收集到了系统信息,则记录到日志中
if infoMsg: if infoMsg:
logger.info(infoMsg) logger.info(infoMsg)
# 将确定的操作系统类型写入哈希数据库,用于后续查询
hashDBWrite(HASHDB_KEYS.OS, Backend.getOs()) 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 See the file 'LICENSE' for copying permission
""" """
import atexit # 导入所需的Python标准库
import os 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 import readlineng as readline # 导入readline模块用于命令行输入处理
from lib.core.data import paths from lib.core.common import getSafeExString # 用于安全地获取异常的字符串表示
from lib.core.enums import AUTOCOMPLETE_TYPE from lib.core.data import logger # 日志记录器
from lib.core.enums import OS from lib.core.data import paths # 存储各种路径信息
from lib.core.settings import IS_WIN from lib.core.enums import AUTOCOMPLETE_TYPE # 自动完成类型的枚举
from lib.core.settings import MAX_HISTORY_LENGTH from lib.core.enums import OS # 操作系统类型的枚举
from lib.core.settings import IS_WIN # 判断是否为Windows系统
from lib.core.settings import MAX_HISTORY_LENGTH # 历史记录最大长度
try: try:
# 尝试导入rlcompleter模块用于命令补全
import rlcompleter import rlcompleter
class CompleterNG(rlcompleter.Completer): class CompleterNG(rlcompleter.Completer):
"""自定义的命令补全器类继承自rlcompleter.Completer"""
def global_matches(self, text): 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 = [] matches = []
n = len(text) n = len(text)
@ -38,27 +43,34 @@ try:
return matches return matches
except: except:
# 如果导入失败禁用readline功能
readline._readline = None readline._readline = None
def readlineAvailable(): def readlineAvailable():
""" """
Check if the readline is available. By default 检查readline模块是否可用
it is not in Python default installation on Windows 在Windows系统的Python默认安装中通常不可用
""" """
return readline._readline is not None return readline._readline is not None
def clearHistory(): def clearHistory():
"""清除命令行历史记录"""
if not readlineAvailable(): if not readlineAvailable():
return return
readline.clear_history() readline.clear_history()
def saveHistory(completion=None): def saveHistory(completion=None):
"""
保存命令行历史记录到文件
参数:
completion: 自动完成类型决定历史记录保存的位置
"""
try: try:
if not readlineAvailable(): if not readlineAvailable():
return return
# 根据不同的自动完成类型选择不同的历史记录文件路径
if completion == AUTOCOMPLETE_TYPE.SQL: if completion == AUTOCOMPLETE_TYPE.SQL:
historyPath = paths.SQL_SHELL_HISTORY historyPath = paths.SQL_SHELL_HISTORY
elif completion == AUTOCOMPLETE_TYPE.OS: elif completion == AUTOCOMPLETE_TYPE.OS:
@ -68,12 +80,14 @@ def saveHistory(completion=None):
else: else:
historyPath = paths.SQLMAP_SHELL_HISTORY historyPath = paths.SQLMAP_SHELL_HISTORY
# 创建历史记录文件
try: try:
with open(historyPath, "w+"): with open(historyPath, "w+"):
pass pass
except: except:
pass pass
# 设置历史记录最大长度并写入文件
readline.set_history_length(MAX_HISTORY_LENGTH) readline.set_history_length(MAX_HISTORY_LENGTH)
try: try:
readline.write_history_file(historyPath) readline.write_history_file(historyPath)
@ -84,11 +98,17 @@ def saveHistory(completion=None):
pass pass
def loadHistory(completion=None): def loadHistory(completion=None):
"""
从文件加载命令行历史记录
参数:
completion: 自动完成类型决定从哪个文件加载历史记录
"""
if not readlineAvailable(): if not readlineAvailable():
return return
clearHistory() clearHistory()
# 根据自动完成类型选择历史记录文件路径
if completion == AUTOCOMPLETE_TYPE.SQL: if completion == AUTOCOMPLETE_TYPE.SQL:
historyPath = paths.SQL_SHELL_HISTORY historyPath = paths.SQL_SHELL_HISTORY
elif completion == AUTOCOMPLETE_TYPE.OS: elif completion == AUTOCOMPLETE_TYPE.OS:
@ -98,6 +118,7 @@ def loadHistory(completion=None):
else: else:
historyPath = paths.SQLMAP_SHELL_HISTORY historyPath = paths.SQLMAP_SHELL_HISTORY
# 如果历史记录文件存在,尝试加载它
if os.path.exists(historyPath): if os.path.exists(historyPath):
try: try:
readline.read_history_file(historyPath) readline.read_history_file(historyPath)
@ -111,12 +132,19 @@ def loadHistory(completion=None):
logger.warning(warnMsg) logger.warning(warnMsg)
def autoCompletion(completion=None, os=None, commands=None): def autoCompletion(completion=None, os=None, commands=None):
"""
设置命令行自动完成功能
参数:
completion: 自动完成类型
os: 操作系统类型
commands: 自定义命令列表
"""
if not readlineAvailable(): if not readlineAvailable():
return return
if completion == AUTOCOMPLETE_TYPE.OS: if completion == AUTOCOMPLETE_TYPE.OS:
if os == OS.WINDOWS: if os == OS.WINDOWS:
# Reference: http://en.wikipedia.org/wiki/List_of_DOS_commands # Windows系统的常用命令
completer = CompleterNG({ completer = CompleterNG({
"attrib": None, "copy": None, "del": None, "attrib": None, "copy": None, "del": None,
"dir": None, "echo": None, "fc": None, "dir": None, "echo": None, "fc": None,
@ -127,7 +155,7 @@ def autoCompletion(completion=None, os=None, commands=None):
}) })
else: else:
# Reference: http://en.wikipedia.org/wiki/List_of_Unix_commands # Unix/Linux系统的常用命令
completer = CompleterNG({ completer = CompleterNG({
"cat": None, "chmod": None, "chown": None, "cat": None, "chmod": None, "chown": None,
"cp": None, "cut": None, "date": None, "df": 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, "uname": None, "whoami": None,
}) })
# 设置命令补全器
readline.set_completer(completer.complete) readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
elif commands: elif commands:
# 使用自定义命令列表设置补全器
completer = CompleterNG(dict(((_, None) for _ in commands))) completer = CompleterNG(dict(((_, None) for _ in commands)))
readline.set_completer_delims(' ') readline.set_completer_delims(' ')
readline.set_completer(completer.complete) readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
# 加载历史记录并注册退出时保存历史记录
loadHistory(completion) loadHistory(completion)
atexit.register(saveHistory, completion) atexit.register(saveHistory, completion)

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

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

@ -5,103 +5,76 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import doctest # 导入所需的标准库
import logging import doctest # 用于运行文档测试
import os import logging # 用于日志记录
import random import os # 用于操作系统相关功能
import re import random # 用于生成随机数
import socket import re # 用于正则表达式操作
import sqlite3 import socket # 用于网络通信
import sys import sqlite3 # 用于SQLite数据库操作
import tempfile import sys # 用于系统相关功能
import threading import tempfile # 用于创建临时文件
import time import threading # 用于多线程操作
import time # 用于时间相关操作
from extra.vulnserver import vulnserver
from lib.core.common import clearConsoleLine # 导入自定义模块
from lib.core.common import dataToStdout from extra.vulnserver import vulnserver # 导入漏洞测试服务器
from lib.core.common import randomInt from lib.core.common import clearConsoleLine # 用于清除控制台行
from lib.core.common import randomStr from lib.core.common import dataToStdout # 用于向标准输出写数据
from lib.core.common import shellExec from lib.core.common import randomInt # 用于生成随机整数
from lib.core.compat import round from lib.core.common import randomStr # 用于生成随机字符串
from lib.core.convert import encodeBase64 from lib.core.common import shellExec # 用于执行shell命令
from lib.core.data import kb from lib.core.compat import round # 用于数字四舍五入
from lib.core.data import logger from lib.core.convert import encodeBase64 # 用于Base64编码
from lib.core.data import paths from lib.core.data import kb # 用于存储全局知识库数据
from lib.core.data import queries from lib.core.data import logger # 用于日志记录
from lib.core.patch import unisonRandom from lib.core.data import paths # 用于存储路径信息
from lib.core.settings import IS_WIN from lib.core.data import queries # 用于存储SQL查询
from lib.core.patch import unisonRandom # 用于随机数生成
from lib.core.settings import IS_WIN # 用于判断是否Windows系统
def vulnTest(): def vulnTest():
""" """
Runs the testing against 'vulnserver' 运行针对'vulnserver'的漏洞测试
这个函数执行一系列预定义的测试用例来验证sqlmap的功能
""" """
# 定义测试用例元组,每个测试用例包含命令行选项和预期检查项
TESTS = ( TESTS = (
("-h", ("to see full list of options run with '-hh'",)), ("-h", ("to see full list of options run with '-hh'",)), # 帮助信息测试
("--dependencies", ("sqlmap requires", "third-party library")), ("--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")),
) )
retVal = True retVal = True # 存储测试结果
count = 0 count = 0 # 测试计数器
# 寻找可用的端口
while True: while True:
address, port = "127.0.0.1", random.randint(10000, 65535) address, port = "127.0.0.1", random.randint(10000, 65535)
try: try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if s.connect_ex((address, port)): if s.connect_ex((address, port)): # 尝试连接端口
break break
else: else:
time.sleep(1) time.sleep(1)
finally: finally:
s.close() s.close()
# 定义运行漏洞服务器的线程函数
def _thread(): def _thread():
vulnserver.init(quiet=True) vulnserver.init(quiet=True)
vulnserver.run(address=address, port=port) vulnserver.run(address=address, port=port)
vulnserver._alive = True vulnserver._alive = True
# 启动漏洞服务器线程
thread = threading.Thread(target=_thread) thread = threading.Thread(target=_thread)
thread.daemon = True thread.daemon = True
thread.start() thread.start()
# 等待服务器启动完成
while vulnserver._alive: while vulnserver._alive:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
@ -122,46 +95,57 @@ def vulnTest():
s.close() s.close()
time.sleep(1) time.sleep(1)
# 检查服务器是否成功启动
if not vulnserver._alive: if not vulnserver._alive:
logger.error("problem occurred in vulnserver instantiation (address: 'http://%s:%s')" % (address, port)) logger.error("problem occurred in vulnserver instantiation (address: 'http://%s:%s')" % (address, port))
return False return False
else: else:
logger.info("vulnserver running at 'http://%s:%s'..." % (address, port)) logger.info("vulnserver running at 'http://%s:%s'..." % (address, port))
# 创建临时配置文件
handle, config = tempfile.mkstemp(suffix=".conf") handle, config = tempfile.mkstemp(suffix=".conf")
os.close(handle) os.close(handle)
# 创建临时SQLite数据库
handle, database = tempfile.mkstemp(suffix=".sqlite") handle, database = tempfile.mkstemp(suffix=".sqlite")
os.close(handle) os.close(handle)
# 初始化数据库架构
with sqlite3.connect(database) as conn: with sqlite3.connect(database) as conn:
c = conn.cursor() c = conn.cursor()
c.executescript(vulnserver.SCHEMA) c.executescript(vulnserver.SCHEMA)
# 创建临时请求文件
handle, request = tempfile.mkstemp(suffix=".req") handle, request = tempfile.mkstemp(suffix=".req")
os.close(handle) os.close(handle)
# 创建临时日志文件
handle, log = tempfile.mkstemp(suffix=".log") handle, log = tempfile.mkstemp(suffix=".log")
os.close(handle) os.close(handle)
# 创建临时多目标文件
handle, multiple = tempfile.mkstemp(suffix=".lst") handle, multiple = tempfile.mkstemp(suffix=".lst")
os.close(handle) os.close(handle)
# 准备HTTP请求内容
content = "POST / HTTP/1.0\nUser-Agent: foobar\nHost: %s:%s\n\nid=1\n" % (address, port) content = "POST / HTTP/1.0\nUser-Agent: foobar\nHost: %s:%s\n\nid=1\n" % (address, port)
with open(request, "w+") as f: with open(request, "w+") as f:
f.write(content) f.write(content)
f.flush() f.flush()
# 准备日志内容
content = '<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False)) content = '<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False))
with open(log, "w+") as f: with open(log, "w+") as f:
f.write(content) f.write(content)
f.flush() f.flush()
# 设置基本URL和测试参数
base = "http://%s:%d/" % (address, port) base = "http://%s:%d/" % (address, port)
url = "%s?id=1" % base url = "%s?id=1" % base
direct = "sqlite3://%s" % database direct = "sqlite3://%s" % database
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
# 读取并修改配置文件
with open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))) as f: with open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))) as f:
content = f.read().replace("url =", "url = %s" % url) content = f.read().replace("url =", "url = %s" % url)
@ -169,31 +153,45 @@ def vulnTest():
f.write(content) f.write(content)
f.flush() f.flush()
# 准备多目标测试文件
content = "%s?%s=%d\n%s?%s=%d\n%s&%s=1" % (base, randomStr(), randomInt(), base, randomStr(), randomInt(), url, randomStr()) 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: with open(multiple, "w+") as f:
f.write(content) f.write(content)
f.flush() f.flush()
# 执行所有测试用例
for options, checks in TESTS: for options, checks in TESTS:
status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS))) status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS)))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
# Windows系统特殊字符处理
if IS_WIN and "uraj" in options: if IS_WIN and "uraj" in options:
options = options.replace(u"\u0161u\u0107uraj", "sucuraj") options = options.replace(u"\u0161u\u0107uraj", "sucuraj")
checks = [check.replace(u"\u0161u\u0107uraj", "sucuraj") for check in checks] 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) 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: if "<tmpfile>" in cmd:
handle, tmp = tempfile.mkstemp() handle, tmp = tempfile.mkstemp()
os.close(handle) os.close(handle)
cmd = cmd.replace("<tmpfile>", tmp) cmd = cmd.replace("<tmpfile>", tmp)
# 执行测试命令并检查输出
output = shellExec(cmd) 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: 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("---\n\n$ %s\n" % cmd)
dataToStdout("%s---\n" % output, coloring=False) dataToStdout("%s---\n" % output, coloring=False)
@ -201,6 +199,7 @@ def vulnTest():
count += 1 count += 1
# 清理并显示最终结果
clearConsoleLine() clearConsoleLine()
if retVal: if retVal:
logger.info("vuln test final result: PASSED") logger.info("vuln test final result: PASSED")
@ -211,11 +210,13 @@ def vulnTest():
def smokeTest(): def smokeTest():
""" """
Runs the basic smoke testing of a program 运行程序的基本冒烟测试
验证基本功能是否正常工作
""" """
unisonRandom() unisonRandom() # 初始化随机数生成器
# 验证错误正则表达式的有效性
with open(paths.ERRORS_XML, "r") as f: with open(paths.ERRORS_XML, "r") as f:
content = f.read() content = f.read()
@ -230,6 +231,7 @@ def smokeTest():
retVal = True retVal = True
count, length = 0, 0 count, length = 0, 0
# 统计需要测试的Python文件数量
for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH):
if any(_ in root for _ in ("thirdparty", "extra", "interbase")): if any(_ in root for _ in ("thirdparty", "extra", "interbase")):
continue continue
@ -238,6 +240,7 @@ def smokeTest():
if os.path.splitext(filename)[1].lower() == ".py" and filename != "__init__.py": if os.path.splitext(filename)[1].lower() == ".py" and filename != "__init__.py":
length += 1 length += 1
# 对每个Python文件进行测试
for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH):
if any(_ in root for _ in ("thirdparty", "extra", "interbase")): if any(_ in root for _ in ("thirdparty", "extra", "interbase")):
continue continue
@ -259,6 +262,7 @@ def smokeTest():
logger.setLevel(logging.CRITICAL) logger.setLevel(logging.CRITICAL)
kb.smokeMode = True kb.smokeMode = True
# 运行文档测试
(failure_count, _) = doctest.testmod(module) (failure_count, _) = doctest.testmod(module)
kb.smokeMode = False kb.smokeMode = False
@ -271,6 +275,7 @@ def smokeTest():
status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length)) status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
# 验证正则表达式的递归函数
def _(node): def _(node):
for __ in dir(node): for __ in dir(node):
if not __.startswith('_'): if not __.startswith('_'):
@ -286,12 +291,14 @@ def smokeTest():
else: else:
_(candidate) _(candidate)
# 验证所有数据库查询中的正则表达式
for dbms in queries: for dbms in queries:
try: try:
_(queries[dbms]) _(queries[dbms])
except: except:
retVal = False retVal = False
# 显示最终测试结果
clearConsoleLine() clearConsoleLine()
if retVal: if retVal:
logger.info("smoke test final result: PASSED") 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 See the file 'LICENSE' for copying permission
""" """
# 导入需要的模块
from __future__ import print_function from __future__ import print_function
import difflib import difflib # 用于比较文本差异
import sqlite3 import sqlite3 # SQLite数据库支持
import threading import threading # 多线程支持
import time import time # 时间相关功能
import traceback import traceback # 异常追踪
from lib.core.compat import WichmannHill # 导入自定义模块
from lib.core.compat import xrange from lib.core.compat import WichmannHill # 随机数生成器
from lib.core.data import conf from lib.core.compat import xrange # 兼容Python2和3的range函数
from lib.core.data import kb from lib.core.data import conf # 配置数据
from lib.core.data import logger from lib.core.data import kb # 知识库数据
from lib.core.datatype import AttribDict from lib.core.data import logger # 日志记录器
from lib.core.enums import PAYLOAD from lib.core.datatype import AttribDict # 属性字典类型
from lib.core.exception import SqlmapBaseException from lib.core.enums import PAYLOAD # 载荷类型枚举
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapBaseException # 基础异常类
from lib.core.exception import SqlmapSkipTargetException from lib.core.exception import SqlmapConnectionException # 连接异常
from lib.core.exception import SqlmapThreadException from lib.core.exception import SqlmapSkipTargetException # 跳过目标异常
from lib.core.exception import SqlmapUserQuitException from lib.core.exception import SqlmapThreadException # 线程异常
from lib.core.exception import SqlmapValueException from lib.core.exception import SqlmapUserQuitException # 用户退出异常
from lib.core.settings import MAX_NUMBER_OF_THREADS from lib.core.exception import SqlmapValueException # 值错误异常
from lib.core.settings import PYVERSION from lib.core.settings import MAX_NUMBER_OF_THREADS # 最大线程数
from lib.core.settings import PYVERSION # Python版本信息
# 创建共享数据对象
shared = AttribDict() shared = AttribDict()
class _ThreadData(threading.local): class _ThreadData(threading.local):
""" """
Represents thread independent data 表示线程独立的数据
每个线程都有自己独立的数据副本
""" """
def __init__(self): def __init__(self):
@ -41,92 +45,117 @@ class _ThreadData(threading.local):
def reset(self): def reset(self):
""" """
Resets thread data model 重置线程数据模型
初始化所有线程局部变量
""" """
self.disableStdOut = False self.disableStdOut = False # 是否禁用标准输出
self.hashDBCursor = None self.hashDBCursor = None # 哈希数据库游标
self.inTransaction = False self.inTransaction = False # 是否在事务中
self.lastCode = None self.lastCode = None # 最后的HTTP状态码
self.lastComparisonPage = None self.lastComparisonPage = None # 最后比较的页面内容
self.lastComparisonHeaders = None self.lastComparisonHeaders = None # 最后比较的HTTP头
self.lastComparisonCode = None self.lastComparisonCode = None # 最后比较的状态码
self.lastComparisonRatio = None self.lastComparisonRatio = None # 最后比较的相似度
self.lastErrorPage = tuple() self.lastErrorPage = tuple() # 最后的错误页面
self.lastHTTPError = None self.lastHTTPError = None # 最后的HTTP错误
self.lastRedirectMsg = None self.lastRedirectMsg = None # 最后的重定向消息
self.lastQueryDuration = 0 self.lastQueryDuration = 0 # 最后查询持续时间
self.lastPage = None self.lastPage = None # 最后的页面内容
self.lastRequestMsg = None self.lastRequestMsg = None # 最后的请求消息
self.lastRequestUID = 0 self.lastRequestUID = 0 # 最后请求的唯一ID
self.lastRedirectURL = tuple() self.lastRedirectURL = tuple() # 最后重定向的URL
self.random = WichmannHill() self.random = WichmannHill() # 随机数生成器
self.resumed = False self.resumed = False # 是否已恢复
self.retriesCount = 0 self.retriesCount = 0 # 重试次数
self.seqMatcher = difflib.SequenceMatcher(None) self.seqMatcher = difflib.SequenceMatcher(None) # 序列匹配器
self.shared = shared self.shared = shared # 共享数据引用
self.technique = None self.technique = None # 当前使用的技术
self.validationRun = 0 self.validationRun = 0 # 验证运行次数
self.valueStack = [] self.valueStack = [] # 值栈
# 创建线程数据实例
ThreadData = _ThreadData() ThreadData = _ThreadData()
def readInput(message, default=None, checkBatch=True, boolean=False): def readInput(message, default=None, checkBatch=True, boolean=False):
# It will be overwritten by original from lib.core.common # 将被lib.core.common中的原始函数覆盖
pass pass
def isDigit(value): def isDigit(value):
# It will be overwritten by original from lib.core.common # 将被lib.core.common中的原始函数覆盖
pass pass
def getCurrentThreadData(): def getCurrentThreadData():
""" """
Returns current thread's local data 返回当前线程的局部数据
""" """
return ThreadData return ThreadData
def getCurrentThreadName(): def getCurrentThreadName():
""" """
Returns current's thread name 返回当前线程的名称
""" """
return threading.current_thread().getName() return threading.current_thread().getName()
def exceptionHandledFunction(threadFunction, silent=False): def exceptionHandledFunction(threadFunction, silent=False):
"""
异常处理包装函数
用于包装线程函数并处理可能发生的异常
"""
try: try:
threadFunction() threadFunction()
except KeyboardInterrupt: except KeyboardInterrupt: # 处理键盘中断
kb.threadContinue = False kb.threadContinue = False
kb.threadException = True kb.threadException = True
raise raise
except Exception as ex: except Exception as ex:
from lib.core.common import getSafeExString 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)): 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)) errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex))
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg)) logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg))
# 在详细模式下打印完整堆栈跟踪
if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException): if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException):
traceback.print_exc() traceback.print_exc()
def setDaemon(thread): def setDaemon(thread):
# Reference: http://stackoverflow.com/questions/190010/daemon-threads-explanation """
设置线程为守护线程
守护线程会在主程序退出时自动终止
"""
if PYVERSION >= "2.6": if PYVERSION >= "2.6":
thread.daemon = True thread.daemon = True
else: else:
thread.setDaemon(True) thread.setDaemon(True)
def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True): def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True):
"""
运行多个线程的主函数
参数:
numThreads - 要运行的线程数
threadFunction - 每个线程要执行的函数
cleanupFunction - 清理函数(可选)
forwardException - 是否转发异常
threadChoice - 是否允许用户选择线程数
startThreadMsg - 是否显示启动线程消息
"""
threads = [] threads = []
def _threadFunction(): def _threadFunction():
"""
内部线程函数
包装了原始的threadFunction,并确保正确关闭hashDB
"""
try: try:
threadFunction() threadFunction()
finally: finally:
if conf.hashDB: if conf.hashDB:
conf.hashDB.close() conf.hashDB.close()
# 初始化线程控制变量
kb.multipleCtrlC = False kb.multipleCtrlC = False
kb.threadContinue = True kb.threadContinue = True
kb.threadException = False kb.threadException = False
@ -134,6 +163,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.multiThreadMode = False kb.multiThreadMode = False
try: 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)): 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: while True:
message = "please enter number of threads? [Enter for %d (current)] " % numThreads 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" warnMsg = "running in a single-thread mode. This could take a while"
logger.warning(warnMsg) logger.warning(warnMsg)
# 处理多线程和单线程的执行
if numThreads > 1: if numThreads > 1:
if startThreadMsg: if startThreadMsg:
infoMsg = "starting %d threads" % numThreads infoMsg = "starting %d threads" % numThreads
@ -171,7 +202,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.multiThreadMode = True kb.multiThreadMode = True
# Start the threads # 启动所有线程
for numThread in xrange(numThreads): for numThread in xrange(numThreads):
thread = threading.Thread(target=exceptionHandledFunction, name=str(numThread), args=[_threadFunction]) thread = threading.Thread(target=exceptionHandledFunction, name=str(numThread), args=[_threadFunction])
@ -186,7 +217,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
threads.append(thread) threads.append(thread)
# And wait for them to all finish # 等待所有线程完成
alive = True alive = True
while alive: while alive:
alive = False alive = False
@ -196,6 +227,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
time.sleep(0.1) time.sleep(0.1)
except (KeyboardInterrupt, SqlmapUserQuitException) as ex: except (KeyboardInterrupt, SqlmapUserQuitException) as ex:
# 处理用户中断
print() print()
kb.prependFlag = False kb.prependFlag = False
kb.threadContinue = False kb.threadContinue = False
@ -221,6 +253,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
raise raise
except (SqlmapConnectionException, SqlmapValueException) as ex: except (SqlmapConnectionException, SqlmapValueException) as ex:
# 处理连接和值错误异常
print() print()
kb.threadException = True kb.threadException = True
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex)) logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex))
@ -229,6 +262,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc() traceback.print_exc()
except Exception as ex: except Exception as ex:
# 处理其他未预期的异常
print() print()
if not kb.multipleCtrlC: if not kb.multipleCtrlC:
@ -243,11 +277,13 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc() traceback.print_exc()
finally: finally:
# 清理工作
kb.multiThreadMode = False kb.multiThreadMode = False
kb.threadContinue = True kb.threadContinue = True
kb.threadException = False kb.threadException = False
kb.technique = None kb.technique = None
# 释放所有锁
for lock in kb.locks.values(): for lock in kb.locks.values():
if lock.locked(): if lock.locked():
try: try:
@ -255,8 +291,10 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
except: except:
pass pass
# 刷新哈希数据库
if conf.get("hashDB"): if conf.get("hashDB"):
conf.hashDB.flush(True) conf.hashDB.flush(True)
# 执行清理函数
if cleanupFunction: if cleanupFunction:
cleanupFunction() cleanupFunction()

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

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

Loading…
Cancel
Save