'root'@'%'是内网提权的黄金入口,因其host='%'不校验ip真实性且常具super_priv权限,攻击者可从任意内网机器直连执行高危操作;须用drop user彻底删除并验证全表清理,配合bind-address与防火墙才构成安全闭环。

为什么'root'@'%'是内网提权的黄金入口
不是所有%都一样危险,但'root'@'%'几乎等于把数据库 root 密码贴在内网交换机上。攻击者只要拿下任意一台内网机器(比如一台被 XSS 或 RCE 漏洞攻陷的 Web 服务器),就能直接连 MySQL 执行SELECT LOAD_FILE('/etc/shadow')、写 WebShell 或调用SYS_EXEC执行系统命令——根本不需要爆破密码,也不依赖外网出口。
关键点在于:host = '%'不校验来源 IP 真实性,而内网环境普遍缺乏源地址过滤(如 ARP 欺骗、容器 overlay 网络、云主机多网卡混用),伪造192.168.1.100这种 IP 成本极低。
检查是否已存在'root'@'%'并确认其权限状态
别只看user表,要查真实生效的权限组合:
SELECT User, Host, Super_priv, Grant_priv, account_locked FROM mysql.user WHERE User = 'root' AND Host = '%';
- 如果
Super_priv = 'Y',说明该账号能执行SHUTDOWN、修改全局变量、绕过所有权限检查——这是最高危组合 - 如果
account_locked = 'Y'但password_last_changed超过 180 天,且last_seen为空(需开启performance_schema.accounts),说明该账号长期未用却仍保留在权限链中,属于“僵尸高危账户” - 若结果为空,但仍有其他用户如
'dba'@'%'具备Super_priv,同样需立即处理
删除必须用DROP USER,禁用UPDATE mysql.user
很多人想省事,直接执行:
UPDATE mysql.user SET Host = '192.168.1.100' WHERE User = 'root' AND Host = '%'; FLUSH PRIVILEGES;
这会导致三类问题:
- MySQL 8.0+ 权限缓存不会自动同步更新,旧规则可能持续生效数分钟甚至更久
-
mysql.db、mysql.tables_priv等表里仍保留Host = '%'的残留记录,新建同名用户时会冲突 - 某些版本下触发内部状态损坏,后续
GRANT操作失败或静默丢弃
正确做法只有一条:
DROP USER 'root'@'%';
若不确定是否还有其他依赖,先备份:
SHOW GRANTS FOR 'root'@'%';
删完必须验证全链路清理效果
执行DROP USER后,不能只查mysql.user表就认为完事。必须确认关联权限表也清空:
SELECT Host FROM mysql.user WHERE Host = '%'; —— 应返回空集
SELECT Host FROM mysql.db WHERE Host = '%'; —— 同样应为空
SELECT Host FROM mysql.tables_priv WHERE Host = '%';
只要其中任一表仍有Host = '%',说明之前有人手动改过表,或使用过DELETE FROM mysql.user——此时建议重建权限表,或升级到 MySQL 8.0.32+(修复了部分孤儿权限残留逻辑)。
真正安全的闭环,是'root'@'%'消失 + bind-address = 127.0.0.1 + 防火墙仅放行应用服务器 IP 段。少一个环节,%就只是换了个姿势继续裸奔。











