MySQL库名中的%和_是通配符,未转义时会按LIKE规则匹配,如app%授权所有以app开头的库,db_1匹配任意单字符,必须双写反斜杠转义为db\\_1才能表示字面量。

MySQL库名里的%和_是通配符,不是字面量
执行 GRANT SELECT ON `app%`.* TO 'u'@'%' 时,MySQL 不会把它当作一个叫 app% 的库名,而是按 LIKE 规则做模式匹配——它实际授权的是所有以 app 开头的库:app_db、app123、app-test、app0x 全算。这不是疏忽,是权限解析器的默认行为。
下划线 _ 同理:GRANT SELECT ON `db_1`.* 匹配的是 db01、dba1、db-1,因为 _ 表示“任意单字符”。不转义就等于主动扩大权限边界。
- 必须写成
`db_1`(反斜杠转义),且 SQL 字符串中要双写反斜杠,即实际执行为GRANT SELECT ON `db\_1`.* - 用
SHOW GRANTS FOR 'u'@'%'确认输出里显示的是`db_1`,而不是`db_1`,才算生效 - 哪怕库名里只含一个
_或%,只要没转义,就已开启通配匹配
撤销权限时通配符必须完全一致,否则静默失败
如果当初用 GRANT SELECT ON `db_1`.* TO 'u'@'%' 授权,那撤销时必须严格写 REVOKE SELECT ON `db_1`.* FROM 'u'@'%'。漏掉反斜杠、写成 `db_1`、或主机名部分不一致(比如原是 'u'@'192.168.%' 却写成 'u'@'%'),MySQL 都不会报错,也不会撤销——权限继续生效,你却以为已经清掉了。
- 执行
REVOKE前,务必先运行SHOW GRANTS FOR 'u'@'%',复制粘贴输出中的完整对象名(含转义符号) - 撤销后立刻再查一遍
SHOW GRANTS,确认对应行已消失 - 不要手动重写语句,复制粘贴是最安全的做法
通配库名 + 通配主机名 = 权限爆炸式扩散
'u'@'%' AND `app%`.* 这种组合,相当于同时放开两个维度的模糊匹配:任意 IP 可连 + 任意以 app 开头的库可读。一旦数据库暴露在边界网络,攻击者只要拿到账号密码,就能遍历所有匹配库,包括本不该访问的测试库、备份库、甚至其他租户的库(如 app_tenant_a、app_tenant_b)。
- 生产环境禁止
GRANT ... ON `app%`.*,必须明确写出库名并加反引号:`app_db` - 真需批量覆盖,先用
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME LIKE 'app%'查出实际匹配列表,人工核对 - 主机名也一样:
'u'@'192.168.5.%'比'u'@'%'安全得多,但前提是该子网内无其他非信任节点
权限叠加让问题更难察觉
一个用户可能同时有 GRANT SELECT ON `app%`.* 和 GRANT SELECT ON `prod_orders`.*,而 prod_orders 正好匹配 app% 模式——这时你删掉后者,权限依然存在。更麻烦的是,MySQL 不会告诉你哪些权限来自通配匹配,SHOW GRANTS 只显示最终合并结果。
- 查隐式权限:运行
SELECT Db, Select_priv FROM mysql.db WHERE User='u' AND Host='%',看是否有未预期的库级记录 - 避免混用通配与精确授权,尤其是跨版本迁移后,老权限可能残留
- 最稳妥的做法:删除所有含通配符的授权,重建为精确库名 + 精确主机名
真正危险的不是用了通配符,而是不知道它正在匹配什么。每次写 GRANT 或 REVOKE,只要对象名里出现 % 或 _,就必须停下来确认是否已转义、是否必要、是否可被枚举验证。


















