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