WAF 能拦截部分 SQL 注入但无法覆盖所有场景,因其依赖规则匹配,对编码绕过、JSON body、HTTP/2 等支持有限;根本防御须靠应用层参数化查询与白名单校验。

WAF 能拦住 SQL 注入,但拦不住所有 SQL 注入
WAF 是最后一道网关,不是万能补丁。它靠规则匹配常见注入特征(比如 ' OR 1=1 --、UNION SELECT),对编码绕过、注释变形、HTTP 参数污染、JSON body 中的恶意 payload 往往失效。真实攻击者早就不写裸 SELECT * FROM users WHERE id = '1' OR '1'='1' 了。
- WAF 规则默认只检查
GET和POST的 query string 与 form data,不检查Content-Type: application/json的 request body —— 除非你手动开启 JSON 解析策略 - 某些 WAF(如 Cloudflare 默认规则集)对 base64 编码、URL 二次编码、大小写混用(
SeLeCt)识别率低 - 自定义规则写得太宽会误杀(比如拦截所有含
union的请求,误伤搜索关键词“union station”);写得太窄又漏掉information_schema或pg_sleep()这类盲注探测
SQLi 防御必须从应用层落地,WAF 只是兜底
真正防住 SQL 注入,核心就一条:绝不拼接用户输入到 SQL 字符串里。WAF 再强,也救不了 query = "SELECT * FROM users WHERE name = '" + req.query.name + "'" 这种写法。
- 优先用参数化查询:
db.query("SELECT * FROM users WHERE id = ?", [req.params.id])(Node.js/mysql2)、cursor.execute("SELECT * FROM users WHERE email = %s", (email,))(Python/psycopg2) - 避免 ORM 的原始 SQL 接口:Django 的
raw()、SQLAlchemy 的text()、MyBatis 的${}占位符(不是#{})都等同于自己造注入点 - 对动态表名/列名这类无法参数化的场景,必须白名单校验:
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', table_name): raise ValueError("Invalid table name")
启用 WAF 拦截前,先确认日志和放行逻辑是否可靠
很多团队一开 WAF 就报错 403,结果发现是监控探针、前端埋点、或内部健康检查请求被误杀——不是规则不准,是没看清流量来源。
- 上线前务必开启 WAF 的「日志记录」+「学习模式」(如 AWS WAF 的
Count模式、阿里云 WAF 的「观察模式」),跑 2–3 天真实流量,再分析RuleID和触发路径 - 把已知合法流量加进 IP 白名单或 URI 白名单:比如
/healthz、/metrics、运维网段的/api/v1/admin/* - 注意 WAF 对 HTTP/2 和 WebSocket 的支持差异:部分老版本 WAF 不解析
h2header,导致Cookie或Authorization字段漏检
生产环境 WAF 规则不能照搬开源模板
直接套用 GitHub 上的 owasp-crs 或 modsecurity-rules 主干配置,在生产里大概率出事。CRS v4 默认规则过于激进,尤其对中文、emoji、GraphQL 请求体兼容性差。
- 禁用高误报规则:比如 CRS 的
942100(SQL 注入:布尔型)在处理带AND/OR的搜索框时极易误判,建议降级为Log而非Block - 关闭对静态资源的 SQLi 检查:WAF 不该扫描
.js、.css、.png请求,否则徒增 CPU 开销 - 若后端用了 GraphQL,需单独配置规则匹配
{"query":"..."}结构,而不是依赖传统 URL 参数检测
WAF 是防御纵深里的一环,但它不会告诉你哪行代码漏了 escape(),也不会帮你修那个没加 limit 的分页查询。真要加固,得从最靠近数据库的地方开始动刀子。










