nginx网关层拦截sql注入是更早、更轻量的第一道防线,可在请求到达后端前用正则匹配$args和$request_uri并return 403,跳过全部业务逻辑与数据库调用,但仅能拦截显性攻击特征,需配合解码一致性、限流及ua封禁等策略才有效。

网关层拦截比后端代码过滤更早、更轻量
请求到达后端应用前,Nginx 已完成解析和转发;在这一阶段做正则匹配,不触发业务逻辑、不加载 Spring Boot 或 Django 等框架栈,CPU 和内存开销极低。一旦 return 403,连接直接断开,后续所有中间件、DAO 层、数据库连接全被跳过——这是真正意义上的“第一道防线”。
常见错误现象:后端写了个全局 Filter 做 SQL 关键字扫描,结果高并发下 GC 频繁、线程阻塞,而 Nginx 日志里早已看到大量 union select 请求被 499(客户端主动断开)或 403 拦截。说明防御点太靠后,既没拦住攻击流量,又拖垮了业务。
- 不要依赖“后端统一拦截器”作为主防手段,它本质是补救,不是拦截
- Nginx 的
map+if是无状态匹配,毫秒级响应;Java/Python 的字符串遍历+正则引擎要消耗堆内存和 GC 周期 - 若用
body_filter_by_lua或 ModSecurity 做请求体检测,注意开启client_body_buffer_size限制,否则大文件上传会吃光内存
绕过 WAF 的关键路径往往不在后端,而在网关与后端的解析差异
很多“WAF 已开启却仍被注入”的案例,根源是 Nginx 和后端对同一段 URL 或 POST 数据的解码顺序不同。比如:
攻击者发 /search?q=%2555NION%2520SEL%2545CT(双重 URL 编码),Nginx 默认只解一层,变成 /search?q=%55NION%20SEL%45CT,再交给后端;后端 Tomcat 或 Flask 再解一次,还原出 UNION SELECT——而 WAF 规则若只匹配原始字符串,就漏掉了。
- 必须在 Nginx 中显式配置
rewrite ^(.*)$ $1 break;配合set $args重写参数,确保规则作用于最终解码后的值 - 对
$request_uri和$args分别建map,不能只查其中一个;union select可能藏在路径里(如/api?callback=xxx),也可能在 body 里 - 宝塔面板的 WAF 若未勾选“深度解码检测”,默认只解一层,需手动开启或改用原生 Nginx 规则
网关规则必须配合限流和 UA 封禁,否则单靠关键词毫无意义
只匹配 select、union 这类词,等于给攻击者发练习题。真实攻击中,SeLeCt、%75nion、/**/UNION/**/SELECT、`version()` 都能绕过简单正则。
- 单独的关键词规则只能挡住脚本小子,不能防专业渗透;必须叠加
limit_req_zone $binary_remote_addr zone=sql_att:10m rate=1r/s,对高频触发者直接限速 - 封禁典型扫描工具 UA:
curl、sqlmap、python-requests、masscan,但注意排除合法爬虫(如百度 UA 以Baiduspider开头) - 禁止空
User-Agent和超长Referer(>200 字符),这两类请求 90% 是自动化探测
规则写在 http 块还是 server 块,直接影响生效范围和维护成本
把 map 写在 http 块里,所有 server 都能复用;写在 server 块里,每个站点要重复定义。但后者更安全:某业务需要传 select 作为字段名(如商品 “Select Edition”),你可以在该 server 下关闭对应 map,而不影响其他站点。
- 推荐结构:
http块定义通用高危模式(如sleep(、benchmark(、load_file),server块内用if ($sql_inj_args = 1) { return 403; }控制开关 - 避免在
location外直接写if ($args ~ ...),Nginx 官方明确警告这种用法有副作用,应统一收口到map+if组合 - 每次修改后必须执行
nginx -t && nginx -s reload,宝塔用户注意点击【重载配置】而非仅保存











