nginx仅能基于$args和$request_uri做字符串粗筛,无法解析sql语义或读取post体;应使用\b边界匹配、%20编码形式等正则规则配合return 403拦截,同时必须配合pdo预处理、最小权限账号等应用层防护。

Nginx 本身不解析 SQL,也不能读取 POST 请求体(除非启用 Lua 或 ModSecurity),所以它只能基于 URL 路径和查询参数做字符串层面的粗筛。这不是万能盾,而是最外层的“安检门”——拦住明摆着的攻击,比如 id=1%20union%20select 或 ?q=' or 1=1--,但对编码绕过、大小写混淆、盲注或 JSON body 里的 payload 无能为力。
真正有效的做法是:用正则匹配 $args 和 $request_uri 中的典型攻击特征,配合 return 403 立即终止请求,同时明确知道它的边界在哪里。
只对 $args 和 $request_uri 做匹配
Nginx 默认拿不到 $request_body,所以所有规则必须围绕这两个变量设计:
-
$args:已解码的查询参数(如id=1%20or%201=1→ 匹配or 1=1) -
$request_uri:原始 URI(含编码,如/api?id=1%20union%20select→ 需匹配%20union%20select或union[[:space:]]+select)
别写 if ($request_body ~ ...) { ... } —— 普通配置下它为空,规则完全无效。
实用、低误杀的正则写法
放在 server 或具体 location 块内,用 return 403 终止:
if ($args ~* "(%27)|(')|(--)|(%23)|(#)|\b(select|union|insert|update|delete|drop|exec|declare|sleep|benchmark)\b") {
return 403;
}
if ($request_uri ~* "(%20OR%20|%20AND%20|/\*|\*/|;\s*(select|union)\s+from|information_schema)") {
return 403;
}
关键细节:
- 用
\b匹配单词边界,避免把user_id里的id当成危险词 -
%20OR%20比OR更可靠,因空格常被 URL 编码 - 不匹配
order by、limit、group by等合法关键词 -
~*表示忽略大小写,防UnIoN SeLeCt这类变形
必须避开的常见坑
这些错误会让规则形同虚设:
- 把
if写在http块顶层,没限定作用域,导致不生效或重复触发 - 在
location ~ \.php$里写规则,但攻击请求走的是/index.php?id=xxx,$args已解码,而正则却按 raw 字符匹配 - 用
rewrite ... break替代return 403,可能被重定向绕过 - 正则里漏转义括号、点号、星号等特殊字符,实际根本没匹配上
更该优先做的事
比起折腾 Nginx 规则,以下几件事防护效果更直接、更根本:
- PHP 层全部使用 PDO 预处理语句,参数用
bindValue()绑定,杜绝 SQL 拼接 - MySQL 创建专用账号,只给必要权限(如仅
SELECT,INSERT),禁用DROP,EXEC,LOAD_FILE - 禁用 PHP 危险函数:
eval(),system(),exec(),passthru() - 开启
modsecurity并加载 OWASP CRS 规则集(需编译安装,适合中高安全要求场景)
不复杂但容易忽略











