不能。mysql权限匹配遵循“最长前缀优先”,host字段含'%'等通配符易被绕过;'localhost'与'127.0.0.1'权限不互通;db表通配符需用户有全局权限才生效,且按字典序匹配首条;grant option可能隐式存在;role机制存在链式继承风险。

user 表里 host 字段不是通配符就能防住越权?
不能。MySQL 的权限匹配是“最长前缀优先”,host 字段用 '%' 或 '192.168.%' 这类模糊值时,只要客户端 IP 能被匹配上,就可能绕过你预期的限制。更危险的是,'localhost' 和 '127.0.0.1' 在 user 表里是两条完全独立的记录——前者走 Unix socket,后者走 TCP,权限不互通。
实操建议:
- 检查
SELECT Host,User,authentication_string FROM mysql.user WHERE Host LIKE '%';,把所有带'%'的Host记录拉出来人工核对,尤其是root用户 - 禁止在生产库的
user表中给非 DBA 用户配置Host = '%';必须开放时,用具体网段如'10.20.30.%'替代 -
localhost权限不能当127.0.0.1用:应用连接字符串写127.0.0.1却只在user表里给了localhost权限,会直接连不上——这不是越权,是误配,但常被当成权限问题排查半天
db 表的 Db 字段支持模式匹配,但权限叠加逻辑很反直觉
mysql.db 表控制数据库级权限,Db 字段支持 'test_%' 这样的通配符,但它的生效前提是:用户必须先在 user 表里有任意全局权限(哪怕只是 USAGE),否则整条 db 记录压根不参与匹配。
常见错误现象:
- 用户 A 在
user表里只有USAGE,在db表里配了Db = 'app_%'和Select_priv = 'Y',结果连USE app_v2都被拒绝 - 同一用户在
db表里有两条记录:Db = 'prod'(Select_priv = 'N')和Db = '%'(Select_priv = 'Y'),实际执行SELECT时仍被拒绝——因为 MySQL 不做“取并集”,而是按匹配顺序选第一条(文档没明说,但实测是字典序升序匹配)
实操建议:
- 运行
SELECT User,Host,Db,Select_priv,Insert_priv FROM mysql.db ORDER BY Db;,确认没有Db = '%'这种宽泛记录盖住了更具体的规则 - 避免混用通配符和字面量:比如既有
Db = 'crm'又有Db = 'crm_%',MySQL 会优先匹配字面量,后面那条可能永远不生效
GRANT 语句不显式指定 WITH GRANT OPTION 就绝对安全?
不安全。即使你没加 WITH GRANT OPTION,只要用户已有 GRANT OPTION 权限(比如通过 user 表直接 UPDATE 过 Grant_priv = 'Y'),他就能给自己或其他人授予权限。而 SHOW GRANTS FOR 'u'@'h' 默认不显示 GRANT OPTION,容易漏看。
实操建议:
- 查权限时必须用
SHOW GRANTS FOR 'u'@'h' USING '*';(MySQL 8.0+)或手动查SELECT Grant_priv FROM mysql.user WHERE User='u' AND Host='h'; - 定期跑脚本检查:
SELECT User,Host FROM mysql.user WHERE Grant_priv = 'Y' AND User NOT IN ('dba','backup');,非必要账号一律清掉Grant_priv - 别信应用层“只读账号”说法——只要该账号在
user表里Grant_priv = 'Y',它就能执行GRANT SELECT ON *.* TO ...,进而访问任意库
MySQL 8.0 的 roles 机制反而放大了权限继承风险
role 是权限集合,但角色本身可以被授予给其他角色,形成链式继承。一旦某个基础 role(比如 base_reader)被意外赋予了 CREATE VIEW 或 EXECUTE,所有依赖它的 role 和用户都会间接获得这些能力,且 SHOW GRANTS 默认不展开 role 内容。
实操建议:
- 查角色权限必须用
SHOW GRANTS FOR 'role_name'@'%';,而不是只查用户 - 禁用嵌套授权:执行
SELECT FROM mysql.role_edges;,发现TO_HOST列不是'%'或FROM_HOST存在非空值,说明有角色授给角色,立刻拆解 - role 命名要有约束力:比如
app_finance_ro末尾的_ro必须对应真实权限集,上线前用SELECT * FROM mysql.role_edges e JOIN mysql.role_edges e2 ON e.TO_USER=e2.FROM_USER;扫描传递链
权限表不是静态快照,是运行时动态匹配的规则集。真正危险的不是某条 SQL 能执行,而是你根本不知道哪条记录正在悄悄放行它。











