nginx原生配置只能通过$request_uri和$args拦截sql注入,$request_body不可读;须用~*忽略大小写,转义特殊字符并加\b单词边界,规则应置于location块内配合return 403立即终止。

只匹配 $request_uri 和 $args,别碰 $request_body
Nginx 原生配置里根本读不到 POST 请求体内容,$request_body 在普通 if 或 rewrite 上始终为空——这不是 bug,是设计限制。所有试图用 if ($request_body ~* "...") 拦截表单提交里的 ' or 1=1 的写法,一律无效。
真正能用的只有两个变量:
-
$request_uri:含原始路径 + 查询参数,如/api/user?id=1' and 1=1 -
$args:仅查询参数部分,如id=1' and 1=1
如果你的业务大量依赖 POST 表单(比如登录、搜索),单靠这两个变量只能拦住 URL 上明写的攻击,POST body 里的 payload 完全漏过。
正则必须用 ~*,且要转义点号、括号、竖线
写 if ($args ~ "union select") 是错的:没加 * 就不忽略大小写,Union Select 就绕过了;没转义 .,select. 会误匹配 select_id 这类合法字段名;没转义 |,union|select 会被当成分隔符而非字面量。
正确写法示例(放在 server 或 location 块内):
if ($args ~* "(%27)|(\')|(--)|(%23)|(#)|(\b(SELECT|INSERT|UPDATE|DELETE|UNION|DROP|CREATE|ALTER|EXEC|CAST|CONVERT)\b)") {
return 403;
}
注意:\b 是单词边界,防止把 user_select 当成 select 拦掉;%27 和 %23 分别对应 URL 编码后的单引号和井号,不能漏。
别把 if 写在 location 外还指望它生效
if 指令在 Nginx 里不是“全局条件判断”,它的作用域严格受限于所在块。常见错误:
- 在
http块里写if,但没配break或return,结果请求照常进入下游location - 在
server块顶部写if,但匹配后用了rewrite ... break,反而触发重写逻辑,可能被绕过 - 把规则写在
location /static里,却想拦/api/login的请求——路径不匹配,规则压根不执行
最稳妥的做法:把 if + return 403 放进具体业务 location 块顶部,例如:
location /api/ {
if ($args ~* "...") { return 403; }
proxy_pass http://backend;
}
用 return 403,别用 rewrite ... redirect 或 deny all
rewrite 触发重定向或内部跳转,攻击者可能利用跳转链绕过检测;deny all 只对 IP 生效,对请求内容无感;而 return 403 是立即终止响应,不进后续流程,最干净。
但要注意两点:
- 如果用了
proxy_pass,return发生在代理前,不会产生上游日志,得靠 Nginx access log 确认拦截是否生效 - 某些客户端(尤其爬虫)会忽略 403 继续发包,所以必须配合日志监控,看
status=403是否集中出现在某几个 pattern 上
真正难防的从来不是 union select 这种明文,而是 %u0027、sel/**/ect、1e0=1 这类变形。Nginx 正则只做字符串扫描,解码、归一化、语义还原,它干不了。别让它承担它扛不起的责任。











