|
|
|
@ -52,69 +52,100 @@ from thirdparty.six.moves import zip as _zip
|
|
|
|
|
class Users(object):
|
|
|
|
|
"""
|
|
|
|
|
This class defines users' enumeration functionalities for plugins.
|
|
|
|
|
这个类定义了插件的用户枚举功能。
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
kb.data.currentUser = ""
|
|
|
|
|
kb.data.isDba = None
|
|
|
|
|
kb.data.cachedUsers = []
|
|
|
|
|
kb.data.cachedUsersPasswords = {}
|
|
|
|
|
kb.data.cachedUsersPrivileges = {}
|
|
|
|
|
kb.data.cachedUsersRoles = {}
|
|
|
|
|
# 初始化用户相关的数据存储
|
|
|
|
|
kb.data.currentUser = "" # 当前用户
|
|
|
|
|
kb.data.isDba = None # 是否是DBA
|
|
|
|
|
kb.data.cachedUsers = [] # 缓存的用户列表
|
|
|
|
|
kb.data.cachedUsersPasswords = {} # 缓存的用户密码哈希
|
|
|
|
|
kb.data.cachedUsersPrivileges = {} # 缓存的用户权限
|
|
|
|
|
kb.data.cachedUsersRoles = {} # 缓存的用户角色
|
|
|
|
|
|
|
|
|
|
def getCurrentUser(self):
|
|
|
|
|
"""
|
|
|
|
|
Retrieves the current database user.
|
|
|
|
|
获取当前数据库用户
|
|
|
|
|
"""
|
|
|
|
|
infoMsg = "fetching current user"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 获取当前用户的SQL查询语句
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].current_user.query
|
|
|
|
|
|
|
|
|
|
# 如果当前用户没有被获取过,则进行获取
|
|
|
|
|
if not kb.data.currentUser:
|
|
|
|
|
kb.data.currentUser = unArrayizeValue(inject.getValue(query))
|
|
|
|
|
|
|
|
|
|
return kb.data.currentUser
|
|
|
|
|
|
|
|
|
|
def isDba(self, user=None):
|
|
|
|
|
"""
|
|
|
|
|
Tests if the current or specified user is a DBA.
|
|
|
|
|
测试当前或指定用户是否是DBA(数据库管理员)。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
user (str, optional): 要测试的用户,默认为None,表示测试当前用户
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: 是否是DBA
|
|
|
|
|
"""
|
|
|
|
|
infoMsg = "testing if current user is DBA"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
query = None
|
|
|
|
|
|
|
|
|
|
# 根据不同的数据库类型,构造不同的SQL查询语句
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL):
|
|
|
|
|
self.getCurrentUser()
|
|
|
|
|
self.getCurrentUser() # 先获取当前用户
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE):
|
|
|
|
|
kb.data.isDba = "root" in (kb.data.currentUser or "")
|
|
|
|
|
kb.data.isDba = "root" in (kb.data.currentUser or "") # Drizzle数据库,通过用户名判断是否为root用户
|
|
|
|
|
elif kb.data.currentUser:
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].is_dba.query % kb.data.currentUser.split("@")[0]
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].is_dba.query % kb.data.currentUser.split("@")[0] # 构建查询语句,判断是否为MySQL的DBA
|
|
|
|
|
elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) and user is not None:
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].is_dba.query2 % user
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].is_dba.query2 % user # 构建查询语句,判断是否为SQL Server或Sybase的DBA
|
|
|
|
|
else:
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].is_dba.query
|
|
|
|
|
query = queries[Backend.getIdentifiedDbms()].is_dba.query # 构建查询语句,判断是否为其他数据库的DBA
|
|
|
|
|
|
|
|
|
|
# 执行查询
|
|
|
|
|
if query:
|
|
|
|
|
query = agent.forgeCaseStatement(query)
|
|
|
|
|
kb.data.isDba = inject.checkBooleanExpression(query) or False
|
|
|
|
|
query = agent.forgeCaseStatement(query) # 注入时,构造Case语句
|
|
|
|
|
kb.data.isDba = inject.checkBooleanExpression(query) or False # 执行查询,并判断是否为DBA
|
|
|
|
|
|
|
|
|
|
return kb.data.isDba
|
|
|
|
|
|
|
|
|
|
def getUsers(self):
|
|
|
|
|
"""
|
|
|
|
|
Retrieves database users.
|
|
|
|
|
获取数据库用户。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
list: 用户列表
|
|
|
|
|
"""
|
|
|
|
|
infoMsg = "fetching database users"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 获取查询用户表的SQL查询语句
|
|
|
|
|
rootQuery = queries[Backend.getIdentifiedDbms()].users
|
|
|
|
|
|
|
|
|
|
# 判断是否需要使用不同的查询语句
|
|
|
|
|
condition = (Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")))
|
|
|
|
|
condition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema)
|
|
|
|
|
|
|
|
|
|
# 优先使用union, error, query技术进行查询,否则使用盲注技术
|
|
|
|
|
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE):
|
|
|
|
|
query = rootQuery.inband.query3
|
|
|
|
|
query = rootQuery.inband.query3 # Drizzle数据库的查询语句
|
|
|
|
|
elif condition:
|
|
|
|
|
query = rootQuery.inband.query2
|
|
|
|
|
query = rootQuery.inband.query2 # 条件判断下的查询语句
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.inband.query
|
|
|
|
|
query = rootQuery.inband.query # 通用查询语句
|
|
|
|
|
|
|
|
|
|
values = inject.getValue(query, blind=False, time=False)
|
|
|
|
|
values = inject.getValue(query, blind=False, time=False) # 执行查询语句,获取用户列表
|
|
|
|
|
|
|
|
|
|
# 处理返回的用户列表
|
|
|
|
|
if not isNoneValue(values):
|
|
|
|
|
kb.data.cachedUsers = []
|
|
|
|
|
for value in arrayizeValue(values):
|
|
|
|
@ -122,18 +153,19 @@ class Users(object):
|
|
|
|
|
if not isNoneValue(value):
|
|
|
|
|
kb.data.cachedUsers.append(value)
|
|
|
|
|
|
|
|
|
|
# 如果没有使用union, error, query技术获取到用户,则使用盲注技术进行获取
|
|
|
|
|
if not kb.data.cachedUsers and isInferenceAvailable() and not conf.direct:
|
|
|
|
|
infoMsg = "fetching number of database users"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE):
|
|
|
|
|
query = rootQuery.blind.count3
|
|
|
|
|
query = rootQuery.blind.count3 # Drizzle数据库的查询语句
|
|
|
|
|
elif condition:
|
|
|
|
|
query = rootQuery.blind.count2
|
|
|
|
|
query = rootQuery.blind.count2 # 条件判断下的查询语句
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.blind.count
|
|
|
|
|
query = rootQuery.blind.count # 通用查询语句
|
|
|
|
|
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取用户数量
|
|
|
|
|
|
|
|
|
|
if count == 0:
|
|
|
|
|
return kb.data.cachedUsers
|
|
|
|
@ -142,8 +174,9 @@ class Users(object):
|
|
|
|
|
raise SqlmapNoneDataException(errMsg)
|
|
|
|
|
|
|
|
|
|
plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne)
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne) # 计算盲注的查询范围
|
|
|
|
|
|
|
|
|
|
# 循环盲注查询用户
|
|
|
|
|
for index in indexRange:
|
|
|
|
|
if Backend.getIdentifiedDbms() in (DBMS.SYBASE, DBMS.MAXDB):
|
|
|
|
|
query = rootQuery.blind.query % (kb.data.cachedUsers[-1] if kb.data.cachedUsers else " ")
|
|
|
|
@ -166,8 +199,16 @@ class Users(object):
|
|
|
|
|
return kb.data.cachedUsers
|
|
|
|
|
|
|
|
|
|
def getPasswordHashes(self):
|
|
|
|
|
"""
|
|
|
|
|
Retrieves password hashes of database users.
|
|
|
|
|
获取数据库用户的密码哈希值。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
dict: 用户名和密码哈希的字典
|
|
|
|
|
"""
|
|
|
|
|
infoMsg = "fetching database users password hashes"
|
|
|
|
|
|
|
|
|
|
# 获取查询密码哈希的SQL查询语句
|
|
|
|
|
rootQuery = queries[Backend.getIdentifiedDbms()].passwords
|
|
|
|
|
|
|
|
|
|
if conf.user == CURRENT_USER:
|
|
|
|
@ -177,7 +218,7 @@ class Users(object):
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
if conf.user and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
|
|
|
|
|
conf.user = conf.user.upper()
|
|
|
|
|
conf.user = conf.user.upper() # Oracle和DB2数据库的用户名为大写
|
|
|
|
|
|
|
|
|
|
if conf.user:
|
|
|
|
|
users = conf.user.split(',')
|
|
|
|
@ -187,28 +228,31 @@ class Users(object):
|
|
|
|
|
parsedUser = re.search(r"['\"]?(.*?)['\"]?\@", user)
|
|
|
|
|
|
|
|
|
|
if parsedUser:
|
|
|
|
|
users[users.index(user)] = parsedUser.groups()[0]
|
|
|
|
|
users[users.index(user)] = parsedUser.groups()[0] # 处理MySQL的用户名格式,去掉引号和@后面的部分
|
|
|
|
|
else:
|
|
|
|
|
users = []
|
|
|
|
|
|
|
|
|
|
users = [_ for _ in users if _]
|
|
|
|
|
|
|
|
|
|
# 优先使用union, error, query技术进行查询,否则使用盲注技术
|
|
|
|
|
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
|
|
|
|
|
if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")):
|
|
|
|
|
query = rootQuery.inband.query2
|
|
|
|
|
query = rootQuery.inband.query2 # SQL Server 2005和2008的查询语句
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.inband.query
|
|
|
|
|
query = rootQuery.inband.query # 通用查询语句
|
|
|
|
|
|
|
|
|
|
condition = rootQuery.inband.condition
|
|
|
|
|
condition = rootQuery.inband.condition # 查询条件
|
|
|
|
|
|
|
|
|
|
# 如果指定了用户,则加入查询条件
|
|
|
|
|
if conf.user:
|
|
|
|
|
query += " WHERE "
|
|
|
|
|
query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users))
|
|
|
|
|
|
|
|
|
|
# 处理Sybase数据库的特殊情况
|
|
|
|
|
if Backend.isDbms(DBMS.SYBASE):
|
|
|
|
|
getCurrentThreadData().disableStdOut = True
|
|
|
|
|
|
|
|
|
|
retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=False)
|
|
|
|
|
retVal = pivotDumpTable("(%s) AS %s" % (query, kb.aliasName), ['%s.name' % kb.aliasName, '%s.password' % kb.aliasName], blind=False) # 使用pivotDumpTable函数获取用户名和密码哈希
|
|
|
|
|
|
|
|
|
|
if retVal:
|
|
|
|
|
for user, password in filterPairValues(_zip(retVal[0]["%s.name" % kb.aliasName], retVal[0]["%s.password" % kb.aliasName])):
|
|
|
|
@ -219,13 +263,14 @@ class Users(object):
|
|
|
|
|
|
|
|
|
|
getCurrentThreadData().disableStdOut = False
|
|
|
|
|
else:
|
|
|
|
|
values = inject.getValue(query, blind=False, time=False)
|
|
|
|
|
values = inject.getValue(query, blind=False, time=False) # 执行查询,获取用户名和密码哈希
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MSSQL) and isNoneValue(values):
|
|
|
|
|
values = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), blind=False, time=False)
|
|
|
|
|
values = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), blind=False, time=False) # SQL Server的特殊情况,替换函数
|
|
|
|
|
elif Backend.isDbms(DBMS.MYSQL) and (isNoneValue(values) or all(len(value) == 2 and (isNullValue(value[1]) or isNoneValue(value[1])) for value in values)):
|
|
|
|
|
values = inject.getValue(query.replace("authentication_string", "password"), blind=False, time=False)
|
|
|
|
|
values = inject.getValue(query.replace("authentication_string", "password"), blind=False, time=False) # MySQL的特殊情况,替换字段
|
|
|
|
|
|
|
|
|
|
# 处理返回的用户名和密码哈希
|
|
|
|
|
for user, password in filterPairValues(values):
|
|
|
|
|
if not user or user == " ":
|
|
|
|
|
continue
|
|
|
|
@ -237,19 +282,21 @@ class Users(object):
|
|
|
|
|
else:
|
|
|
|
|
kb.data.cachedUsersPasswords[user].append(password)
|
|
|
|
|
|
|
|
|
|
# 如果没有使用union, error, query技术获取到密码哈希,则使用盲注技术进行获取
|
|
|
|
|
if not kb.data.cachedUsersPasswords and isInferenceAvailable() and not conf.direct:
|
|
|
|
|
fallback = False
|
|
|
|
|
|
|
|
|
|
if not len(users):
|
|
|
|
|
users = self.getUsers()
|
|
|
|
|
users = self.getUsers() # 先获取用户列表
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL):
|
|
|
|
|
for user in users:
|
|
|
|
|
parsedUser = re.search(r"['\"]?(.*?)['\"]?\@", user)
|
|
|
|
|
|
|
|
|
|
if parsedUser:
|
|
|
|
|
users[users.index(user)] = parsedUser.groups()[0]
|
|
|
|
|
users[users.index(user)] = parsedUser.groups()[0] # 处理MySQL的用户名格式,去掉引号和@后面的部分
|
|
|
|
|
|
|
|
|
|
# 处理Sybase数据库的特殊情况
|
|
|
|
|
if Backend.isDbms(DBMS.SYBASE):
|
|
|
|
|
getCurrentThreadData().disableStdOut = True
|
|
|
|
|
|
|
|
|
@ -268,8 +315,9 @@ class Users(object):
|
|
|
|
|
|
|
|
|
|
getCurrentThreadData().disableStdOut = False
|
|
|
|
|
else:
|
|
|
|
|
retrievedUsers = set()
|
|
|
|
|
retrievedUsers = set() # 已获取密码哈希的用户
|
|
|
|
|
|
|
|
|
|
# 循环盲注查询密码哈希
|
|
|
|
|
for user in users:
|
|
|
|
|
user = unArrayizeValue(user)
|
|
|
|
|
|
|
|
|
@ -277,26 +325,26 @@ class Users(object):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO):
|
|
|
|
|
count = 1
|
|
|
|
|
count = 1 # Informix和Virtuoso数据库的特殊情况,直接查询密码哈希
|
|
|
|
|
else:
|
|
|
|
|
infoMsg = "fetching number of password hashes "
|
|
|
|
|
infoMsg += "for user '%s'" % user
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")):
|
|
|
|
|
query = rootQuery.blind.count2 % user
|
|
|
|
|
query = rootQuery.blind.count2 % user # SQL Server 2005和2008的查询语句
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.blind.count % user
|
|
|
|
|
query = rootQuery.blind.count % user # 通用查询语句
|
|
|
|
|
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取密码哈希数量
|
|
|
|
|
|
|
|
|
|
if not isNumPosStrValue(count):
|
|
|
|
|
if Backend.isDbms(DBMS.MSSQL):
|
|
|
|
|
fallback = True
|
|
|
|
|
count = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
|
|
|
|
count = inject.getValue(query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # SQL Server的特殊情况,替换函数
|
|
|
|
|
elif Backend.isDbms(DBMS.MYSQL):
|
|
|
|
|
fallback = True
|
|
|
|
|
count = inject.getValue(query.replace("authentication_string", "password"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
|
|
|
|
count = inject.getValue(query.replace("authentication_string", "password"), union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # MySQL的特殊情况,替换字段
|
|
|
|
|
|
|
|
|
|
if not isNumPosStrValue(count):
|
|
|
|
|
warnMsg = "unable to retrieve the number of password "
|
|
|
|
@ -310,33 +358,34 @@ class Users(object):
|
|
|
|
|
passwords = []
|
|
|
|
|
|
|
|
|
|
plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne)
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne) # 计算盲注的查询范围
|
|
|
|
|
|
|
|
|
|
# 循环盲注查询密码哈希
|
|
|
|
|
for index in indexRange:
|
|
|
|
|
if Backend.isDbms(DBMS.MSSQL):
|
|
|
|
|
if Backend.isVersionWithin(("2005", "2008")):
|
|
|
|
|
query = rootQuery.blind.query2 % (user, index, user)
|
|
|
|
|
query = rootQuery.blind.query2 % (user, index, user) # SQL Server 2005和2008的查询语句
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.blind.query % (user, index, user)
|
|
|
|
|
query = rootQuery.blind.query % (user, index, user) # 通用查询语句
|
|
|
|
|
|
|
|
|
|
if fallback:
|
|
|
|
|
query = query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr")
|
|
|
|
|
query = query.replace("master.dbo.fn_varbintohexstr", "sys.fn_sqlvarbasetostr") # SQL Server的特殊情况,替换函数
|
|
|
|
|
|
|
|
|
|
elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO):
|
|
|
|
|
query = rootQuery.blind.query % (user,)
|
|
|
|
|
query = rootQuery.blind.query % (user,) # Informix和Virtuoso数据库的特殊情况
|
|
|
|
|
|
|
|
|
|
elif Backend.isDbms(DBMS.HSQLDB):
|
|
|
|
|
query = rootQuery.blind.query % (index, user)
|
|
|
|
|
query = rootQuery.blind.query % (index, user) # HSQLDB数据库的特殊情况
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.blind.query % (user, index)
|
|
|
|
|
query = rootQuery.blind.query % (user, index) # 通用查询语句
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL):
|
|
|
|
|
if fallback:
|
|
|
|
|
query = query.replace("authentication_string", "password")
|
|
|
|
|
query = query.replace("authentication_string", "password") # MySQL的特殊情况,替换字段
|
|
|
|
|
|
|
|
|
|
password = unArrayizeValue(inject.getValue(query, union=False, error=False))
|
|
|
|
|
password = parsePasswordHash(password)
|
|
|
|
|
password = parsePasswordHash(password) # 解析密码哈希
|
|
|
|
|
|
|
|
|
|
passwords.append(password)
|
|
|
|
|
|
|
|
|
@ -355,26 +404,37 @@ class Users(object):
|
|
|
|
|
logger.error(errMsg)
|
|
|
|
|
else:
|
|
|
|
|
for user in kb.data.cachedUsersPasswords:
|
|
|
|
|
kb.data.cachedUsersPasswords[user] = list(set(kb.data.cachedUsersPasswords[user]))
|
|
|
|
|
kb.data.cachedUsersPasswords[user] = list(set(kb.data.cachedUsersPasswords[user])) # 去重密码哈希
|
|
|
|
|
|
|
|
|
|
storeHashesToFile(kb.data.cachedUsersPasswords)
|
|
|
|
|
storeHashesToFile(kb.data.cachedUsersPasswords) # 保存密码哈希到文件
|
|
|
|
|
|
|
|
|
|
message = "do you want to perform a dictionary-based attack "
|
|
|
|
|
message += "against retrieved password hashes? [Y/n/q]"
|
|
|
|
|
choice = readInput(message, default='Y').upper()
|
|
|
|
|
choice = readInput(message, default='Y').upper() # 提示是否进行字典攻击
|
|
|
|
|
|
|
|
|
|
if choice == 'N':
|
|
|
|
|
pass
|
|
|
|
|
elif choice == 'Q':
|
|
|
|
|
raise SqlmapUserQuitException
|
|
|
|
|
else:
|
|
|
|
|
attackCachedUsersPasswords()
|
|
|
|
|
attackCachedUsersPasswords() # 进行字典攻击
|
|
|
|
|
|
|
|
|
|
return kb.data.cachedUsersPasswords
|
|
|
|
|
|
|
|
|
|
def getPrivileges(self, query2=False):
|
|
|
|
|
"""
|
|
|
|
|
Retrieves privileges of database users.
|
|
|
|
|
获取数据库用户的权限
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
query2 (bool, optional): 是否使用第二种查询方式,默认为False
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: 用户名和权限的字典,以及DBA用户的集合
|
|
|
|
|
"""
|
|
|
|
|
infoMsg = "fetching database users privileges"
|
|
|
|
|
|
|
|
|
|
# 获取查询权限的SQL查询语句
|
|
|
|
|
rootQuery = queries[Backend.getIdentifiedDbms()].privileges
|
|
|
|
|
|
|
|
|
|
if conf.user == CURRENT_USER:
|
|
|
|
@ -401,36 +461,38 @@ class Users(object):
|
|
|
|
|
users = [_ for _ in users if _]
|
|
|
|
|
|
|
|
|
|
# Set containing the list of DBMS administrators
|
|
|
|
|
areAdmins = set()
|
|
|
|
|
areAdmins = set() # 存储DBA用户的集合
|
|
|
|
|
|
|
|
|
|
if not kb.data.cachedUsersPrivileges and any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
|
|
|
|
|
query = rootQuery.inband.query2
|
|
|
|
|
condition = rootQuery.inband.condition2
|
|
|
|
|
query = rootQuery.inband.query2 # MySQL 5.0以下版本的查询语句
|
|
|
|
|
condition = rootQuery.inband.condition2 # MySQL 5.0以下版本的查询条件
|
|
|
|
|
elif Backend.isDbms(DBMS.ORACLE) and query2:
|
|
|
|
|
query = rootQuery.inband.query2
|
|
|
|
|
condition = rootQuery.inband.condition2
|
|
|
|
|
query = rootQuery.inband.query2 # Oracle的第二种查询方式
|
|
|
|
|
condition = rootQuery.inband.condition2 # Oracle的第二种查询条件
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.inband.query
|
|
|
|
|
condition = rootQuery.inband.condition
|
|
|
|
|
query = rootQuery.inband.query # 通用查询语句
|
|
|
|
|
condition = rootQuery.inband.condition # 通用查询条件
|
|
|
|
|
|
|
|
|
|
# 如果指定了用户,则加入查询条件
|
|
|
|
|
if conf.user:
|
|
|
|
|
query += " WHERE "
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema:
|
|
|
|
|
query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users))
|
|
|
|
|
query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) # MySQL 5.0以上版本的查询条件
|
|
|
|
|
else:
|
|
|
|
|
query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users))
|
|
|
|
|
query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) # 通用查询条件
|
|
|
|
|
|
|
|
|
|
values = inject.getValue(query, blind=False, time=False)
|
|
|
|
|
values = inject.getValue(query, blind=False, time=False) # 执行查询语句,获取权限信息
|
|
|
|
|
|
|
|
|
|
if not values and Backend.isDbms(DBMS.ORACLE) and not query2:
|
|
|
|
|
infoMsg = "trying with table 'USER_SYS_PRIVS'"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
return self.getPrivileges(query2=True)
|
|
|
|
|
return self.getPrivileges(query2=True) # 如果没有获取到权限信息,尝试使用第二种查询方式
|
|
|
|
|
|
|
|
|
|
if not isNoneValue(values):
|
|
|
|
|
# 处理返回的权限信息
|
|
|
|
|
for value in values:
|
|
|
|
|
user = None
|
|
|
|
|
privileges = set()
|
|
|
|
@ -438,7 +500,7 @@ class Users(object):
|
|
|
|
|
for count in xrange(0, len(value or [])):
|
|
|
|
|
# The first column is always the username
|
|
|
|
|
if count == 0:
|
|
|
|
|
user = value[count]
|
|
|
|
|
user = value[count] # 获取用户名
|
|
|
|
|
|
|
|
|
|
# The other columns are the privileges
|
|
|
|
|
else:
|
|
|
|
@ -451,23 +513,23 @@ class Users(object):
|
|
|
|
|
# True, 0 otherwise
|
|
|
|
|
if Backend.isDbms(DBMS.PGSQL) and getUnicode(privilege).isdigit():
|
|
|
|
|
if int(privilege) == 1 and count in PGSQL_PRIVS:
|
|
|
|
|
privileges.add(PGSQL_PRIVS[count])
|
|
|
|
|
privileges.add(PGSQL_PRIVS[count]) # PostgreSQL的权限处理
|
|
|
|
|
|
|
|
|
|
# In MySQL >= 5.0 and Oracle we get the list
|
|
|
|
|
# of privileges as string
|
|
|
|
|
elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema) or Backend.getIdentifiedDbms() in (DBMS.VERTICA, DBMS.MIMERSQL, DBMS.CUBRID):
|
|
|
|
|
privileges.add(privilege)
|
|
|
|
|
privileges.add(privilege) # MySQL 5.0以上版本和Oracle的权限处理
|
|
|
|
|
|
|
|
|
|
# In MySQL < 5.0 we get Y if the privilege is
|
|
|
|
|
# True, N otherwise
|
|
|
|
|
elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
|
|
|
|
|
if privilege.upper() == 'Y':
|
|
|
|
|
privileges.add(MYSQL_PRIVS[count])
|
|
|
|
|
privileges.add(MYSQL_PRIVS[count]) # MySQL 5.0以下版本的权限处理
|
|
|
|
|
|
|
|
|
|
# In Firebird we get one letter for each privilege
|
|
|
|
|
elif Backend.isDbms(DBMS.FIREBIRD):
|
|
|
|
|
if privilege.strip() in FIREBIRD_PRIVS:
|
|
|
|
|
privileges.add(FIREBIRD_PRIVS[privilege.strip()])
|
|
|
|
|
privileges.add(FIREBIRD_PRIVS[privilege.strip()]) # Firebird的权限处理
|
|
|
|
|
|
|
|
|
|
# In DB2 we get Y or G if the privilege is
|
|
|
|
|
# True, N otherwise
|
|
|
|
@ -487,21 +549,21 @@ class Users(object):
|
|
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
|
|
privileges.add(privilege)
|
|
|
|
|
privileges.add(privilege) # DB2的权限处理
|
|
|
|
|
|
|
|
|
|
if user in kb.data.cachedUsersPrivileges:
|
|
|
|
|
kb.data.cachedUsersPrivileges[user] = list(privileges.union(kb.data.cachedUsersPrivileges[user]))
|
|
|
|
|
kb.data.cachedUsersPrivileges[user] = list(privileges.union(kb.data.cachedUsersPrivileges[user])) # 合并权限
|
|
|
|
|
else:
|
|
|
|
|
kb.data.cachedUsersPrivileges[user] = list(privileges)
|
|
|
|
|
|
|
|
|
|
if not kb.data.cachedUsersPrivileges and isInferenceAvailable() and not conf.direct:
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema:
|
|
|
|
|
conditionChar = "LIKE"
|
|
|
|
|
conditionChar = "LIKE" # MySQL 5.0以上版本的模糊查询
|
|
|
|
|
else:
|
|
|
|
|
conditionChar = "="
|
|
|
|
|
conditionChar = "=" # 通用查询
|
|
|
|
|
|
|
|
|
|
if not len(users):
|
|
|
|
|
users = self.getUsers()
|
|
|
|
|
users = self.getUsers() # 获取用户列表
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL):
|
|
|
|
|
for user in users:
|
|
|
|
@ -510,33 +572,34 @@ class Users(object):
|
|
|
|
|
if parsedUser:
|
|
|
|
|
users[users.index(user)] = parsedUser.groups()[0]
|
|
|
|
|
|
|
|
|
|
retrievedUsers = set()
|
|
|
|
|
retrievedUsers = set() # 已获取权限的用户
|
|
|
|
|
|
|
|
|
|
# 循环盲注查询权限
|
|
|
|
|
for user in users:
|
|
|
|
|
outuser = user
|
|
|
|
|
if user in retrievedUsers:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema:
|
|
|
|
|
user = "%%%s%%" % user
|
|
|
|
|
user = "%%%s%%" % user # MySQL 5.0以上版本的模糊查询
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.INFORMIX):
|
|
|
|
|
count = 1
|
|
|
|
|
count = 1 # Informix数据库的特殊情况,直接查询权限
|
|
|
|
|
else:
|
|
|
|
|
infoMsg = "fetching number of privileges "
|
|
|
|
|
infoMsg += "for user '%s'" % outuser
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
|
|
|
|
|
query = rootQuery.blind.count2 % user
|
|
|
|
|
query = rootQuery.blind.count2 % user # MySQL 5.0以下版本的查询语句
|
|
|
|
|
elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema:
|
|
|
|
|
query = rootQuery.blind.count % (conditionChar, user)
|
|
|
|
|
query = rootQuery.blind.count % (conditionChar, user) # MySQL 5.0以上版本的查询语句
|
|
|
|
|
elif Backend.isDbms(DBMS.ORACLE) and query2:
|
|
|
|
|
query = rootQuery.blind.count2 % user
|
|
|
|
|
query = rootQuery.blind.count2 % user # Oracle的第二种查询方式
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.blind.count % user
|
|
|
|
|
query = rootQuery.blind.count % user # 通用查询语句
|
|
|
|
|
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
|
|
|
|
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) # 获取权限数量
|
|
|
|
|
|
|
|
|
|
if not isNumPosStrValue(count):
|
|
|
|
|
if not retrievedUsers and Backend.isDbms(DBMS.ORACLE) and not query2:
|
|
|
|
@ -556,21 +619,22 @@ class Users(object):
|
|
|
|
|
privileges = set()
|
|
|
|
|
|
|
|
|
|
plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne)
|
|
|
|
|
indexRange = getLimitRange(count, plusOne=plusOne) # 计算盲注的查询范围
|
|
|
|
|
|
|
|
|
|
# 循环盲注查询权限
|
|
|
|
|
for index in indexRange:
|
|
|
|
|
if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
|
|
|
|
|
query = rootQuery.blind.query2 % (user, index)
|
|
|
|
|
query = rootQuery.blind.query2 % (user, index) # MySQL 5.0以下版本的查询语句
|
|
|
|
|
elif Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema:
|
|
|
|
|
query = rootQuery.blind.query % (conditionChar, user, index)
|
|
|
|
|
query = rootQuery.blind.query % (conditionChar, user, index) # MySQL 5.0以上版本的查询语句
|
|
|
|
|
elif Backend.isDbms(DBMS.ORACLE) and query2:
|
|
|
|
|
query = rootQuery.blind.query2 % (user, index)
|
|
|
|
|
query = rootQuery.blind.query2 % (user, index) # Oracle的第二种查询方式
|
|
|
|
|
elif Backend.isDbms(DBMS.FIREBIRD):
|
|
|
|
|
query = rootQuery.blind.query % (index, user)
|
|
|
|
|
query = rootQuery.blind.query % (index, user) # Firebird数据库的查询语句
|
|
|
|
|
elif Backend.isDbms(DBMS.INFORMIX):
|
|
|
|
|
query = rootQuery.blind.query % (user,)
|
|
|
|
|
query = rootQuery.blind.query % (user,) # Informix数据库的查询语句
|
|
|
|
|
else:
|
|
|
|
|
query = rootQuery.blind.query % (user, index)
|
|
|
|
|
query = rootQuery.blind.query % (user, index) # 通用查询语句
|
|
|
|
|
|
|
|
|
|
privilege = unArrayizeValue(inject.getValue(query, union=False, error=False))
|
|
|
|
|
|
|
|
|
@ -586,14 +650,14 @@ class Users(object):
|
|
|
|
|
|
|
|
|
|
for priv in privs:
|
|
|
|
|
if priv.isdigit() and int(priv) == 1 and i in PGSQL_PRIVS:
|
|
|
|
|
privileges.add(PGSQL_PRIVS[i])
|
|
|
|
|
privileges.add(PGSQL_PRIVS[i]) # PostgreSQL的权限处理
|
|
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
|
|
# In MySQL >= 5.0 and Oracle we get the list
|
|
|
|
|
# of privileges as string
|
|
|
|
|
elif Backend.isDbms(DBMS.ORACLE) or (Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema) or Backend.getIdentifiedDbms() in (DBMS.VERTICA, DBMS.MIMERSQL, DBMS.CUBRID):
|
|
|
|
|
privileges.add(privilege)
|
|
|
|
|
privileges.add(privilege) # MySQL 5.0以上版本和Oracle的权限处理
|
|
|
|
|
|
|
|
|
|
# In MySQL < 5.0 we get Y if the privilege is
|
|
|
|
|
# True, N otherwise
|
|
|
|
@ -606,19 +670,19 @@ class Users(object):
|
|
|
|
|
if priv.upper() == 'Y':
|
|
|
|
|
for position, mysqlPriv in MYSQL_PRIVS.items():
|
|
|
|
|
if position == i:
|
|
|
|
|
privileges.add(mysqlPriv)
|
|
|
|
|
privileges.add(mysqlPriv) # MySQL 5.0以下版本的权限处理
|
|
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
|
|
# In Firebird we get one letter for each privilege
|
|
|
|
|
elif Backend.isDbms(DBMS.FIREBIRD):
|
|
|
|
|
if privilege.strip() in FIREBIRD_PRIVS:
|
|
|
|
|
privileges.add(FIREBIRD_PRIVS[privilege.strip()])
|
|
|
|
|
privileges.add(FIREBIRD_PRIVS[privilege.strip()]) # Firebird的权限处理
|
|
|
|
|
|
|
|
|
|
# In Informix we get one letter for the highest privilege
|
|
|
|
|
elif Backend.isDbms(DBMS.INFORMIX):
|
|
|
|
|
if privilege.strip() in INFORMIX_PRIVS:
|
|
|
|
|
privileges.add(INFORMIX_PRIVS[privilege.strip()])
|
|
|
|
|
privileges.add(INFORMIX_PRIVS[privilege.strip()]) # Informix的权限处理
|
|
|
|
|
|
|
|
|
|
# In DB2 we get Y or G if the privilege is
|
|
|
|
|
# True, N otherwise
|
|
|
|
@ -633,7 +697,7 @@ class Users(object):
|
|
|
|
|
if priv.upper() in ('Y', 'G'):
|
|
|
|
|
for position, db2Priv in DB2_PRIVS.items():
|
|
|
|
|
if position == i:
|
|
|
|
|
privilege += ", " + db2Priv
|
|
|
|
|
privilege += ", " + db2Priv # DB2的权限处理
|
|
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
|
@ -661,13 +725,6 @@ class Users(object):
|
|
|
|
|
|
|
|
|
|
for user, privileges in kb.data.cachedUsersPrivileges.items():
|
|
|
|
|
if isAdminFromPrivileges(privileges):
|
|
|
|
|
areAdmins.add(user)
|
|
|
|
|
|
|
|
|
|
return (kb.data.cachedUsersPrivileges, areAdmins)
|
|
|
|
|
|
|
|
|
|
def getRoles(self, query2=False):
|
|
|
|
|
warnMsg = "on %s the concept of roles does not " % Backend.getIdentifiedDbms()
|
|
|
|
|
warnMsg += "exist. sqlmap will enumerate privileges instead"
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
areAdmins.add(user) # 判断是否为DBA
|
|
|
|
|
|
|
|
|
|
return self.getPrivileges(query2)
|
|
|
|
|
return (kb.data.cachedUsersPrivileges)
|