数据库安全评估工具
DbDat 对数据库执行多项安全检查,涵盖配置、权限、用户和信息四大类别。检查通过运行查询或读取数据库配置文件完成。该工具的目标是突出需要立即关注的问题,并识别应审查其适宜性的配置设置。该工具不用于识别应用程序中的SQL注入漏洞——已有成熟工具(如 https://github.com/sqlmapproject)处理此类问题。同时,该工具不会尝试判断目标数据库版本受哪些CVE影响(未来可能加入,或许吧)。相反,它可以帮助你更好地理解因配置薄弱或访问控制不当而成功实施SQL注入攻击后可能产生的潜在影响。大部分检查源自CIS(https://cisecurity.org)数据库安全基准,在此感谢CIS!基准文档可在此处获取:https://benchmarks.cisecurity.org/downloads/browse/index.cfm?category=benchmarks.servers.database
我强烈建议下载目标数据库的基准文档,其中包含关于所执行检查的更多信息。
最后,DbDat 旨在成为一个框架,以便轻松创建新的插件和检查。来自安全社区甚至数据库管理员社区的贡献将使这个工具变得出色。当前检查集远未完善,肯定还有更多工作需要完成。欢迎贡献!
开发新的数据库检查
非常欢迎拉取请求! 检查按数据库类型(如 MySQL、Oracle、MS SQL 等)组织在 plugins 文件夹中。每个检查是一个单独的 Python 文件,文件名必须以 check_ 开头。每个文件包含一个带有 do_check 方法的类,该方法执行检查的主要逻辑。快速入门的方法是复制现有检查文件并修改它。但更多细节请参考下面的“开发插件”部分。
etc/dbdat.conf 文件中为每个要评估的数据库添加一个连接配置文件条目。python dbdat.py -p <配置文件名称>cd 到 reports 目录并运行 python -m SimpleHTTPServer 9000(或选择你偏好的端口号)。然后在浏览器中访问 http://localhost:9000。要查看其他命令行参数列表,运行 python dbdat.py -h
报告按级别组织结果:RED、YELLOW、ORANGE、GRAY 和 GREEN。
截至目前,DbDat 已在 Debian Linux、CentOS Linux 和 Windows 7 上使用 Python 2.7 进行测试。
运行:pip install MySQL-python
或者在 Debian 上运行:apt-get install python-mysqldb
运行:pip install psycopg2
运行:pip install cx_Oracle
注意:你需要安装 Oracle 客户端库才能正常工作。
运行:pip install pymssql
运行:pip install ibm_db 或 easy_install ibm_db
注意:需要确保运行 DbDat 的用户有权执行 DB2 CLP 命令(例如 db2 和 db2level)。
运行:pip install pymongo
要支持 MongoDB YAML 配置文件,运行:pip install pyyaml
运行:pip install couchdb
在 plugins 文件夹中,每种数据库类型有一个文件夹,每个文件夹包含检查文件。结构非常简单,你也可以浏览 plugins 文件夹熟悉一下 https://github.com/foospidy/DbDat/tree/master/plugins
数据库文件夹将包含:
__init__.py - init 文件包含每个检查文件的导入语句。check_ 文件 - 这些是执行数据库检查的实际文件。文件名和文件中定义的类名应相同。helper.py - 包含公共函数的文件。检查文件可以导入 helper.py 以利用公共函数。添加新的检查文件时,需要在相应插件目录的 __init__.py 文件中添加导入语句。检查的代码模式相当一致。查看现有文件以了解其结构。注意 sql 类型和 configuration_file 类型检查之间的区别。
可以定义不同“类型”的检查。检查类型由 TYPE 变量决定,可以是 sql、configuration_file、nosql 或 clp。以下是不同检查类型场景的示例实现。
对于 sql 检查,do_check 方法签名必须为:do_check(self, *results)
https://github.com/foospidy/DbDat/blob/master/plugins/mysql/check_user_empty_password.py
在此示例中,我们需要来自调用父类的 appuser 变量。由于需要在 SQL 语句中动态添加 appuser,因此 self.SQL 变量是在 __init__ 方法中设置的。另外,由于此 do_check 方法需要执行 SQL,我们还需要数据库游标(连接),这也在 __init__ 方法中通过 self.dbcurs = parent.dbcurs 设置。
https://github.com/foospidy/DbDat/blob/master/plugins/mysql/check_privilege_user_grants.py
对于 configuration_file 检查,do_check 方法签名必须为:do_check(self, configuration_file)
https://github.com/foospidy/DbDat/blob/master/plugins/mysql/check_configuration_general_log.py
在此示例中,PostgreSQL 配置文件没有节(例如 [section_name]),因此 PostgreSQL 文件夹中的 helper 模块包含一个处理此情况的函数。请参见 helper.py 中定义的 get_config_value 函数。
对于其他配置文件格式,你需要定义自己的解析逻辑。
对于 nosql 检查,do_check 方法签名必须为:do_check(self)
NoSQL 查询需要在 do_check 方法内执行,因此类 __init__ 方法必须实现 self.db = parent.db。然后可以在 do_check 方法中使用 self.db 变量执行 NoSQL 查询。
https://github.com/foospidy/DbDat/blob/master/plugins/mongodb/check_information_banner.py
对于 clp 检查,do_check 方法签名必须为:do_check(self, *results). clp 检查用于 IBM DB2 数据库,以便执行 db2 命令行处理器获取数据库信息。不过,此检查类型也可用于执行任意命令行命令。所有命令行输出都可以从传递给 do_check 方法的 results 变量中解析。此外,你需要定义 CMD 类方法。该变量是一个包含命令及相关参数的列表。
https://github.com/foospidy/DbDat/blob/master/plugins/db2/check_privilege_group_entitlements.py
每个检查必须使用 CATEGORY 变量指定类别。类别用于组织检查。可能的类别有:Information、Configuration、Privilege 和 User。请指定与检查上下文最相关的类别。
以下是一个粗略示例,展示检查文件应遵循的模式:
# (可选):添加导入语句,包含该检查所需的任何模块
import helper
# (必需):定义类,类名必须与其文件名相同。
class check_configuration_evaluate_something():
# (必需):添加文档,提供此检查的信息,这些信息将显示在报告中。
"""
check_configuration_evaluate_something:
此处填写一些描述!
"""
# (可选):添加参考资料,这只是代码注释,添加参考资料对他人有帮助。
# 参考资料:
# https://www.percona.com/blog/2012/12/28/auditing-login-attempts-in-mysql/
# https://dev.mysql.com/doc/refman/5.7/en/password-security-user.html
# (必需):添加标准变量集
TITLE = '客户端密码'
CATEGORY = 'Configuration'
TYPE = 'configuration_file'
SQL = None
verbose = False
skip = False
result = {}
# (可选):添加该检查特有的任何自定义变量
custom_var = 'custom_val'
# (必需):定义 do_check 方法,这是实际的检查逻辑,由主程序调用。
def do_check(self, *results):
# (必需):检查逻辑写在这里,务必设置 self.result['level'] 和 self.result['output'] 的值
# 可以通过以下方式处理 SQL 变量的结果:
for rows in results:
for row in rows:
# (必需):
# 在返回之前始终设置 self.result['level'] 和 self.result['output'] 变量。
if row[0] == 'bad':
self.result['level'] = 'RED'
self.result['output'] = '结果:%s' % (row[0])
elif row[0] == 'needs review':
self.result['level'] = 'YELLOW'
self.result['output'] = '结果:%s' % (row[0])
else:
self.result['level'] = 'GREEN'
self.result['output'] = '结果:%s' % (row[0])
# 总是返回 self.result
return self.result
# (必需):__init__ 方法至少应打印正在执行的检查。
def __init__(self, parent):
print('正在执行检查:' + self.TITLE)
# 从调用父类获取值
self.verbose = parent.verbose