ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

把 MySQL 权限审计做成可回归的策略测试:最小权限、证据快照与受控修复

把 MySQL 权限审计做成可回归的策略测试:最小权限、证据快照与受控修复

数据库权限问题往往不是由复杂攻击开始的,而是由日常变更逐步累积:临时排障时授予的高权限没有回收;应用账户为了绕过报错被扩大到整个库;离职或系统下线后账户仍可登录;同一服务在测试与生产环境使用了不同授权,却没有差异记录。

如果只依赖人工定期查看用户列表,团队很难回答三个具体问题:

  1. 某账户当前是否拥有超出职责范围的权限?
  2. 权限变化是经过审批的预期变更,还是配置漂移?
  3. 发现问题后,修复操作是否会误伤依赖该权限的业务?

较稳妥的做法是把权限要求写成机器可读取的策略,将数据库实际授权采集为证据快照,再把二者比较为一组可重复执行的测试。这里的“测试”并不替代安全审计或变更审批;它的价值在于让常见偏差尽早被检测,并保留可复核的输入、结果和处理记录。

核心原理:基线、证据与差异的闭环

一个可维护的权限审计闭环包含四层。

1. 身份边界

MySQL 账户由用户名和主机部分共同构成。'report_app'@'10.%''report_app'@'localhost'是不同账户。审计时只看用户名会漏掉主机范围扩大这一类风险,因此基线必须完整表达账户标识。

同时应区分人类账户、应用账户、运维账户和自动化账户。它们的认证方式、生命周期和允许来源通常不同,不宜共用同一种模板。

2. 授权基线

基线不是简单的“应有权限列表”,还应定义授权对象范围。例如报表服务读取analytics库,可基线化为:

  • 允许:SELECTanalytics.*
  • 禁止:任何全局权限、GRANT OPTION、数据定义和数据修改权限;
  • 不允许:来自未登记主机范围的同名账户。

对于复杂系统,优先用角色承载权限集合,用户只被授予角色。这样角色变动与用户绑定可以分别审计,减少大量重复的逐用户授权。

3. 事实采集

SHOW GRANTS FOR返回的是数据库当前的授权事实,适合作为证据来源。采集结果需要原样保存,而非只存“通过/失败”:授权语句是后续排查、审批和回滚判断的重要上下文。

不同 MySQL 部署的认证插件、角色启用方式和授权输出可能有差异。脚本应在目标环境先试运行,并把当前版本、采集时间和连接目标一并写入审计记录;不要假定所有环境的输出完全一致。

4. 差异处理

差异不应直接触发自动REVOKE。正确流程是:检测到差异后创建工单或告警,确认业务依赖和变更记录;批准后再执行明确、可审阅的修复 SQL;最后重新采集,确认差异已消失。对于高风险账户,修复应纳入既有变更窗口。

第一步:用角色建立一个最小权限基线

以下示例假设执行者已通过受控渠道取得数据库管理连接。密码不写入 SQL 文件,也不提交到仓库;命令行从环境变量读取。

exportMYSQL_PWD="$DB_ADMIN_PASSWORD"mysql-h"$DB_HOST"-P"${DB_PORT:-3306}"-u"$DB_ADMIN_USER"<bootstrap.sqlunsetMYSQL_PWD

bootstrap.sql可从最小读权限开始:

CREATEROLEIFNOTEXISTS`analytics_reader`;GRANTSELECTON`analytics`.*TO`analytics_reader`;CREATEUSERIFNOTEXISTS`report_app`@`10.%`IDENTIFIEDBY'${APPLICATION_PASSWORD}';GRANT`analytics_reader`TO`report_app`@`10.%`;SETDEFAULTROLE`analytics_reader`TO`report_app`@`10.%`;

上例中的${APPLICATION_PASSWORD}只是占位符,不应直接作为可执行 SQL 的替换方式。生产实践中,建议由密钥管理系统或部署工具在运行时生成受保护的临时文件或安全参数,再执行建户操作。若使用的 MySQL 环境不支持上述角色语法或认证写法,应根据目标环境文档调整后验证。

接着,将策略放入版本库,例如policy.json

{"accounts":{"report_app@10.%":{"required_fragments":["GRANT `analytics_reader`@`%` TO `report_app`@`10.%`"],"forbidden_fragments":["ON *.*","WITH GRANT OPTION","INSERT","UPDATE","DELETE","DROP"]}}}

策略中的字符串比较是一个易理解的起点,但不是通用 SQL 解析器。授权输出的大小写、反引号和角色表示方式可能因环境而异。正式上线前,应以实际SHOW GRANTS输出校准策略;复杂规则可进一步实现结构化解析,而不是无限增加字符串例外。

第二步:采集授权并执行策略测试

安装连接驱动:

python-mpipinstallmysql-connector-python

