|
|
|
@ -5,10 +5,14 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
|
|
|
|
|
See the file 'LICENSE' for copying permission
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# 导入正则表达式模块
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
# 从lib.core.agent模块导入agent对象,用于处理注入过程中的各种细节
|
|
|
|
|
from lib.core.agent import agent
|
|
|
|
|
# 从lib.core.bigarray模块导入BigArray类,用于高效存储和处理大量数据
|
|
|
|
|
from lib.core.bigarray import BigArray
|
|
|
|
|
# 从lib.core.common模块导入各种常用函数和常量,如Backend、clearConsoleLine、getLimitRange等
|
|
|
|
|
from lib.core.common import Backend
|
|
|
|
|
from lib.core.common import clearConsoleLine
|
|
|
|
|
from lib.core.common import getLimitRange
|
|
|
|
@ -25,33 +29,47 @@ from lib.core.common import singleTimeLogMessage
|
|
|
|
|
from lib.core.common import singleTimeWarnMessage
|
|
|
|
|
from lib.core.common import unArrayizeValue
|
|
|
|
|
from lib.core.common import unsafeSQLIdentificatorNaming
|
|
|
|
|
# 从lib.core.convert模块导入getConsoleLength函数,用于获取控制台字符串的长度
|
|
|
|
|
from lib.core.convert import getConsoleLength
|
|
|
|
|
from lib.core.convert import getUnicode
|
|
|
|
|
# 从lib.core.data模块导入conf和kb对象,用于存储配置信息和知识库信息
|
|
|
|
|
from lib.core.data import conf
|
|
|
|
|
from lib.core.data import kb
|
|
|
|
|
# 从lib.core.data模块导入logger对象,用于记录日志信息
|
|
|
|
|
from lib.core.data import logger
|
|
|
|
|
# 从lib.core.data模块导入queries对象,用于存储各种数据库的查询语句
|
|
|
|
|
from lib.core.data import queries
|
|
|
|
|
# 从lib.core.dicts模块导入DUMP_REPLACEMENTS字典,用于替换转储数据中的特殊字符
|
|
|
|
|
from lib.core.dicts import DUMP_REPLACEMENTS
|
|
|
|
|
# 从lib.core.enums模块导入各种枚举类型,如CHARSET_TYPE、DBMS、EXPECTED、PAYLOAD等
|
|
|
|
|
from lib.core.enums import CHARSET_TYPE
|
|
|
|
|
from lib.core.enums import DBMS
|
|
|
|
|
from lib.core.enums import EXPECTED
|
|
|
|
|
from lib.core.enums import PAYLOAD
|
|
|
|
|
# 从lib.core.exception模块导入各种自定义异常类
|
|
|
|
|
from lib.core.exception import SqlmapConnectionException
|
|
|
|
|
from lib.core.exception import SqlmapMissingMandatoryOptionException
|
|
|
|
|
from lib.core.exception import SqlmapNoneDataException
|
|
|
|
|
from lib.core.exception import SqlmapUnsupportedFeatureException
|
|
|
|
|
# 从lib.core.settings模块导入各种配置常量
|
|
|
|
|
from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD
|
|
|
|
|
from lib.core.settings import CURRENT_DB
|
|
|
|
|
from lib.core.settings import METADB_SUFFIX
|
|
|
|
|
from lib.core.settings import NULL
|
|
|
|
|
from lib.core.settings import PLUS_ONE_DBMSES
|
|
|
|
|
from lib.core.settings import UPPER_CASE_DBMSES
|
|
|
|
|
# 从lib.request模块导入inject函数,用于执行SQL注入
|
|
|
|
|
from lib.request import inject
|
|
|
|
|
# 从lib.utils.hash模块导入attackDumpedTable函数,用于对转储的表数据进行攻击
|
|
|
|
|
from lib.utils.hash import attackDumpedTable
|
|
|
|
|
# 从lib.utils.pivotdumptable模块导入pivotDumpTable函数,用于执行透视转储表操作
|
|
|
|
|
from lib.utils.pivotdumptable import pivotDumpTable
|
|
|
|
|
# 导入第三方six库,用于兼容Python 2和Python 3
|
|
|
|
|
from thirdparty import six
|
|
|
|
|
# 导入第三方six库的zip函数,并重命名为_zip
|
|
|
|
|
from thirdparty.six.moves import zip as _zip
|
|
|
|
|
|
|
|
|
|
# 定义Entries类,用于封装枚举数据库条目的相关功能
|
|
|
|
|
class Entries(object):
|
|
|
|
|
"""
|
|
|
|
|
This class defines entries' enumeration functionalities for plugins.
|
|
|
|
@ -60,9 +78,12 @@ class Entries(object):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# 定义dumpTable方法,用于转储指定表的条目
|
|
|
|
|
def dumpTable(self, foundData=None):
|
|
|
|
|
# 强制执行数据库枚举,确保已获取数据库类型
|
|
|
|
|
self.forceDbmsEnum()
|
|
|
|
|
|
|
|
|
|
# 如果没有指定数据库或指定为当前数据库,则获取当前数据库
|
|
|
|
|
if conf.db is None or conf.db == CURRENT_DB:
|
|
|
|
|
if conf.db is None:
|
|
|
|
|
warnMsg = "missing database parameter. sqlmap is going "
|
|
|
|
@ -71,65 +92,81 @@ class Entries(object):
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
conf.db = self.getCurrentDb()
|
|
|
|
|
|
|
|
|
|
# 如果指定了数据库
|
|
|
|
|
elif conf.db is not None:
|
|
|
|
|
# 如果数据库是属于大写数据库类型,则将其转换为大写
|
|
|
|
|
if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
|
|
|
|
|
conf.db = conf.db.upper()
|
|
|
|
|
|
|
|
|
|
# 如果数据库名包含逗号,则抛出异常,因为只允许一个数据库名
|
|
|
|
|
if ',' in conf.db:
|
|
|
|
|
errMsg = "only one database name is allowed when enumerating "
|
|
|
|
|
errMsg += "the tables' columns"
|
|
|
|
|
raise SqlmapMissingMandatoryOptionException(errMsg)
|
|
|
|
|
|
|
|
|
|
# 如果数据库名匹配排除模式,则跳过
|
|
|
|
|
if conf.exclude and re.search(conf.exclude, conf.db, re.I) is not None:
|
|
|
|
|
infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
|
|
|
|
|
singleTimeLogMessage(infoMsg)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 对数据库名进行安全处理
|
|
|
|
|
conf.db = safeSQLIdentificatorNaming(conf.db) or ""
|
|
|
|
|
|
|
|
|
|
# 如果指定了表
|
|
|
|
|
if conf.tbl:
|
|
|
|
|
# 如果表名是属于大写数据库类型,则将其转换为大写
|
|
|
|
|
if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
|
|
|
|
|
conf.tbl = conf.tbl.upper()
|
|
|
|
|
|
|
|
|
|
# 将表名拆分为列表
|
|
|
|
|
tblList = conf.tbl.split(',')
|
|
|
|
|
# 如果没有指定表
|
|
|
|
|
else:
|
|
|
|
|
# 获取所有表
|
|
|
|
|
self.getTables()
|
|
|
|
|
|
|
|
|
|
# 如果已缓存表信息
|
|
|
|
|
if len(kb.data.cachedTables) > 0:
|
|
|
|
|
# 获取表列表
|
|
|
|
|
tblList = list(six.itervalues(kb.data.cachedTables))
|
|
|
|
|
|
|
|
|
|
# 如果表列表嵌套,则解包
|
|
|
|
|
if tblList and isListLike(tblList[0]):
|
|
|
|
|
tblList = tblList[0]
|
|
|
|
|
# 如果指定了数据库但未能获取表信息
|
|
|
|
|
elif conf.db and not conf.search:
|
|
|
|
|
errMsg = "unable to retrieve the tables "
|
|
|
|
|
errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
|
|
|
|
|
raise SqlmapNoneDataException(errMsg)
|
|
|
|
|
else:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 对表名列表中的表名进行安全处理
|
|
|
|
|
for tbl in tblList:
|
|
|
|
|
tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True)
|
|
|
|
|
|
|
|
|
|
# 遍历表列表
|
|
|
|
|
for tbl in tblList:
|
|
|
|
|
# 如果检测到键盘中断,则跳出循环
|
|
|
|
|
if kb.dumpKeyboardInterrupt:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# 如果表名匹配排除模式,则跳过
|
|
|
|
|
if conf.exclude and re.search(conf.exclude, tbl, re.I) is not None:
|
|
|
|
|
infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
singleTimeLogMessage(infoMsg)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 设置当前表名
|
|
|
|
|
conf.tbl = tbl
|
|
|
|
|
# 初始化已转储的表数据
|
|
|
|
|
kb.data.dumpedTable = {}
|
|
|
|
|
|
|
|
|
|
# 如果没有传入已发现的列数据
|
|
|
|
|
if foundData is None:
|
|
|
|
|
# 清空缓存的列信息
|
|
|
|
|
kb.data.cachedColumns = {}
|
|
|
|
|
# 获取列信息,仅获取列名,并设置转储模式
|
|
|
|
|
self.getColumns(onlyColNames=True, dumpMode=True)
|
|
|
|
|
# 如果传入了已发现的列数据,则直接使用
|
|
|
|
|
else:
|
|
|
|
|
kb.data.cachedColumns = foundData
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 根据数据库类型设置转储表名
|
|
|
|
|
if Backend.isDbms(DBMS.INFORMIX):
|
|
|
|
|
kb.dumpTable = "%s:%s" % (conf.db, tbl)
|
|
|
|
|
elif Backend.isDbms(DBMS.SQLITE):
|
|
|
|
@ -139,6 +176,7 @@ class Entries(object):
|
|
|
|
|
else:
|
|
|
|
|
kb.dumpTable = "%s.%s" % (conf.db, tbl)
|
|
|
|
|
|
|
|
|
|
# 如果未能获取列信息,则跳过当前表
|
|
|
|
|
if safeSQLIdentificatorNaming(conf.db) not in kb.data.cachedColumns or safeSQLIdentificatorNaming(tbl, True) not in kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]:
|
|
|
|
|
warnMsg = "unable to enumerate the columns for table '%s'" % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
if METADB_SUFFIX.upper() not in conf.db.upper():
|
|
|
|
@ -148,12 +186,16 @@ class Entries(object):
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 获取当前表的列信息
|
|
|
|
|
columns = kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]
|
|
|
|
|
# 对列名列表进行排序
|
|
|
|
|
colList = sorted(column for column in columns if column)
|
|
|
|
|
|
|
|
|
|
# 如果指定了排除模式,则从列名列表中排除匹配的列
|
|
|
|
|
if conf.exclude:
|
|
|
|
|
colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
|
|
|
|
|
|
|
|
|
|
# 如果没有可用的列名,则跳过当前表
|
|
|
|
|
if not colList:
|
|
|
|
|
warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
if METADB_SUFFIX.upper() not in conf.db.upper():
|
|
|
|
@ -161,9 +203,11 @@ class Entries(object):
|
|
|
|
|
warnMsg += " (no usable column names)"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 设置全局变量 kb.dumpColumns 为当前表需要转储的列名列表
|
|
|
|
|
kb.dumpColumns = [unsafeSQLIdentificatorNaming(_) for _ in colList]
|
|
|
|
|
# 将列名列表转换为逗号分隔的字符串
|
|
|
|
|
colNames = colString = ','.join(column for column in colList)
|
|
|
|
|
# 获取转储表的根查询
|
|
|
|
|
rootQuery = queries[Backend.getIdentifiedDbms()].dump_table
|
|
|
|
|
|
|
|
|
|
infoMsg = "fetching entries"
|
|
|
|
@ -173,18 +217,22 @@ class Entries(object):
|
|
|
|
|
if METADB_SUFFIX.upper() not in conf.db.upper():
|
|
|
|
|
infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 遍历列名列表,对每个列名进行预处理
|
|
|
|
|
for column in colList:
|
|
|
|
|
_ = agent.preprocessField(tbl, column)
|
|
|
|
|
if _ != column:
|
|
|
|
|
colString = re.sub(r"\b%s\b" % re.escape(column), _.replace("\\", r"\\"), colString)
|
|
|
|
|
|
|
|
|
|
# 初始化条目计数
|
|
|
|
|
entriesCount = 0
|
|
|
|
|
|
|
|
|
|
# 如果存在可用的注入技术,或使用了--direct参数
|
|
|
|
|
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
|
|
|
|
|
# 初始化条目列表
|
|
|
|
|
entries = []
|
|
|
|
|
query = None
|
|
|
|
|
|
|
|
|
|
# 根据数据库类型构建查询语句
|
|
|
|
|
if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL):
|
|
|
|
|
query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())))
|
|
|
|
|
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA):
|
|
|
|
@ -247,9 +295,10 @@ class Entries(object):
|
|
|
|
|
query = rootQuery.inband.query % (colString, conf.db, tbl)
|
|
|
|
|
|
|
|
|
|
query = agent.whereQuery(query)
|
|
|
|
|
|
|
|
|
|
# 如果没有获取到条目,并且查询语句存在,并且没有检测到键盘中断
|
|
|
|
|
if not entries and query and not kb.dumpKeyboardInterrupt:
|
|
|
|
|
try:
|
|
|
|
|
# 执行查询语句,获取条目信息
|
|
|
|
|
entries = inject.getValue(query, blind=False, time=False, dump=True)
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
entries = None
|
|
|
|
@ -257,7 +306,7 @@ class Entries(object):
|
|
|
|
|
clearConsoleLine()
|
|
|
|
|
warnMsg = "Ctrl+C detected in dumping phase"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
# 如果成功获取到条目
|
|
|
|
|
if not isNoneValue(entries):
|
|
|
|
|
if isinstance(entries, six.string_types):
|
|
|
|
|
entries = [entries]
|
|
|
|
@ -266,6 +315,7 @@ class Entries(object):
|
|
|
|
|
|
|
|
|
|
entriesCount = len(entries)
|
|
|
|
|
|
|
|
|
|
# 遍历每个列名和条目,更新转储的表数据信息
|
|
|
|
|
for index, column in enumerate(colList):
|
|
|
|
|
if column not in kb.data.dumpedTable:
|
|
|
|
|
kb.data.dumpedTable[column] = {"length": len(column), "values": BigArray()}
|
|
|
|
@ -273,19 +323,20 @@ class Entries(object):
|
|
|
|
|
for entry in entries:
|
|
|
|
|
if entry is None or len(entry) == 0:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 如果条目是字符串类型
|
|
|
|
|
if isinstance(entry, six.string_types):
|
|
|
|
|
colEntry = entry
|
|
|
|
|
# 否则,获取指定索引的条目
|
|
|
|
|
else:
|
|
|
|
|
colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u''
|
|
|
|
|
|
|
|
|
|
maxLen = max(getConsoleLength(column), getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry))))
|
|
|
|
|
|
|
|
|
|
# 更新最大长度
|
|
|
|
|
if maxLen > kb.data.dumpedTable[column]["length"]:
|
|
|
|
|
kb.data.dumpedTable[column]["length"] = maxLen
|
|
|
|
|
|
|
|
|
|
# 添加条目值
|
|
|
|
|
kb.data.dumpedTable[column]["values"].append(colEntry)
|
|
|
|
|
|
|
|
|
|
# 如果没有转储表数据,并且可以使用盲注方式,且不是直接模式
|
|
|
|
|
if not kb.data.dumpedTable and isInferenceAvailable() and not conf.direct:
|
|
|
|
|
infoMsg = "fetching number of "
|
|
|
|
|
if conf.col:
|
|
|
|
@ -293,7 +344,8 @@ class Entries(object):
|
|
|
|
|
infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 构建盲注获取条目计数的查询语句
|
|
|
|
|
if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL):
|
|
|
|
|
query = rootQuery.blind.count % (tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())))
|
|
|
|
|
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.MAXDB, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA):
|
|
|
|
@ -307,21 +359,22 @@ class Entries(object):
|
|
|
|
|
|
|
|
|
|
query = agent.whereQuery(query)
|
|
|
|
|
|
|
|
|
|
# 执行盲注查询,获取条目计数
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
|
|
|
|
|
|
|
|
|
lengths = {}
|
|
|
|
|
entries = {}
|
|
|
|
|
|
|
|
|
|
# 如果条目计数为0
|
|
|
|
|
if count == 0:
|
|
|
|
|
warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming(conf.db)
|
|
|
|
|
warnMsg += "appears to be empty"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
# 初始化长度和条目
|
|
|
|
|
for column in colList:
|
|
|
|
|
lengths[column] = len(column)
|
|
|
|
|
entries[column] = []
|
|
|
|
|
|
|
|
|
|
# 如果未能获取条目计数
|
|
|
|
|
elif not isNumPosStrValue(count):
|
|
|
|
|
warnMsg = "unable to retrieve the number of "
|
|
|
|
|
if conf.col:
|
|
|
|
@ -331,7 +384,7 @@ class Entries(object):
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 对于特定数据库
|
|
|
|
|
elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL, DBMS.INFORMIX, DBMS.MCKOI, DBMS.RAIMA):
|
|
|
|
|
if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.RAIMA):
|
|
|
|
|
table = tbl
|
|
|
|
@ -339,7 +392,7 @@ class Entries(object):
|
|
|
|
|
table = "%s.%s" % (conf.db, tbl) if conf.db else tbl
|
|
|
|
|
elif Backend.isDbms(DBMS.INFORMIX):
|
|
|
|
|
table = "%s:%s" % (conf.db, tbl) if conf.db else tbl
|
|
|
|
|
|
|
|
|
|
# 如果是mssql并且没有强制透视
|
|
|
|
|
if Backend.isDbms(DBMS.MSSQL) and not conf.forcePivoting:
|
|
|
|
|
warnMsg = "in case of table dumping problems (e.g. column entry order) "
|
|
|
|
|
warnMsg += "you are advised to rerun with '--force-pivoting'"
|
|
|
|
@ -369,7 +422,7 @@ class Entries(object):
|
|
|
|
|
clearConsoleLine()
|
|
|
|
|
warnMsg = "Ctrl+C detected in dumping phase"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
# 如果没有获取到条目,且没有检测到键盘中断
|
|
|
|
|
if not entries and not kb.dumpKeyboardInterrupt:
|
|
|
|
|
try:
|
|
|
|
|
retVal = pivotDumpTable(table, colList, count, blind=True)
|
|
|
|
@ -382,12 +435,12 @@ class Entries(object):
|
|
|
|
|
|
|
|
|
|
if retVal:
|
|
|
|
|
entries, lengths = retVal
|
|
|
|
|
|
|
|
|
|
# 对于其他数据库
|
|
|
|
|
else:
|
|
|
|
|
emptyColumns = []
|
|
|
|
|
plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne)
|
|
|
|
|
|
|
|
|
|
# 如果列的数量小于行数,且大于阈值,则进行空列检查
|
|
|
|
|
if len(colList) < len(indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD:
|
|
|
|
|
debugMsg = "checking for empty columns"
|
|
|
|
|
logger.debug(infoMsg)
|
|
|
|
@ -398,7 +451,7 @@ class Entries(object):
|
|
|
|
|
debugMsg = "column '%s' of table '%s' will not be " % (column, kb.dumpTable)
|
|
|
|
|
debugMsg += "dumped as it appears to be empty"
|
|
|
|
|
logger.debug(debugMsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
for index in indexRange:
|
|
|
|
|
for column in colList:
|
|
|
|
@ -409,7 +462,7 @@ class Entries(object):
|
|
|
|
|
|
|
|
|
|
if column not in entries:
|
|
|
|
|
entries[column] = BigArray()
|
|
|
|
|
|
|
|
|
|
# 根据不同的数据库类型,构建盲注查询语句
|
|
|
|
|
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE):
|
|
|
|
|
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index)
|
|
|
|
|
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,):
|
|
|
|
@ -428,10 +481,10 @@ class Entries(object):
|
|
|
|
|
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index)
|
|
|
|
|
|
|
|
|
|
query = agent.whereQuery(query)
|
|
|
|
|
|
|
|
|
|
# 执行盲注查询,获取值
|
|
|
|
|
value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True)
|
|
|
|
|
value = '' if value is None else value
|
|
|
|
|
|
|
|
|
|
# 更新最大长度和条目值
|
|
|
|
|
lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
|
|
|
|
|
entries[column].append(value)
|
|
|
|
|
|
|
|
|
@ -440,14 +493,15 @@ class Entries(object):
|
|
|
|
|
clearConsoleLine()
|
|
|
|
|
warnMsg = "Ctrl+C detected in dumping phase"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
# 遍历获取到的条目,将结果保存到kb.data.dumpedTable中
|
|
|
|
|
for column, columnEntries in entries.items():
|
|
|
|
|
length = max(lengths[column], len(column))
|
|
|
|
|
|
|
|
|
|
kb.data.dumpedTable[column] = {"length": length, "values": columnEntries}
|
|
|
|
|
|
|
|
|
|
# 获取总行数
|
|
|
|
|
entriesCount = len(columnEntries)
|
|
|
|
|
|
|
|
|
|
# 如果没有转储表数据,或者条目数为0,且有权限标识
|
|
|
|
|
if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag):
|
|
|
|
|
warnMsg = "unable to retrieve the entries "
|
|
|
|
|
if conf.col:
|
|
|
|
@ -456,15 +510,18 @@ class Entries(object):
|
|
|
|
|
warnMsg += "in database '%s'%s" % (unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "")
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
else:
|
|
|
|
|
# 保存转储的信息,包括总行数、表名和数据库名
|
|
|
|
|
kb.data.dumpedTable["__infos__"] = {"count": entriesCount,
|
|
|
|
|
"table": safeSQLIdentificatorNaming(tbl, True),
|
|
|
|
|
"db": safeSQLIdentificatorNaming(conf.db)}
|
|
|
|
|
try:
|
|
|
|
|
# 对转储的数据进行攻击
|
|
|
|
|
attackDumpedTable()
|
|
|
|
|
except (IOError, OSError) as ex:
|
|
|
|
|
errMsg = "an error occurred while attacking "
|
|
|
|
|
errMsg += "table dump ('%s')" % getSafeExString(ex)
|
|
|
|
|
logger.critical(errMsg)
|
|
|
|
|
# 将转储的数据传递给dumper
|
|
|
|
|
conf.dumper.dbTableValues(kb.data.dumpedTable)
|
|
|
|
|
|
|
|
|
|
except SqlmapConnectionException as ex:
|
|
|
|
@ -473,40 +530,46 @@ class Entries(object):
|
|
|
|
|
logger.critical(errMsg)
|
|
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
# 清空全局变量
|
|
|
|
|
kb.dumpColumns = None
|
|
|
|
|
kb.dumpTable = None
|
|
|
|
|
|
|
|
|
|
# 定义dumpAll方法,用于转储所有数据库中的所有表的所有条目
|
|
|
|
|
def dumpAll(self):
|
|
|
|
|
# 如果指定了数据库,但没有指定表,则只转储该数据库下的表
|
|
|
|
|
if conf.db is not None and conf.tbl is None:
|
|
|
|
|
self.dumpTable()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 如果是MySQL数据库,且没有information_schema
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
|
|
|
|
|
errMsg = "information_schema not available, "
|
|
|
|
|
errMsg += "back-end DBMS is MySQL < 5.0"
|
|
|
|
|
raise SqlmapUnsupportedFeatureException(errMsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
infoMsg = "sqlmap will dump entries of all tables from all databases now"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表和列的全局变量
|
|
|
|
|
conf.tbl = None
|
|
|
|
|
conf.col = None
|
|
|
|
|
|
|
|
|
|
# 获取所有表
|
|
|
|
|
self.getTables()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 如果有缓存的表信息
|
|
|
|
|
if kb.data.cachedTables:
|
|
|
|
|
if isinstance(kb.data.cachedTables, list):
|
|
|
|
|
kb.data.cachedTables = {None: kb.data.cachedTables}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 遍历数据库和表
|
|
|
|
|
for db, tables in kb.data.cachedTables.items():
|
|
|
|
|
conf.db = db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for table in tables:
|
|
|
|
|
if conf.exclude and re.search(conf.exclude, table, re.I) is not None:
|
|
|
|
|
infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table)
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
conf.tbl = table
|
|
|
|
|
kb.data.cachedColumns = {}
|
|
|
|
@ -517,45 +580,57 @@ class Entries(object):
|
|
|
|
|
infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table)
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 定义dumpFoundColumn方法,用于转储已发现的列
|
|
|
|
|
def dumpFoundColumn(self, dbs, foundCols, colConsider):
|
|
|
|
|
message = "do you want to dump found column(s) entries? [Y/n] "
|
|
|
|
|
|
|
|
|
|
# 询问用户是否要转储已发现的列
|
|
|
|
|
if not readInput(message, default='Y', boolean=True):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
dumpFromDbs = []
|
|
|
|
|
message = "which database(s)?\n[a]ll (default)\n"
|
|
|
|
|
message = "which database(s)?\
|
|
|
|
|
[a]ll (default)\
|
|
|
|
|
"
|
|
|
|
|
|
|
|
|
|
# 构建数据库选项
|
|
|
|
|
for db, tblData in dbs.items():
|
|
|
|
|
if tblData:
|
|
|
|
|
message += "[%s]\n" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
message += "[%s]\
|
|
|
|
|
" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
|
|
|
|
|
message += "[q]uit"
|
|
|
|
|
# 接收用户选择
|
|
|
|
|
choice = readInput(message, default='a')
|
|
|
|
|
|
|
|
|
|
# 处理用户选择
|
|
|
|
|
if not choice or choice in ('a', 'A'):
|
|
|
|
|
dumpFromDbs = list(dbs.keys())
|
|
|
|
|
elif choice in ('q', 'Q'):
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
dumpFromDbs = choice.replace(" ", "").split(',')
|
|
|
|
|
|
|
|
|
|
# 遍历数据库
|
|
|
|
|
for db, tblData in dbs.items():
|
|
|
|
|
if db not in dumpFromDbs or not tblData:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
conf.db = db
|
|
|
|
|
dumpFromTbls = []
|
|
|
|
|
message = "which table(s) of database '%s'?\n" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
message += "[a]ll (default)\n"
|
|
|
|
|
message = "which table(s) of database '%s'?\
|
|
|
|
|
" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
message += "[a]ll (default)\
|
|
|
|
|
"
|
|
|
|
|
|
|
|
|
|
# 构建表选项
|
|
|
|
|
for tbl in tblData:
|
|
|
|
|
message += "[%s]\n" % tbl
|
|
|
|
|
message += "[%s]\
|
|
|
|
|
" % tbl
|
|
|
|
|
|
|
|
|
|
message += "[s]kip\n"
|
|
|
|
|
message += "[s]kip\
|
|
|
|
|
"
|
|
|
|
|
message += "[q]uit"
|
|
|
|
|
# 接收用户选择
|
|
|
|
|
choice = readInput(message, default='a')
|
|
|
|
|
|
|
|
|
|
# 处理用户选择
|
|
|
|
|
if not choice or choice in ('a', 'A'):
|
|
|
|
|
dumpFromTbls = tblData
|
|
|
|
|
elif choice in ('s', 'S'):
|
|
|
|
@ -564,80 +639,120 @@ class Entries(object):
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
dumpFromTbls = choice.replace(" ", "").split(',')
|
|
|
|
|
|
|
|
|
|
# 遍历表
|
|
|
|
|
for table, columns in tblData.items():
|
|
|
|
|
if table not in dumpFromTbls:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
conf.tbl = table
|
|
|
|
|
colList = [_ for _ in columns if _]
|
|
|
|
|
|
|
|
|
|
# 如果指定了排除模式,则排除匹配的列
|
|
|
|
|
if conf.exclude:
|
|
|
|
|
colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
|
|
|
|
|
|
|
|
|
|
# 设置需要转储的列
|
|
|
|
|
conf.col = ','.join(colList)
|
|
|
|
|
kb.data.cachedColumns = {}
|
|
|
|
|
kb.data.dumpedTable = {}
|
|
|
|
|
|
|
|
|
|
data = self.dumpTable(dbs)
|
|
|
|
|
|
|
|
|
|
# 如果成功转储了数据,则传递给dumper
|
|
|
|
|
if data:
|
|
|
|
|
conf.dumper.dbTableValues(data)
|
|
|
|
|
|
|
|
|
|
def dumpFoundTables(self, tables):
|
|
|
|
|
# 1. 定义一个消息,询问用户是否要转储发现的表条目
|
|
|
|
|
message = "do you want to dump found table(s) entries? [Y/n] "
|
|
|
|
|
|
|
|
|
|
# 2. 使用 readInput 函数获取用户输入,默认值为 'Y' (是),并将其转换为布尔值
|
|
|
|
|
# 如果用户输入 'n' (否) 或者其他非 'y' 的值,则返回 False,否则返回 True。
|
|
|
|
|
# 如果用户输入为否,则直接返回,不进行后续的转储操作
|
|
|
|
|
if not readInput(message, default='Y', boolean=True):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 3. 初始化一个空列表 dumpFromDbs,用于存储用户选择要转储的数据库
|
|
|
|
|
dumpFromDbs = []
|
|
|
|
|
message = "which database(s)?\n[a]ll (default)\n"
|
|
|
|
|
# 4. 构建一个消息,用于提示用户选择要转储的数据库,其中 [a]ll (default) 表示默认选择全部数据库
|
|
|
|
|
message = "which database(s)?\
|
|
|
|
|
[a]ll (default)\
|
|
|
|
|
"
|
|
|
|
|
|
|
|
|
|
# 5. 遍历传入的 tables 字典,该字典的键是数据库名,值是该数据库下的表列表
|
|
|
|
|
for db, tablesList in tables.items():
|
|
|
|
|
# 6. 如果该数据库有表,则将数据库名添加到消息中,并进行安全 SQL 标识符命名处理
|
|
|
|
|
if tablesList:
|
|
|
|
|
message += "[%s]\n" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
message += "[%s]\
|
|
|
|
|
" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
|
|
|
|
|
# 7. 在消息中添加一个选项 [q]uit,允许用户退出
|
|
|
|
|
message += "[q]uit"
|
|
|
|
|
# 8. 使用 readInput 函数获取用户输入,默认值为 'a',表示选择全部数据库
|
|
|
|
|
choice = readInput(message, default='a')
|
|
|
|
|
|
|
|
|
|
# 9. 如果用户没有输入或者输入为 'a' (或 'A'),则将所有数据库添加到 dumpFromDbs 列表中
|
|
|
|
|
if not choice or choice.lower() == 'a':
|
|
|
|
|
dumpFromDbs = list(tables.keys())
|
|
|
|
|
# 10. 如果用户输入为 'q' (或 'Q'),则直接返回,不进行后续的转储操作
|
|
|
|
|
elif choice.lower() == 'q':
|
|
|
|
|
return
|
|
|
|
|
# 11. 否则,将用户输入的数据库名按逗号分割,并添加到 dumpFromDbs 列表中
|
|
|
|
|
else:
|
|
|
|
|
dumpFromDbs = choice.replace(" ", "").split(',')
|
|
|
|
|
|
|
|
|
|
# 12. 遍历 tables 字典,键是数据库名,值是该数据库下的表列表
|
|
|
|
|
for db, tablesList in tables.items():
|
|
|
|
|
# 13. 如果当前数据库不在 dumpFromDbs 列表中,或者当前数据库没有表,则跳过该数据库
|
|
|
|
|
if db not in dumpFromDbs or not tablesList:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 14. 将当前数据库名赋值给 conf.db (全局配置参数)
|
|
|
|
|
conf.db = db
|
|
|
|
|
# 15. 初始化一个空列表 dumpFromTbls,用于存储用户选择要转储的表
|
|
|
|
|
dumpFromTbls = []
|
|
|
|
|
message = "which table(s) of database '%s'?\n" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
message += "[a]ll (default)\n"
|
|
|
|
|
|
|
|
|
|
# 16. 构建一个消息,用于提示用户选择当前数据库下要转储的表
|
|
|
|
|
message = "which table(s) of database '%s'?\
|
|
|
|
|
" % unsafeSQLIdentificatorNaming(db)
|
|
|
|
|
# 17. 在消息中添加一个选项 [a]ll (default),表示默认选择全部表
|
|
|
|
|
message += "[a]ll (default)\
|
|
|
|
|
"
|
|
|
|
|
|
|
|
|
|
# 18. 遍历当前数据库下的表列表,将表名添加到消息中,并进行安全 SQL 标识符命名处理
|
|
|
|
|
for tbl in tablesList:
|
|
|
|
|
message += "[%s]\n" % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
message += "[%s]\
|
|
|
|
|
" % unsafeSQLIdentificatorNaming(tbl)
|
|
|
|
|
|
|
|
|
|
message += "[s]kip\n"
|
|
|
|
|
# 19. 在消息中添加一个选项 [s]kip,允许用户跳过当前数据库的表
|
|
|
|
|
message += "[s]kip\
|
|
|
|
|
"
|
|
|
|
|
# 20. 在消息中添加一个选项 [q]uit,允许用户退出
|
|
|
|
|
message += "[q]uit"
|
|
|
|
|
# 21. 使用 readInput 函数获取用户输入,默认值为 'a',表示选择全部表
|
|
|
|
|
choice = readInput(message, default='a')
|
|
|
|
|
|
|
|
|
|
# 22. 如果用户没有输入或者输入为 'a' (或 'A'),则将所有表添加到 dumpFromTbls 列表中
|
|
|
|
|
if not choice or choice.lower() == 'a':
|
|
|
|
|
dumpFromTbls = tablesList
|
|
|
|
|
# 23. 如果用户输入为 's' (或 'S'),则跳过当前数据库的表,继续处理下一个数据库
|
|
|
|
|
elif choice.lower() == 's':
|
|
|
|
|
continue
|
|
|
|
|
# 24. 如果用户输入为 'q' (或 'Q'),则直接返回,不进行后续的转储操作
|
|
|
|
|
elif choice.lower() == 'q':
|
|
|
|
|
return
|
|
|
|
|
# 25. 否则,将用户输入的表名按逗号分割,并添加到 dumpFromTbls 列表中
|
|
|
|
|
else:
|
|
|
|
|
dumpFromTbls = choice.replace(" ", "").split(',')
|
|
|
|
|
|
|
|
|
|
# 26. 遍历 dumpFromTbls 列表,该列表存储当前数据库下要转储的表名
|
|
|
|
|
for table in dumpFromTbls:
|
|
|
|
|
# 27. 将当前表名赋值给 conf.tbl (全局配置参数)
|
|
|
|
|
conf.tbl = table
|
|
|
|
|
# 28. 清空 kb.data.cachedColumns (全局配置参数) 缓存的列信息
|
|
|
|
|
kb.data.cachedColumns = {}
|
|
|
|
|
# 29. 清空 kb.data.dumpedTable (全局配置参数) 缓存的表数据
|
|
|
|
|
kb.data.dumpedTable = {}
|
|
|
|
|
|
|
|
|
|
# 30. 调用 self.dumpTable() 函数,转储当前表的数据
|
|
|
|
|
data = self.dumpTable()
|
|
|
|
|
|
|
|
|
|
# 31. 如果转储成功 (data 不为空),则将转储的数据传递给 conf.dumper.dbTableValues() 函数进行后续处理
|
|
|
|
|
if data:
|
|
|
|
|
conf.dumper.dbTableValues(data)
|
|
|
|
|
conf.dumper.dbTableValues(data)
|