You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sqlmap/src/sqlmap-master/plugins/generic/search.py

742 lines
46 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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) # 将数据库名称添加到查询条件中
# 如果支持UNIONERROR或者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) # 将表名添加到表查询条件中
# 如果支持UNIONERROR或者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) # 将列名添加到查询条件中
# 如果支持UNIONERROR或者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)
# 打印警告消息