下面脚本读取环境变量连接数据库,采集指定账户的授权,并以非零退出码标记失败,适合被定时任务或 CI 调用。

importjsonimportosimportsysfromdatetimeimportdatetime,timezoneimportmysql.connectorwithopen("policy.json","r",encoding="utf-8")asf:policy=json.load(f)["accounts"]conn=mysql.connector.connect(host=os.environ["DB_HOST"],port=int(os.getenv("DB_PORT","3306")),user=os.environ["DB_AUDIT_USER"],password=os.environ["DB_AUDIT_PASSWORD"],connection_timeout=10,)cur=conn.cursor()results={"captured_at":datetime.now(timezone.utc).isoformat(),"accounts":{}}failed=[]foraccount,rulesinpolicy.items():user,host=account.rsplit("@",1)cur.execute(f"SHOW GRANTS FOR `{user}`@`{host}`")grants=[row[0]forrowincur.fetchall()]evidence="\n".join(grants).upper()missing=[xforxinrules["required_fragments"]ifx.upper()notinevidence]forbidden=[xforxinrules["forbidden_fragments"]ifx.upper()inevidence]results["accounts"][account]={"grants":grants,"missing":missing,"forbidden":forbidden}ifmissingorforbidden:failed.append(account)withopen("grant-evidence.json","w",encoding="utf-8")asf:json.dump(results,f,ensure_ascii=False,indent=2)cur.close()conn.close()iffailed:print("权限策略不符合:"+", ".join(failed),file=sys.stderr)sys.exit(2)print("权限策略检查通过")

注意,示例以账户名来自受控的策略文件为前提,不能将外部用户输入直接拼入 SQL。审计账户也应遵循最小权限,只获得完成账户枚举和授权查看所需的权限;具体需要哪些授权,取决于 MySQL 部署方式和审计范围,应在预发布环境验证。

第三步:接入流水线,但将修复与检测隔离

可将检测放入每日计划任务或发布前检查。以 GitHub Actions 为例,数据库连接信息存于仓库 Secret,日志中不要输出环境变量:

name:privilege-auditon:schedule:-cron:"20 2 * * 1-5"workflow_dispatch:jobs:audit:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:python-version:"3.12"-run:pip install mysql-connector-python-run:python audit_grants.pyenv:DB_HOST:${{secrets.DB_AUDIT_HOST}}DB_PORT:${{secrets.DB_AUDIT_PORT}}DB_AUDIT_USER:${{secrets.DB_AUDIT_USER}}DB_AUDIT_PASSWORD:${{secrets.DB_AUDIT_PASSWORD}}

不要把修复 SQL直接接在失败分支中自动执行。更安全的设计是生成grant-evidence.json作为受保护制品,失败后通知责任人;人工确认后,在独立审批流程中运行一份参数固定、范围明确的修复脚本。

如果团队希望用模型把差异转写为工单摘要,可将授权证据先脱敏,再交给符合组织要求的模型接口处理。例如在评估 HaerAPI(https://www.haerapi.com)这类模型接入服务时,应限制输入为必要的授权差异和系统代号,避免发送密码、连接串、完整业务数据或敏感表名,并保留人工审核环节。

常见问题

角色已授予,为什么应用仍然没有权限?

应检查该用户的默认角色是否设置,以及当前会话实际启用的角色状态。角色“被授予”和角色“在会话中生效”是两个需要分别验证的事实。还要确认应用连接的账号主机部分与预期账户一致。

为什么不直接查询权限系统表?

系统表可提供补充信息,但其字段、存储方式和可见范围可能随部署及版本而不同。以SHOW GRANTS作为最终授权证据通常更贴近实际授权语义;若同时读取系统表,应将其视为辅助数据并进行一致性校验。

审计脚本会不会泄露授权信息?

会有这种可能。授权语句可能暴露库名、对象名、账户名和网络范围。应把证据文件视为敏感运维资料:限制制品访问者、设置保留期限、避免上传公共日志,并按组织规范决定是否需要进一步脱敏。

权限变更很多,基线文件难维护怎么办?

先按服务职责拆分角色和策略文件,而不是把每个用户的所有授权写成巨型清单。对于经过审批的临时授权,设置明确到期日期,并在策略中单独标识;到期后由检测任务提示回收。基线应随应用架构变更同步评审,而非成为脱离现实的静态文档。

总结

权限治理的关键不在于积累更多授权命令,而在于建立“期望策略—实际证据—差异判定—审批修复—复测确认”的闭环。以角色压缩授权面,以SHOW GRANTS保存可复核事实,以脚本将策略变成可回归检查,并把自动检测与人工修复隔离,能够让最小权限原则进入日常工程流程。开始时只覆盖最关键的应用账户和高风险权限,待输出稳定后再逐步扩展范围,通常比一次性追求全量自动化更可靠。

返回列表