MySQL不支持CIDR,仅能通过'host'@'192.168.1.%'实现字符串前缀匹配(非子网计算),需配合iptables防火墙规则(-I INPUT -p tcp --dport 3306 -s 192.168.1.0/24 -j DROP)及bind-address与skip_name_resolve协同配置。

MySQL 本身不支持 CIDR 或子网掩码语法,单靠 CREATE USER 或 GRANT 指定 'user'@'192.168.1.0/24' 是无效的——它会被当作文本字面量匹配,而客户端发来的是 192.168.1.42,两者完全不等价。
MySQL 的 Host 字段只认 % 和 _,不解析 IP 段
MySQL 权限系统对 Host 字段做纯字符串模式匹配,不是网络层子网计算。写 'app'@'192.168.1.%' 是唯一可行的“C 段近似”,但要注意:
-
%匹配任意长度字符,所以192.168.1.abc.example.com、192.168.1.999(MySQL 静默截断为192.168.1.99)也会被放行 -
'app'@'192.168.%'会匹配192.168.10.5、192.168.100.200,远超预期范围 - 不能对已有
'app'@'%'执行REVOKE ... FROM 'app'@'192.168.1.%'——权限表里根本不存在这条记录 - 正确做法是先
DROP USER 'app'@'%',再CREATE USER 'app'@'192.168.1.%'并GRANT
iptables 必须用 -I 插入链首并限定 --dport 3306
MySQL 在 TCP SYN 阶段就响应连接请求,防火墙规则若位置靠后或未指定端口,攻击者仍能探测服务存在。常见错误:
-
iptables -A INPUT -s 192.168.5.0/24 -j DROP:追加在末尾,大概率被前面的ACCEPT规则放行 -
iptables -I INPUT -s 192.168.5.0/24 -j DROP:没加--dport 3306,会误杀 SSH、HTTP 等其他服务 - 正确写法:
iptables -I INPUT -s 192.168.5.0/24 -p tcp --dport 3306 -j DROP - 验证是否生效:
telnet 192.168.5.10 3306应超时或直接拒绝;若返回Connection refused,说明 MySQL 已响应,防火墙没起作用
bind-address 和 skip_name_resolve 必须协同配置
仅改 bind-address 不等于限制 IP 访问:
-
bind-address = 0.0.0.0或*表示监听所有接口,此时全靠防火墙和user@host控制 -
bind-address = 192.168.1.10只监听该内网地址,可减少暴露面,但若服务器有多个网卡且公网接口也开了 3306,仍可能被连上 - 必须确认实际监听地址:
ss -tlnp | grep :3306,输出含*:3306或0.0.0.0:3306才需防火墙干预 -
skip_name_resolve = ON必须启用,否则 MySQL 会对客户端 IP 做 DNS 反查,导致延迟、失败,甚至因反解域名而匹配错Host
localhost 和 127.0.0.1 是两个独立账号
很多本地连接失败就栽在这里:
-
'app'@'localhost'强制走 Unix socket,'app'@'127.0.0.1'走 TCP/IP,二者权限互不影响 - 应用明确指定
host=127.0.0.1(如 Docker 容器内、某些 ORM 默认配置),但只建了'app'@'localhost',连接必然失败 - 想两者都支持,必须分别创建:
CREATE USER 'app'@'localhost'和CREATE USER 'app'@'127.0.0.1',再各自GRANT - 验证时用
SELECT USER(), CURRENT_USER():前者是客户端声明的 host,后者才是 MySQL 实际匹配的权限条目
真正安全的限制从来不是只靠一层——bind-address 控制监听范围,iptables 拦在 TCP 握手前,user@host 做账号级鉴权,三者缺一不可;最容易被忽略的是规则顺序(-I vs -A)和 DNS 反查干扰(skip_name_resolve)。


















