|
|
|
@ -7,94 +7,109 @@ See the file 'LICENSE' for copying permission
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
from lib.core.common import Backend
|
|
|
|
|
from lib.core.common import Format
|
|
|
|
|
from lib.core.common import getCurrentThreadData
|
|
|
|
|
from lib.core.common import randomStr
|
|
|
|
|
from lib.core.common import wasLastResponseDBMSError
|
|
|
|
|
from lib.core.data import conf
|
|
|
|
|
from lib.core.data import kb
|
|
|
|
|
from lib.core.data import logger
|
|
|
|
|
from lib.core.enums import DBMS
|
|
|
|
|
from lib.core.session import setDbms
|
|
|
|
|
from lib.core.settings import ACCESS_ALIASES
|
|
|
|
|
from lib.core.settings import METADB_SUFFIX
|
|
|
|
|
from lib.request import inject
|
|
|
|
|
from plugins.generic.fingerprint import Fingerprint as GenericFingerprint
|
|
|
|
|
|
|
|
|
|
# 1. 从库中引入需要的模块和类
|
|
|
|
|
from lib.core.common import Backend # 后端数据库信息
|
|
|
|
|
from lib.core.common import Format # 格式化输出
|
|
|
|
|
from lib.core.common import getCurrentThreadData # 获取当前线程的数据
|
|
|
|
|
from lib.core.common import randomStr # 生成随机字符串
|
|
|
|
|
from lib.core.common import wasLastResponseDBMSError # 判断最后响应是否包含数据库错误
|
|
|
|
|
from lib.core.data import conf # 全局配置信息
|
|
|
|
|
from lib.core.data import kb # 全局知识库
|
|
|
|
|
from lib.core.data import logger # 日志记录器
|
|
|
|
|
from lib.core.enums import DBMS # 数据库类型枚举
|
|
|
|
|
from lib.core.session import setDbms # 设置当前数据库类型
|
|
|
|
|
from lib.core.settings import ACCESS_ALIASES # ACCESS 数据库的别名
|
|
|
|
|
from lib.core.settings import METADB_SUFFIX # 元数据表后缀
|
|
|
|
|
from lib.request import inject # 注入相关函数
|
|
|
|
|
from plugins.generic.fingerprint import Fingerprint as GenericFingerprint # 通用指纹识别类
|
|
|
|
|
|
|
|
|
|
# 2. 定义一个类 Fingerprint,继承自 GenericFingerprint
|
|
|
|
|
class Fingerprint(GenericFingerprint):
|
|
|
|
|
# 3. 构造函数,初始化数据库类型
|
|
|
|
|
def __init__(self):
|
|
|
|
|
GenericFingerprint.__init__(self, DBMS.ACCESS)
|
|
|
|
|
|
|
|
|
|
# 4. 私有方法,检查是否在沙盒中运行
|
|
|
|
|
def _sandBoxCheck(self):
|
|
|
|
|
# Reference: http://milw0rm.com/papers/198
|
|
|
|
|
# 参考链接: http://milw0rm.com/papers/198
|
|
|
|
|
retVal = None
|
|
|
|
|
table = None
|
|
|
|
|
|
|
|
|
|
# 5. 根据 Access 版本设置需要查询的表名
|
|
|
|
|
if Backend.isVersionWithin(("97", "2000")):
|
|
|
|
|
table = "MSysAccessObjects"
|
|
|
|
|
elif Backend.isVersionWithin(("2002-2003", "2007")):
|
|
|
|
|
table = "MSysAccessStorage"
|
|
|
|
|
|
|
|
|
|
# 6. 如果有对应的表名,则执行查询判断是否处于沙盒环境
|
|
|
|
|
if table is not None:
|
|
|
|
|
result = inject.checkBooleanExpression("EXISTS(SELECT CURDIR() FROM %s)" % table)
|
|
|
|
|
retVal = "not sandboxed" if result else "sandboxed"
|
|
|
|
|
|
|
|
|
|
# 7. 返回检测结果
|
|
|
|
|
return retVal
|
|
|
|
|
|
|
|
|
|
# 8. 私有方法,检查系统表是否存在
|
|
|
|
|
def _sysTablesCheck(self):
|
|
|
|
|
infoMsg = "executing system table(s) existence fingerprint"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# Microsoft Access table reference updated on 01/2010
|
|
|
|
|
# 9. 定义不同版本 Access 需要检查的系统表
|
|
|
|
|
sysTables = {
|
|
|
|
|
"97": ("MSysModules2", "MSysAccessObjects"),
|
|
|
|
|
"2000": ("!MSysModules2", "MSysAccessObjects"),
|
|
|
|
|
"2002-2003": ("MSysAccessStorage", "!MSysNavPaneObjectIDs"),
|
|
|
|
|
"2007": ("MSysAccessStorage", "MSysNavPaneObjectIDs"),
|
|
|
|
|
}
|
|
|
|
|
# MSysAccessXML 是不稳定的系统表,因为它并非总是存在
|
|
|
|
|
|
|
|
|
|
# MSysAccessXML is not a reliable system table because it doesn't always exist
|
|
|
|
|
# ("Access through Access", p6, should be "normally doesn't exist" instead of "is normally empty")
|
|
|
|
|
|
|
|
|
|
# 10. 遍历系统表,进行检查
|
|
|
|
|
for version, tables in sysTables.items():
|
|
|
|
|
exist = True
|
|
|
|
|
|
|
|
|
|
for table in tables:
|
|
|
|
|
negate = False
|
|
|
|
|
|
|
|
|
|
# 11. 如果表名以 ! 开头,表示该表应该不存在
|
|
|
|
|
if table[0] == '!':
|
|
|
|
|
negate = True
|
|
|
|
|
table = table[1:]
|
|
|
|
|
|
|
|
|
|
# 12. 执行 SQL 查询,检查表是否存在
|
|
|
|
|
result = inject.checkBooleanExpression("EXISTS(SELECT * FROM %s WHERE [RANDNUM]=[RANDNUM])" % table)
|
|
|
|
|
if result is None:
|
|
|
|
|
result = False
|
|
|
|
|
|
|
|
|
|
# 13. 如果表不应该存在,则取反
|
|
|
|
|
if negate:
|
|
|
|
|
result = not result
|
|
|
|
|
|
|
|
|
|
# 14. 对所有表的结果进行与运算,只有都满足才能认为当前版本匹配
|
|
|
|
|
exist &= result
|
|
|
|
|
|
|
|
|
|
if not exist:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# 15. 如果当前版本匹配,则返回版本号
|
|
|
|
|
if exist:
|
|
|
|
|
return version
|
|
|
|
|
|
|
|
|
|
# 16. 如果所有版本都不匹配,则返回 None
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# 17. 私有方法,获取数据库所在目录
|
|
|
|
|
def _getDatabaseDir(self):
|
|
|
|
|
retVal = None
|
|
|
|
|
|
|
|
|
|
infoMsg = "searching for database directory"
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 18. 生成随机字符串
|
|
|
|
|
randStr = randomStr()
|
|
|
|
|
# 19. 执行 SQL 查询,尝试触发错误,获取数据库目录
|
|
|
|
|
inject.checkBooleanExpression("EXISTS(SELECT * FROM %s.%s WHERE [RANDNUM]=[RANDNUM])" % (randStr, randStr))
|
|
|
|
|
|
|
|
|
|
# 20. 如果最后响应包含数据库错误
|
|
|
|
|
if wasLastResponseDBMSError():
|
|
|
|
|
threadData = getCurrentThreadData()
|
|
|
|
|
# 21. 从错误信息中提取数据库目录
|
|
|
|
|
match = re.search(r"Could not find file\s+'([^']+?)'", threadData.lastErrorPage[1])
|
|
|
|
|
|
|
|
|
|
if match:
|
|
|
|
@ -102,92 +117,115 @@ class Fingerprint(GenericFingerprint):
|
|
|
|
|
|
|
|
|
|
if retVal.endswith('\\'):
|
|
|
|
|
retVal = retVal[:-1]
|
|
|
|
|
|
|
|
|
|
# 22. 返回数据库目录
|
|
|
|
|
return retVal
|
|
|
|
|
|
|
|
|
|
# 23. 获取指纹信息
|
|
|
|
|
def getFingerprint(self):
|
|
|
|
|
value = ""
|
|
|
|
|
# 24. 获取 Web 服务器操作系统指纹
|
|
|
|
|
wsOsFp = Format.getOs("web server", kb.headersFp)
|
|
|
|
|
|
|
|
|
|
# 25. 将 Web 服务器操作系统指纹添加到输出
|
|
|
|
|
if wsOsFp:
|
|
|
|
|
value += "%s\n" % wsOsFp
|
|
|
|
|
value += "%s\
|
|
|
|
|
" % wsOsFp
|
|
|
|
|
|
|
|
|
|
# 26. 如果有数据库 Banner 信息
|
|
|
|
|
if kb.data.banner:
|
|
|
|
|
# 27. 获取后端数据库操作系统指纹
|
|
|
|
|
dbmsOsFp = Format.getOs("back-end DBMS", kb.bannerFp)
|
|
|
|
|
|
|
|
|
|
# 28. 将后端数据库操作系统指纹添加到输出
|
|
|
|
|
if dbmsOsFp:
|
|
|
|
|
value += "%s\n" % dbmsOsFp
|
|
|
|
|
value += "%s\
|
|
|
|
|
" % dbmsOsFp
|
|
|
|
|
|
|
|
|
|
value += "back-end DBMS: "
|
|
|
|
|
|
|
|
|
|
# 29. 如果不是详细指纹,则返回数据库类型
|
|
|
|
|
if not conf.extensiveFp:
|
|
|
|
|
value += DBMS.ACCESS
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
# 30. 获取活动的指纹信息
|
|
|
|
|
actVer = Format.getDbms() + " (%s)" % (self._sandBoxCheck())
|
|
|
|
|
blank = " " * 15
|
|
|
|
|
value += "active fingerprint: %s" % actVer
|
|
|
|
|
|
|
|
|
|
# 31. 如果有 Banner 解析指纹
|
|
|
|
|
if kb.bannerFp:
|
|
|
|
|
banVer = kb.bannerFp.get("dbmsVersion")
|
|
|
|
|
|
|
|
|
|
# 32. 如果有 Banner 版本号
|
|
|
|
|
if banVer:
|
|
|
|
|
# 33. 如果 Banner 信息包含 -log,则表示启用日志
|
|
|
|
|
if re.search(r"-log$", kb.data.banner or ""):
|
|
|
|
|
banVer += ", logging enabled"
|
|
|
|
|
|
|
|
|
|
# 34. 格式化 Banner 版本号
|
|
|
|
|
banVer = Format.getDbms([banVer])
|
|
|
|
|
value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer)
|
|
|
|
|
value += "\
|
|
|
|
|
%sbanner parsing fingerprint: %s" % (blank, banVer)
|
|
|
|
|
|
|
|
|
|
# 35. 获取 HTML 错误指纹
|
|
|
|
|
htmlErrorFp = Format.getErrorParsedDBMSes()
|
|
|
|
|
|
|
|
|
|
# 36. 将 HTML 错误指纹添加到输出
|
|
|
|
|
if htmlErrorFp:
|
|
|
|
|
value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp)
|
|
|
|
|
|
|
|
|
|
value += "\ndatabase directory: '%s'" % self._getDatabaseDir()
|
|
|
|
|
|
|
|
|
|
value += "\
|
|
|
|
|
%shtml error message fingerprint: %s" % (blank, htmlErrorFp)
|
|
|
|
|
# 37. 获取数据库目录并添加到输出
|
|
|
|
|
value += "\
|
|
|
|
|
database directory: '%s'" % self._getDatabaseDir()
|
|
|
|
|
# 38. 返回完整的指纹信息
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
# 39. 检查数据库类型是否为 Access
|
|
|
|
|
def checkDbms(self):
|
|
|
|
|
# 40. 如果不是详细指纹,且当前数据库类型属于 Access 别名,则设置数据库类型为 Access 并返回 True
|
|
|
|
|
if not conf.extensiveFp and Backend.isDbmsWithin(ACCESS_ALIASES):
|
|
|
|
|
setDbms(DBMS.ACCESS)
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# 41. 输出正在测试的数据库类型
|
|
|
|
|
infoMsg = "testing %s" % DBMS.ACCESS
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 42. 执行 SQL 查询,检查数据库类型是否为 Access
|
|
|
|
|
result = inject.checkBooleanExpression("VAL(CVAR(1))=1")
|
|
|
|
|
|
|
|
|
|
# 43. 如果查询成功
|
|
|
|
|
if result:
|
|
|
|
|
infoMsg = "confirming %s" % DBMS.ACCESS
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 44. 执行 SQL 查询,再次确认数据库类型是否为 Access
|
|
|
|
|
result = inject.checkBooleanExpression("IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0")
|
|
|
|
|
|
|
|
|
|
# 45. 如果再次确认失败,则输出警告信息,并返回 False
|
|
|
|
|
if not result:
|
|
|
|
|
warnMsg = "the back-end DBMS is not %s" % DBMS.ACCESS
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 46. 设置数据库类型为 Access
|
|
|
|
|
setDbms(DBMS.ACCESS)
|
|
|
|
|
|
|
|
|
|
# 47. 如果不是详细指纹,则返回 True
|
|
|
|
|
if not conf.extensiveFp:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# 48. 输出正在进行详细指纹识别
|
|
|
|
|
infoMsg = "actively fingerprinting %s" % DBMS.ACCESS
|
|
|
|
|
logger.info(infoMsg)
|
|
|
|
|
|
|
|
|
|
# 49. 执行系统表检查,获取 Access 版本
|
|
|
|
|
version = self._sysTablesCheck()
|
|
|
|
|
|
|
|
|
|
# 50. 如果获取到版本信息,则设置数据库版本
|
|
|
|
|
if version is not None:
|
|
|
|
|
Backend.setVersion(version)
|
|
|
|
|
|
|
|
|
|
# 51. 返回 True
|
|
|
|
|
return True
|
|
|
|
|
# 52. 如果第一次查询失败,则输出警告信息,并返回 False
|
|
|
|
|
else:
|
|
|
|
|
warnMsg = "the back-end DBMS is not %s" % DBMS.ACCESS
|
|
|
|
|
logger.warning(warnMsg)
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 53. 强制枚举数据库类型
|
|
|
|
|
def forceDbmsEnum(self):
|
|
|
|
|
conf.db = ("%s%s" % (DBMS.ACCESS, METADB_SUFFIX)).replace(' ', '_')
|
|
|
|
|
# 54. 设置数据库名称
|
|
|
|
|
conf.db = ("%s%s" % (DBMS.ACCESS, METADB_SUFFIX)).replace(' ', '_')
|