空密码高危用户需查 authentication_string 字段为空或为 '',用 LENGTH(TRIM(authentication_string)) = 0 或 IN ('', '') 判定,结合 plugin 字段加固处理。

authentication_string 字段为空或为 '*' 就是没设密码的高危用户,直接查这个字段最准——不依赖插件类型、不触发登录、也不需要猜明文。
为什么不能用 PASSWORD 字段或明文比对
MySQL 5.7 起已弃用 Password 字段,8.0 完全移除;所有密码都存进 authentication_string,且是插件生成的哈希值(比如 mysql_native_password 和 caching_sha2_password 对同一明文输出完全不同)。试图用 WHERE authentication_string = '123456' 这种写法永远查不到结果。
authentication_string 值为 ''(空字符串)或 '*' 才真正代表无密码——前者是显式空,后者常见于旧版迁移后未重置的占位符。
-
TRIM(authentication_string)必须加,防止攻击者用空格填充伪装成“非空” -
LENGTH(TRIM(authentication_string)) = 0比= ''更鲁棒 - 只查
authentication_string IN ('', '*')不够,得结合plugin字段看后续怎么加固
查空密码用户的最小安全 SQL
必须用具备 SELECT ON mysql.* 权限的账号(如 root)执行:
SELECT host, user, plugin, authentication_string
FROM mysql.user
WHERE LENGTH(TRIM(authentication_string)) = 0
OR authentication_string IN ('', '*');
- 结果中若出现
host = '%'且user非'root',大概率是误配,应删掉或限制主机范围 -
plugin字段必须一并返回:后续重置密码时,caching_sha2_password用户不能用SET PASSWORD直接赋值,得用ALTER USER ... IDENTIFIED WITH caching_sha2_password BY 'xxx' - 这条语句不会锁表,但建议在低峰期执行,避免和并发 DDL 冲突
查到之后不能直接删,得先确认用途
空密码用户未必都能删。有些可能是监控探针、备份脚本或遗留服务在用,贸然删除会导致业务中断。
- 先用
SHOW GRANTS FOR 'username'@'host';看权限是否极小(比如只对某张表有SELECT) - 检查连接来源:
SELECT * FROM performance_schema.threads WHERE PROCESSLIST_USER = 'username';看最近是否有活跃连接 - 查错误日志:
grep -i "Access denied" /var/log/mysql/error.log | tail -20,确认是否有人正尝试用该账号登录 - 真要删,用
DROP USER 'user'@'host';,别用DELETE FROM mysql.user—— 后者不刷新权限缓存,且可能破坏元数据一致性
真正难的不是找出空密码用户,而是判断它背后有没有没人认领的定时任务、容器健康检查或离线脚本在静默调用。一个 ''@'localhost' 看似无害,但可能是某个 Python 脚本硬编码了空密码连本地库——这种依赖藏得深,删了就报错。











