应执行select user, host, authentication_string from mysql.user where user = '' or authentication_string = '' or authentication_string is null;若返回任意行即存在匿名用户或空密码账号,必须立即处理。

直接查 mysql.user 表就能准确定位,不用依赖扫描工具或外部脚本。只要 User 为空字符串或 authentication_string 为空/NULL,就属于必须清理的高危账号。
怎么快速确认是否存在匿名用户或空密码账号
匿名用户指 User 列为 ''(空字符串)的记录,不是 NULL;空密码则看 authentication_string 是否为 '' 或 NULL。这两类都该一起查:
SELECT User, Host, authentication_string FROM mysql.user WHERE User = '' OR authentication_string = '' OR authentication_string IS NULL;- 注意:不能用
TRIM(User) = ''替代User = ''——虽然某些旧数据带空格,但生产环境里真正在用的匿名用户几乎全是严格空字符串,TRIM反而干扰判断 - 如果返回任意行,说明存在风险账号;哪怕只有一条
''@'localhost',也得处理
为什么 DROP USER ''@'host' 有时会失败
常见报错 ERROR 1396 (HY000): Operation DROP USER failed for ''@'host',本质是 MySQL 解析器拒绝模糊匹配,但更深层原因有三个:
- Host 值大小写不一致:查询结果里是
'LOCALHOST',你却写了''@'localhost',MySQL 8.0+ 默认校验大小写 - 引号没写对:必须是单引号包裹空字符串,
DROP USER ''@'localhost';合法,DROP USER @'localhost';或DROP USER ''@''都非法 - MySQL 版本兼容问题:5.7 某些小版本、8.0.28+ 的部分补丁对升级残留的空用户支持不稳定,此时
DROP USER会静默失败
安全删除的实操路径与关键细节
优先走 DROP USER,失败再降级用 DELETE,但条件必须收紧:
- 先逐条生成删除语句:
SELECT CONCAT('DROP USER ''', User, '''@''', Host, '''') FROM mysql.user WHERE User = '' OR authentication_string = '' OR authentication_string IS NULL; - 执行前人工核对每条语句里的
Host是否和查询结果完全一致(包括大小写、IPv6格式如'::1') - 若
DROP USER报错,改用:DELETE FROM mysql.user WHERE User = '' AND Host IN ('localhost', '127.0.0.1', '::1');——绝不无条件删所有User = '',防止误删监控用的''@'10.0.1.%' - 无论哪种方式,删完立刻
FLUSH PRIVILEGES;;MySQL 8.0+ 虽声称自动刷新,但权限缓存实际生效仍依赖这步
删完还要验证是否真生效
很多团队删完就认为结束了,结果攻击面仍在。真正要验证的是三件事:
- 重跑原始检查语句,结果必须为空;否则说明删漏了或用了错误 Host
- 本地尝试连接:
mysql -u '' -h 127.0.0.1 -P3306 -e "SELECT USER();",应报错Access denied,而非返回@127.0.0.1 - 检查关联权限表是否残留:
SELECT * FROM mysql.db WHERE User = '';,若有结果,需同步清理或重建权限库
最容易被忽略的是:有些老旧运维脚本硬编码了 -u '' -h localhost,删完后它们会突然失联,得提前排查并更新配置,而不是回滚删除操作。











