应执行SELECT user, host, Super_priv, Grant_priv FROM mysql.user WHERE Super_priv = 'Y' OR Grant_priv = 'Y'定位高危账号,因Super_priv可绕过SQL限制,Grant_priv能扩散权限,非DBA账号持有即属风险;需结合SHOW GRANTS、role_edges及动态权限视图交叉验证,避免遗漏角色继承与隐性权限。

查哪些账号拥有Super_priv或Grant_priv权限
这两个权限是权限失控的根源:Super_priv能绕过所有SQL限制(比如kill任意连接、修改只读模式),Grant_priv允许把已有权限再授给他人,形成不可控的权限链。别只盯着root,任何业务账号带这两个权限都算高危。
执行这条语句快速定位:
SELECT user, host, Super_priv, Grant_priv FROM mysql.user WHERE Super_priv = 'Y' OR Grant_priv = 'Y';
注意:Super_priv和Grant_priv字段值是字符型'Y'/'N',不是布尔值,不能写= 1;结果里如果出现'app_user'@'192.168.1.%'这种非DBA账号,必须立刻处理。
检查是否对*.*或整个库授予了ALL PRIVILEGES
业务账号一旦拿到ON *.*或ON database.*的ALL PRIVILEGES,就等于拿到了该范围内的DROP、CREATE、FILE、PROCESS等所有能力——而绝大多数应用只需要几条DML语句。
用SHOW GRANTS逐个验证比查表更可靠,因为角色继承、动态权限不会体现在mysql.user里:
- 对每个可疑账号运行:
SHOW GRANTS FOR 'app_user'@'10.10.20.%'; - 重点看输出里有没有
GRANT ALL PRIVILEGES ON *.*或WITH GRANT OPTION - 如果有,立即回收:
REVOKE ALL PRIVILEGES ON *.* FROM 'app_user'@'10.10.20.%';,再按需重授最小集
别信GRANT USAGE是“空权限”就安全——它只是占位符,不阻止后续叠加授权,真正干净的状态是SHOW GRANTS只返回USAGE且无其他GRANT行。
识别host为'%'且DML权限≥3的组合
宽泛的host = '%'本身不危险,但配上高权限就是放大器。攻击者只要拿到密码,就能从任意IP发起破坏性操作。
用这个查询抓出风险组合:
SELECT user, host, CONCAT('SHOW GRANTS FOR ''', user, '''@''', host, ''';') AS review_cmd FROM mysql.user WHERE host = '%' AND (Select_priv = 'Y' AND Insert_priv = 'Y' AND Update_priv = 'Y');
它会生成可直接执行的SHOW GRANTS命令,方便批量审查。收缩方案不是删账号,而是改host字段:RENAME USER 'app'@'%' TO 'app'@'192.168.5.%';,哪怕只限一个网段,攻击面也大幅缩小。
别忽略角色和动态权限带来的隐性权限
MySQL 8.0+里,SHOW GRANTS FOR 'u'@'h'默认不显示角色继承的权限,也不包含动态权限(如BACKUP_ADMIN、CLONE_ADMIN)。你以为回收了权限,实际还挂在角色上。
必须补查两处:
- 角色继承关系:
SELECT * FROM mysql.role_edges WHERE TO_USER = 'app_user' AND TO_HOST = '10.10.20.%'; - 动态权限:
SELECT * FROM mysql.role_edges WHERE FROM_HOST = 'backup_role';(先查角色归属,再查角色本身权限) - 当前用户实际拥有的动态权限:
SELECT * FROM performance_schema.role_table_grants WHERE GRANTEE = "'app_user'@'10.10.20.%'";
角色不能嵌套(MySQL 8.0.16前),设计时避免role_a → role_b → role_c链式依赖;回收权限优先REVOKE角色,而不是单个用户——否则新建同名用户会意外继承旧角色。











