应用账号必须表级精确授权、锁定内网IP、禁用root@%、强制使用角色管理、显式回收高危权限,并清除匿名用户。

应用账号必须精确到表级授权,不能用 db.*
应用真正执行的 SQL 很少扫全库,GRANT SELECT ON app_db.* TO 'app'@'10.20.30.%' 看似方便,实则埋雷。一旦库中混入 sys_config、audit_log 或临时调试表,敏感数据就暴露了。更隐蔽的是,app_db.* 会连带让账号对 information_schema 保持默认可读——SELECT * FROM information_schema.tables 就能列出所有库名和表结构。
- 先确认真实访问表:开
general_log跑一轮典型业务链路,再用SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(argument,' ', 3),' ', -1) FROM mysql.general_log WHERE argument LIKE 'SELECT %' OR argument LIKE 'UPDATE %'提取实际表名 - 逐表授权:比如订单服务只读,就写
GRANT SELECT ON app_db.orders TO 'order_reader'@'10.20.30.%';需要更新状态,再加一条GRANT UPDATE (status, updated_at) ON app_db.orders TO 'order_reader'@'10.20.30.%' - MySQL 8.0.29+ 可禁用兼容模式后单独授业务表:
SET PERSIST show_compatibility_56 = OFF,再REVOKE SELECT ON *.* FROM 'app'@'10.20.30.%',最后只GRANT SELECT ON app_db.orders
主机段必须锁定内网 CIDR,严禁用 '%'
'app'@'%' 不是懒,是把防火墙撕开一道口子。攻击者只要拿到密码,就能从任意公网 IP 连入,绕过所有网络层防护。DNS 反查还能让 'app'@'%.example.com' 被恶意域名绕过。
- 固定 IP 场景:直接写
'app'@'10.20.30.45' - K8s 或云环境:用 CIDR 格式,如
'app'@'10.100.0.0/255.255.0.0'(MySQL 8.0.19+ 支持) - 开发网段收敛:统一用
'dev'@'192.168.5.%',不开放办公网全部子网 - 绝对禁用
'root'@'%':立刻执行RENAME USER 'root'@'%' TO 'root'@'127.0.0.1',并确认skip-grant-tables在配置中彻底关闭
MySQL 8.0+ 必须用角色封装权限,且设默认角色
直接给用户授一堆 GRANT 语句,后期改权限就是灾难。多个服务共用一类权限时,靠人工同步极易漏掉某个账号。角色不是锦上添花,是批量管控的基础设施。
- 建角色:如
CREATE ROLE 'order_rw', 'report_ro' - 授角色权限:注意粒度,
GRANT SELECT, INSERT, UPDATE ON app_db.orders TO 'order_rw',别把user_password表也包进去 - 绑用户并激活:
GRANT 'order_rw' TO 'webapp'@'10.20.30.%'; SET DEFAULT ROLE 'order_rw' TO 'webapp'@'10.20.30.%' - 验证方式不对:
SHOW GRANTS FOR 'webapp'@'10.20.30.%'看不到角色权限,得用SHOW GRANTS FOR 'webapp'@'10.20.30.%' USING 'order_rw'或登录后查CURRENT_ROLE()
高危权限必须显式 REVOKE,不能靠“没给就安全”
很多人以为不写 DROP 就等于没这个权限,但 MySQL 默认行为不是“白名单”,而是“未明确禁止即可行”——尤其在非严格模式下,部分越权操作可能静默失败,掩盖真实风险。
- 创建账号后立刻清理:
REVOKE DROP, ALTER, CREATE, GRANT OPTION, FILE, PROCESS ON *.* FROM 'app'@'10.20.30.%' -
ALTER权限隐含DROP行为(如RENAME TABLE实质是 drop + create),无法拆分,真要改结构必须走运维平台审批 - 禁用匿名用户:
DROP USER ''@'localhost'是上线必做项,否则空用户名可无密码登录 - 应用账号绝不能出现在
mysql.user表的Super_priv='Y'行中——那是 DBA 应急兜底用的,不是日常连接该碰的
角色默认不激活,SET DEFAULT ROLE 后仍需确认应用连接池是否支持自动识别;列级权限虽可用,但 ORM 自动生成的语句常绕过它,真要保密码字段,得靠应用层加密或数据库代理层过滤。


















