应优先查询mysql.user表中grant_priv、super_priv、file_priv为'y'的账号,并特别标记host为'%'的高危账号;再结合performance_schema或系统日志识别长期未登录的闲置高权限账号予以回收。

如何用SQL快速识别高危权限账号
MySQL里最危险的不是密码弱,而是账号被误授了GRANT OPTION、SUPER、FILE或全库ALL PRIVILEGES。巡检第一件事就是查出这些账号:
-
SELECT user, host, privilege_type FROM information_schema.role_table_grants WHERE privilege_type IN ('GRANT OPTION', 'SUPER', 'FILE')(适用于8.0+角色模型) -
SELECT User, Host, Select_priv, Insert_priv, Update_priv, Delete_priv, Grant_priv, Super_priv, File_priv FROM mysql.user WHERE Grant_priv = 'Y' OR Super_priv = 'Y' OR File_priv = 'Y'(兼容5.7/8.0,直接查mysql.user) - 特别注意
Host为'%'的账号——它允许从任意IP连接,比'localhost'风险高得多
哪些账号该被标记为“闲置”并回收
长期不用但拥有高权限的账号,是攻击者最爱的跳板。MySQL本身不记录登录时间,但可以通过以下方式交叉判断:
- 检查
performance_schema.events_statements_summary_by_account_by_event_name(需开启performance_schema且consumers启用events_statements_summary_by_account_by_event_name) - 结合系统日志:
grep 'Connect' /var/log/mysqld.log | awk '{print $3}' | sort | uniq -c(前提是启用了general_log或log_error_verbosity=3) - 更可靠的做法:在巡检脚本中加一步
SELECT user, host FROM mysql.user WHERE account_locked = 'N' AND password_last_changed ,再人工确认是否真闲置
为什么SHOW GRANTS FOR 'user'@'host'不能代替权限表扫描
这个命令只显示当前生效的权限合并结果,会掩盖两个关键风险:
- 角色(Role)叠加权限:用户A被赋予角色R,R有
DROP权限,但SHOW GRANTS只显示ROLE R,不展开R的实际权限 - 库/表级权限覆盖:用户在
mysql库有SELECT,但在test.*有ALL,SHOW GRANTS会分两行显示,容易漏看第二行 - 真正要巡检的是底层授权表:
mysql.db、mysql.tables_priv、mysql.columns_priv——它们才是权限生效的唯一依据
自动化巡检脚本必须避开的三个坑
写定时任务跑SQL很容易,但实际部署时常见三类失效:
- 权限不足:脚本用的账号没查
mysql.user的权限,报错Access denied for SELECT on mysql.user。解决方法:用root或显式授权SELECT ON mysql.* - 字符集混乱:脚本输出含中文注释或用户名,若终端或重定向文件编码是
latin1,会导致???乱码,进而让grep漏匹配关键词。统一用SET NAMES utf8mb4开头 - 未处理空值:比如
max_connections字段为NULL时,WHERE max_connections > 100会跳过该行。巡检条件里所有数值比较都要加IS NOT NULL
复杂点不在SQL多难写,而在每次上线前得确认目标实例的版本、日志开关状态、字符集配置——漏一项,巡检报告就可能漏掉一个能删库的账号。










