应使用 ngx_waf 或 modsecurity 等成熟 waf 模块而非原生 if + 正则拦截 sql 注入,因后者无法读取 post 请求体、易误杀绕过、性能差;ngx_waf 编译后一行启用,内置编码归一化与规则更新能力,但最终需结合应用层预处理与最小权限加固。

直接在 Nginx 原生配置里写 if + 正则拦截 SQL 注入,效果有限且容易误杀或被绕过;真正能落地、低维护、有规则更新能力的方案,是用成熟第三方 WAF 模块,比如 ngx_waf 或 ModSecurity。
为什么不用原生 if + $args 正则做主防线
原生 Nginx 的 if 指令在 location 外大量使用会引发重写逻辑混乱,且 $request_body 在标准模块中不可读——这意味着 POST 请求里的 SQL payload(如 {"q":"1' OR '1'='1"})完全无法检测。常见错误现象包括:
-
if ($args ~* "union select")对UNION%20SELECT或大小写混写(UnIoN)无效 - 用
.*宽泛匹配(如select.*from)导致/api/select_user被误拦 - 正则中未用
\b边界符,把user_id=123里的id当成攻击词 - 高并发下频繁执行 PCRE 匹配,CPU 使用率异常升高
推荐方案:用 ngx_waf 模块零代码启用防护
ngx_waf 是 C 编写的轻量级模块,不依赖 Lua,编译进 Nginx 后只需一行配置即可开启默认规则集,自动覆盖 SQLi、XSS、目录遍历等 OWASP Top 10 攻击特征。实操建议如下:
- 从 GitHub 获取最新 release(如
v2.5.0),按文档用--add-module重新编译 Nginx - 在
http或server块中添加:waf on;<br>waf_rule_path /usr/local/nginx/conf/waf-rules/;
- 规则路径下默认含
sql_injection.rule,已预置 Unicode 编码解码、大小写归一化、空格/注释符归并等处理逻辑 - 拦截时返回
403并记录日志到error_log,字段含攻击类型、匹配规则 ID、原始 URI
如果必须用 Lua(OpenResty 环境)
当已有 OpenResty 且需细粒度控制时,可用 access_by_lua* 钩子,但要注意关键限制:
- 不要只检查
ngx.var.request_uri,必须同时解析ngx.var.request_body(需开启client_body_buffer_size和client_max_body_size) - 避免在
access_by_lua中做耗时操作(如 HTTP 请求、文件读取),否则阻塞 worker 进程 - 示例安全写法:
access_by_lua_block {<br> local uri = ngx.var.request_uri<br> local args = ngx.var.args<br> local body = ngx.var.request_body or ""<br> if uri:match("[Uu][Nn][Ii][Oo][Nn]%s+[Ss][Ee][Ll][Ee][Cc][Tt]")<br> or args:match("'%s*[Aa][Nn][Dd]%s*'%d+%s*=%s*'%d+")<br> or body:match("exec%s+xp_") then<br> ngx.exit(403)<br> end<br>} - 注意:Lua 字符串匹配不自动忽略 URL 编码,
%20不会被当成空格,需先调用ngx.unescape_uri()解码后再匹配
最易被忽略的一点:无论用哪种方式,WAF 规则都只是第一道过滤网。真正的防御纵深在于应用层参数绑定(如 PDO 预处理)、最小权限数据库账户、以及对所有用户输入做白名单校验——Nginx 层的拦截,本质是买时间、压流量、挡脚本小子,不是替代后端安全设计。











