#!/usr/bin/env python """ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ 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 # 初始化方法,此处为空 def searchDb(self): """ 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 # 如果是MySQL且没有information_schema,则使用condition2 else: dbCond = rootQuery.inband.condition # 否则使用condition dbConsider, dbCondParam = self.likeOrExact("database") # 获取数据库搜索条件 for db in dbList: # 遍历数据库列表 values = [] # 初始化查询结果列表 db = safeSQLIdentificatorNaming(db) # 对数据库名称进行安全命名 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: db = db.upper() # 如果是需要大写的数据库,则转换为大写 infoMsg = "searching database" # 初始化日志信息 if dbConsider == "1": 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 = "" # 如果不排除系统数据库,则SQL语句为空 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 # 如果是MySQL且没有information_schema,则使用query2 else: query = rootQuery.inband.query # 否则使用query query = query % (dbQuery + exclDbsQuery) # 将查询条件添加到SQL语句中 values = inject.getValue(query, blind=False, time=False) # 执行SQL查询 if not isNoneValue(values): # 如果查询结果不为空 values = arrayizeValue(values) # 将查询结果转换为数组 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" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) # 打印日志信息 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.blind.count2 # 如果是MySQL且没有information_schema,则使用count2 else: query = rootQuery.blind.count # 否则使用count query = query % (dbQuery + exclDbsQuery) # 将查询条件添加到SQL语句中 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 执行盲注,获取数据库数量 if not isNumPosStrValue(count): # 如果数据库数量不是正整数 warnMsg = "no database" if dbConsider == "1": warnMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE warnMsg += " '%s' found" % unsafeSQLIdentificatorNaming(db) logger.warning(warnMsg) # 打印日志信息 continue # 跳过当前数据库 indexRange = getLimitRange(count) # 获取索引范围 for index in indexRange: # 遍历索引范围 if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: query = rootQuery.blind.query2 # 如果是MySQL且没有information_schema,则使用query2 else: query = rootQuery.blind.query # 否则使用query 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) # 打印找到的数据库列表 def searchTable(self): """ 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 # 如果是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 choice == 'N': # 如果用户选择不使用常用表名检查 return elif choice == 'Q': # 如果用户选择退出 raise SqlmapUserQuitException else: 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") # 获取表搜索条件 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 # 如果用户指定了数据库,则转换为大写 infoMsg = "searching table" if tblConsider == '1': infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) # 添加不安全的表名 if conf.db == CURRENT_DB: 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) # 构建排除系统数据库的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 = "" # 如果不排除系统数据库,则SQL语句为空 if dbCond and conf.exclude: whereDbsQuery += " AND %s NOT LIKE '%s'" % (dbCond, re.sub(r"\.[*+]", '%', conf.exclude._original)) # 添加排除条件 logger.info(infoMsg) # 打印日志信息 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) # 将查询条件添加到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) # 对表名进行安全命名 if foundDb is None or foundTbl is None: continue if foundDb in foundTbls: # 如果数据库已经存在于结果列表中 foundTbls[foundDb].append(foundTbl) # 则添加表名到该数据库的表列表中 else: 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" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) logger.info(infoMsg) # 打印日志信息 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): # 如果表数量不是正整数 warnMsg = "no databases have table" if tblConsider == "1": warnMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE warnMsg += " '%s'" % unsafeSQLIdentificatorNaming(tbl) logger.warning(warnMsg) # 打印日志信息 continue # 跳过当前表 indexRange = getLimitRange(count) # 获取索引范围 for index in indexRange: 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] = [] # 如果数据库不存在于结果列表中,则新建 if tblConsider == "2": foundTbls[foundDb].append(tbl) # 如果是精确匹配,则直接添加表名 if tblConsider == "2": 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: # 如果是SQLite或Firebird数据库 dbName = "SQLite" if Backend.isDbms(DBMS.SQLITE) else "Firebird" # 获取数据库名称 foundTbls["%s%s" % (dbName, METADB_SUFFIX)] = [] # 添加数据库名称到结果列表中 for db in foundTbls: # 遍历数据库列表 db = safeSQLIdentificatorNaming(db) # 对数据库名称进行安全命名 infoMsg = "fetching number of table" if tblConsider == "1": infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s' in database '%s'" % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(db)) # 添加表名和数据库名到日志信息 logger.info(infoMsg) # 打印日志信息 query = rootQuery.blind.count2 # 获取盲注计数查询 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): 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" # 如果是LIKE匹配,则添加LIKE warnMsg += " '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(db) logger.warning(warnMsg) # 打印日志信息 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)) # 如果查询语句包含 ORDER BY,则添加 AND 条件 elif query.endswith("'%s')"): query = query[:-1] + " AND %s)" % tblQuery # 如果查询语句以 ')' 结尾,则添加 AND 条件 else: query += " AND %s" % tblQuery # 否则直接添加 AND 条件 if Backend.isDbms(DBMS.FIREBIRD): query = safeStringFormat(query, index) # 如果是Firebird数据库,则格式化查询语句 if Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.FIREBIRD): query = safeStringFormat(query, unsafeSQLIdentificatorNaming(db)) # 如果不是SQLite或Firebird数据库,则格式化查询语句 if not Backend.isDbms(DBMS.FIREBIRD): query = agent.limitQuery(index, query) # 如果不是Firebird数据库,则添加限制条件 foundTbl = unArrayizeValue(inject.getValue(query, union=False, error=False)) # 执行盲注,获取表名 if not isNoneValue(foundTbl): kb.hintValue = foundTbl foundTbl = safeSQLIdentificatorNaming(foundTbl, True) # 对表名进行安全命名 foundTbls[db].append(foundTbl) # 将表名添加到结果列表中 for db in list(foundTbls.keys()): # 遍历数据库列表 if isNoneValue(foundTbls[db]): # 如果某个数据库的表列表为空 del foundTbls[db] # 则删除该数据库 if not foundTbls: # 如果没有找到表 warnMsg = "no databases contain any of the provided tables" logger.warning(warnMsg) # 打印日志信息 return # 返回 conf.dumper.dbTables(foundTbls) # 打印找到的表列表 self.dumpFoundTables(foundTbls) # 导出找到的表 def searchColumn(self): """ Searches for column names based on user input. 根据用户输入搜索列名称。 """ bruteForce = False # 初始化是否进行爆破 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 # 如果是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]") choice = readInput(message, default='Y' if 'Y' in message else 'N').upper() if choice == 'N': return elif choice == 'Q': raise SqlmapUserQuitException else: regex = '|'.join(conf.col.split(',')) conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS, regex)) message = "do you want to dump entries? [Y/n] " if readInput(message, default='Y', boolean=True): self.dumpAll() return 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] # 如果存在排除条件,则过滤列列表 origTbl = conf.tbl origDb = conf.db 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 # 重置表名 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 # 如果用户指定了表,则转换为大写 infoMsg = "searching column" if colConsider == "1": infoMsg += "s LIKE" # 如果是LIKE匹配,则添加LIKE infoMsg += " '%s'" % unsafeSQLIdentificatorNaming(column) # 添加不安全的列名 foundCols[column] = {} # 初始化列结果列表 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)) # 添加表信息到日志信息 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) # 构建排除系统数据库的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" # 如果不指定数据库,则表示在所有数据库中查找 if conf.exclude: whereDbsQuery += " AND %s NOT LIKE '%s'" % (dbCond, re.sub(r"\.[*+]", '%', conf.exclude._original)) # 添加排除条件 logger.info("%s%s%s" % (infoMsg, infoMsgTbl, infoMsgDb)) # 打印日志信息 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) # 将查询条件添加到SQL语句中 values = inject.getValue(query, blind=False, time=False) # 执行SQL查询 else: # Assume provided databases' tables contain the # column(s) provided values = [] # 如果指定了数据库和表,则直接使用它们 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 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 # 设置列名 self.getColumns(onlyColNames=True, colTuple=(colConsider, colCondParam), bruteForce=False) # 获取列名,不进行爆破 if db in kb.data.cachedColumns and tbl in kb.data.cachedColumns[db]: # 如果列信息已经缓存 if db not in dbs: dbs[db] = {} # 如果数据库不在dbs中,则添加 if tbl not in dbs[db]: dbs[db][tbl] = {} # 如果表不在dbs中,则添加 dbs[db][tbl].update(kb.data.cachedColumns[db][tbl]) # 更新缓存的列信息 if db in foundCols[column]: foundCols[column][db].append(tbl) # 如果数据库已经在foundCols中,则添加表 else: 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] # 在 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) # 打印警告消息