在 Nginx 请求处理管道中嵌入 Lua,用不到 1000 行核心代码实现企业级 WAF 防护——支持基于域名的差异化规则、IPv6/CIDR 黑白名单、CC 防护、云凭据探测拦截,GET 流量吞吐仅下降约 16%。
为什么需要又一个 WAF? 市面上的 WAF 方案很多:Cloudflare 等 SaaS WAF 简单但黑盒、ModSecurity 功能强大但过重、商业硬件 WAF 价格不菲。很多团队的实际诉求其实很简单:
轻量 :不想引入额外进程,直接在现有 Nginx 上挂 Lua 脚本
可控 :规则文件就是纯文本正则,随时改随时生效,不需要重启
多域名 :不同域名需要不同防护策略,而不是一刀切
高性能 :不能因为加了 WAF 就让吞吐掉一半
NginxGuard 就是围绕这几个目标设计的。
项目架构 整个项目只有 4 个 Lua 文件和一组规则文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 waf/ ├── config.lua # 全局配置(开关、日志路径、规则目录等) ├── init.lua # init_by_lua_file 预加载模块 ├── access.lua # access_by_lua_file 每请求检测入口 ├── lib.lua # 核心库(IP获取、规则加载、域名配置、日志、输出) ├── nginx-config/ │ └── nginx.conf # nginx 配置示例 └── rule-config/ ├── domain.json # 域名级规则配置 ├── args.rule # URL 参数规则 ├── blackip.rule # 黑名单 IP ├── cdnip.rule # CDN/可信代理 IP 列表 ├── cookie.rule # Cookie 规则 ├── post.rule # POST 规则 ├── url.rule # URL 规则 ├── useragent.rule # User-Agent 规则 ├── whiteip.rule # 白名单 IP ├── whiteua.rule # 白名单 UA(搜索引擎爬虫) ├── whiteurl.rule # 白名单 URL ├── referer.rule # Referer 规则 ├── fileext.rule # 文件上传扩展名规则 └── domains/ # 域名专属规则目录 └── www.example.com/ └── ...(同构的规则文件)
工作原理 NginxGuard 挂载在 Nginx 的 access_by_lua_file 阶段,每个请求进入时依次执行安全检测:
1 2 3 init_by_lua_file "/apps/nginx/conf/waf/init.lua" ;access_by_lua_file "/apps/nginx/conf/waf/access.lua" ;
init.lua 在 Nginx 启动时预加载模块;access.lua 在每个请求的 access 阶段执行检测逻辑;lib.lua 是核心库,提供 IP 匹配、规则加载、日志记录等能力。
检测流程 每个请求按以下顺序依次检测,命中任一项即拦截并返回 403,后续检测不再执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 1. $waf_enable 检查(location 级开关,off 则跳过全部) ↓ 2. waf_enable 检查(域名级/全局级开关) ↓ 3. 白名单 IP 放行 ↓ 4. 动态黑名单(CC 自动拉黑期内) ↓ 5. 静态黑名单 IP ↓ 6. 白名单 URL 匹配(返回 per-URL 跳过表) ↓ 7. User-Agent 攻击检测(白名单 UA 跳过此项) ↓ 8. Referer 攻击检测 ↓ 9. CC 限速 ↓ 10. 文件上传扩展名检测 ↓ 11. URL 路径攻击检测 ↓ 12. URL 参数攻击检测 ↓ 13. Cookie 攻击检测 ↓ 14. POST 攻击检测(表单 + JSON body) ↓ 放行
整个流程包裹在 pcall 中,任何意外错误都被捕获并记录到 ngx.log(ngx.ERR),不会导致 500 错误返回给客户端。
白名单 URL 新机制 :white_url_check() 不再简单地全量放行,而是返回一个跳过表(如 {url_attack=true} 或 {user_agent=true, referer=true}),后续各检测项根据跳过表决定是否执行。纯路径格式默认只跳过 url_attack,扩展格式可指定跳过任意组合的检测项。
核心特性详解 1. 基于域名的差异化规则 这是 NginxGuard 最实用的功能之一。通过 domain.json 配置文件,可以为不同域名设置不同的防护策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 { "www.example.com" : { "url_check" : "off" , "cc_rate" : "100/60" , "cc_block_ttl" : 300 , "rule_dir" : "domains/www.example.com" } , "api.example.com" : { "waf_enable" : "off" } , "limit.example.com" : { "multipart_streaming_check" : "on" , "post_body_scan_limit" : 1048576 , "upload_filename_scan_limit" : 1024 } , "strict.example.com" : { "bodyless" : "off" } , "*.test.com" : { "post_check" : "off" , "cookie_check" : "off" } }
配置优先级 :location 级 > 域名级 > 全局级
config.lua 是全局基线,所有未在 domain.json 中配置的域名都走全局配置
domain.json 只放需要覆盖的域名,未指定的字段自动回退到全局
支持通配符域名(*.test.com),精确匹配优先于通配符
完整配置参数参考 :
config.lua 中的全局配置项(对应 domain.json 中的键名):
domain.json 键名
config.lua 变量
默认值
说明
waf_enable
config_waf_enable
"on"
WAF 总开关,off 跳过全部检测
trust_proxy_headers
config_trust_proxy_headers
"on"
是否信任代理转发的 IP 头(CDN 场景)
white_ip_check
config_white_ip_check
"on"
白名单 IP 检测
black_ip_check
config_black_ip_check
"on"
黑名单 IP 检测
white_url_check
config_white_url_check
"on"
白名单 URL 检测
white_ua_check
config_white_ua_check
"on"
白名单 UA 检测(搜索引擎爬虫)
user_agent_check
config_user_agent_check
"on"
User-Agent 黑名单检测
url_check
config_url_check
"on"
URL 路径攻击检测
url_args_check
config_url_args_check
"on"
URL 参数攻击检测
cookie_check
config_cookie_check
"on"
Cookie 攻击检测
cc_check
config_cc_check
"on"
CC 限速检测
cc_rate
config_cc_rate
"150/60"
CC 限速参数(请求次数/时间窗口秒)
cc_block_ttl
config_cc_block_ttl
600
CC 触发后自动拉黑秒数,0=禁用自动拉黑
post_check
config_post_check
"on"
POST body 攻击检测
referer_check
config_referer_check
"off"
Referer 检测
file_upload_check
config_file_upload_check
"on"
文件上传扩展名检测
bodyless
config_bodyless
"on"
GET/HEAD/OPTIONS 跳过 body 检测,off=全方法扫描
multipart_streaming_check
config_multipart_streaming_check
"off"
multipart 临时文件 body 流式扫描
upload_filename_scan_limit
config_upload_filename_scan_limit
0
multipart 文件名扫描字节上限,0=全扫
post_body_scan_limit
config_post_body_scan_limit
2097152
大 body 扫描字节上限,超出直接拒绝
domain.json 中的键名就是 config.lua 变量去掉 config_ 前缀。例如 config_url_check → url_check。此外 domain.json 还支持 rule_dir 键(指定域名专属规则目录),该键没有对应的 config.lua 变量。config_log_dir、config_rule_dir、config_waf_output、config_redirect_url、config_output_html 为全局路径/输出配置,不支持域名级覆盖。
规则文件回退机制 也设计得很精巧:当域名配置了独立的 rule_dir 时,NginxGuard 会先在域名目录中查找规则文件。如果文件不存在则回退到全局目录;如果文件存在但为空,则不回退,等同于该域名无此项规则。这意味着空文件 = 明确指定”无规则”,而非”使用全局规则”。
2. IP 规则与 CIDR 支持 IP 规则文件(blackip.rule / whiteip.rule / cdnip.rule)全面支持 IPv4/IPv6、CIDR、通配符和精确 IP:
1 2 3 4 5 6 7 8.8.8.8 10.0.0.0/8 2001:db8::/32 fe80::* ::1
性能方面做了大量优化:
规模
匹配方式
查找复杂度
1195 条 IPv4 CIDR
排序数组 + 二分查找
O(log n) ≈ 11 次比较
IPv6 CIDR
4×32-bit chunk mask 匹配
O(n),通常 <20 条
通配符
预编译 regex
O(n),通常 <10 条
精确 IP
小写化字符串比较
O(n),通常 <100 条
CIDR 规则在文件加载时预编译为数值区间,查找性能极高。所有 IP 规则缓存 10 秒 + mtime 检查,修改规则文件后无需 reload nginx。
3. CDN 场景下的 IP 信任链 当 WAF 部署在 CDN 后面时,需要从 X-Forwarded-For 获取真实客户端 IP,但直接信任 XFF 会让攻击者伪造该头绕过 IP 黑白名单。
NginxGuard 的解决方案是 cdnip.rule:
1 2 config_trust_proxy_headers = "on"
1 2 3 4 5 6 173.245.48.0/20 104.16.0.0/13 10.0.0.0/8
条件
XFF 处理
说明
remote_addr 在 cdnip.rule 中
信任 XFF
提取真实客户端 IP
remote_addr 不在 cdnip.rule 中
不信任 XFF
使用 remote_addr(防直连伪造)
cdnip.rule 不存在
信任所有 XFF
向后兼容
IP 提取优先级:CF-Connecting-IP > X-Real-IP > X-Forwarded-For > remote_addr。
4. CC 攻击防护与自动拉黑 CC 防护基于 ngx.shared.dict 实现滑动窗口限速:
1 2 3 4 config_cc_rate = "150/60" config_cc_block_ttl = 600
超限后 IP 自动加入 badGuys 共享字典,600 秒内所有请求直接返回 403,到期后自动解封。这个封禁是跨 Worker 生效的(基于共享字典),不需要外部存储。
cc_rate 参数解析结果也做了 worker 级缓存,避免每次请求都解析字符串。如果配置格式错误(如 "abc"),CC 检测会静默 fail open,同时在 error.log 中记录错误日志。
5. 云凭据探测拦截 url.rule 中内置了针对云服务凭据和敏感配置文件的探测拦截规则,覆盖攻击者常用的扫描路径:
规则
拦截目标
/firebase.*\.json
Firebase 配置文件
/gcp-key\.json
GCP 服务账号密钥
/\.aws/credentials
AWS 凭据文件
/\.gcloud/
GCP 凭据目录
/\.ssh/id_rsa
SSH 私钥
/terraform\.tfstate
Terraform 状态文件
/.env
环境变量配置
/.git/config
Git 仓库配置
/actuator/env
Spring Boot Actuator
/swagger-ui
API 文档
/druid/
Druid 监控面板
/h2-console
H2 数据库控制台
还覆盖了版本控制目录(.git/、.svn/)、包管理文件(package.json、composer.json、Pipfile)、管理后台路径(wp-admin/、phpmyadmin)、Webshell 探测(shell.php、eval.php)等数百条规则。
6. 白名单 URL 的精细化跳过 旧版白名单 URL 一旦匹配即跳过全部安全检测,这在实际场景中过于宽松——一个合法的旧系统路径可能只需跳过 URL 路径检测,但不应该同时跳过 SQL 注入、XSS 等参数检测。
NginxGuard 对 whiteurl.rule 进行了重构,支持两种格式:
1 2 3 4 5 6 7 8 9 /static/ /api/public/ /legacy/ user_agent,referer,url_attack,url_args /api/old/ post,cookie
纯路径格式只跳过 URL 路径检测(url.rule),参数检测、Cookie 检测、POST 检测等照常执行。扩展格式则可以精确指定跳过哪些检测项:
可跳过的检测项
说明
user_agent
User-Agent 黑名单检测
referer
Referer 检测
url_attack
URL 路径攻击检测
url_args
URL 参数攻击检测
cookie
Cookie 攻击检测
post
POST body 攻击检测
file_upload
文件上传扩展名检测
cc
CC 限速检测
安全设计要点 :
前缀匹配 :路径采用前缀匹配(string.sub),而非子串匹配(string.find),防止 /123/ 匹配到 /x/123/etc/passwd 这类绕过攻击
最长匹配优先 :多条规则同时匹配时,选择路径最长的规则,实现精确控制
默认最小权限 :纯路径格式默认只跳过 url_attack,不跳过其他安全检测
1 2 3 4 5 6 7 8 if not (url_skips and url_skips.url_attack) then if url_attack_check() then return end end if not (url_skips and url_skips.url_args) then if url_args_attack_check() then return end end
7. 多层解码与绕过防护 攻击者经常通过编码绕过 WAF,NginxGuard 的 full_decode 函数实现了递归解码:
关键优化是 快速特征检测 (has_encode_markers):只有当输入中包含 %、\u、\x、&# 等编码标记时才触发解码,正常流量零额外开销。
所有检测项(URL、参数、Cookie、POST)都采用”先匹配原始输入 → 未命中且有编码标记 → 解码后再匹配”的双重检测策略。
性能优化 NginxGuard 在性能方面做了大量工程优化,以下是关键措施:
规则引擎优化 双引擎匹配 是核心优化之一。规则文件加载时,自动将纯字符串规则和正则规则分离:
纯字符串规则 :用 string.find(plain 模式)匹配,比正则快约 10 倍
正则规则 :合并为 (?:rule1|rule2|rule3|...) 单一正则,一次匹配代替 N 次循环
1 2 3 4 5 6 7 if r:find ("[()%.%[%]%*%+%?%^%$|\\]" ) then table .insert (regex_rules, r) else table .insert (fast_rules, r) fast_hash[r] = true end
正常流量匹配从 O(N) 降至 O(1),对 300 条规则的 url.rule,原来需要 300 次正则匹配,现在只需 1 次合并正则 + 少量字符串查找。
缓存层次
层级
存储位置
缓存内容
失效策略
Worker 级
Lua 模块变量
规则文件 + 合并正则 + 预编译 glob
TTL 10s + mtime 检查
Worker 级
Lua 模块变量
domain.json 解析结果
TTL 10s + mtime 检查
Worker 级
Lua 模块变量
cc_rate 解析结果
配置变更时重解析
请求级
ngx.ctx
client_ip / domain / config / headers
每请求自动清除
FFI stat 获取文件修改时间 规则热加载的关键是检测文件 mtime 变化。NginxGuard 使用 LuaJIT FFI 直接调用 libc 的 stat() 函数,无需编译任何 C 模块:
1 2 3 4 5 6 7 8 ffi.cdef[[ struct waf_stat_t { long long _pad_to_mtime[11]; long long st_mtime; long long _rest[7]; }; int stat(const char *path, struct waf_stat_t *buf); ]]
支持现代 glibc(stat())和旧版 glibc(__xstat()),兼容 x86_64 和 aarch64。当 FFI 不可用时回退为文件大小 + 60 秒 TTL。
请求级短路优化
GET/HEAD/OPTIONS 跳过 body 检测 :config_bodyless 默认为 on,GET/HEAD/OPTIONS 请求跳过 POST body 和文件上传检测以降低延迟;设为 off 时对所有方法扫描 body
无参数 URL 短路 :next(REQ_ARGS) == nil 时直接返回
最小输入长度检查 :#input < 2 直接返回,避免对短参数做正则
UA bloom-filter 预检查 :先检查 7 个 bot 标记词,99% 正常流量跳过 50 次 string.find
内网 IP 快速短路 :127.x/10.x/172.x/192.x 跳过完整 CIDR 匹配
Cookie 检测后移 :Cookie 检测移到 URL/Args 之后,攻击请求在 URL 阶段即短路返回
配置一次加载 所有 20 个配置项在请求入口一次性加载到 ngx.ctx._cfg,避免每检测项重复调用 get_effective_config:
1 2 3 4 5 6 7 local CONFIG_KEYS = { "waf_enable" , "white_ip_check" , "black_ip_check" , ... } for _, k in ipairs (CONFIG_KEYS) do ctx._cfg[k] = get_effective_config(k) end
规则匹配缓存 对小体积请求体/参数,按 规则文件 + mtime + flags + input 做 worker 级缓存,重复请求直接复用匹配结果:
1 2 3 4 5 if entry.cache_key and #input <= MATCH_CACHE_INPUT_MAX then cache_key = entry.cache_key .. "|" .. (flags or "" ) .. "|" .. input local cached = worker_match_cache[cache_key] if cached ~= nil then return cached or nil end end
压测数据 测试环境
项目
配置
CPU
4 核 x86_64
内存
24 GB
OS
Linux 6.18 (RHEL 9)
Nginx
1.31.3 + LuaJIT2 (OpenResty 分支)
测试工具
ApacheBench (ab),200 并发 50000 请求
GET 请求
场景
req/s
CPU
RSS
P99
吞吐下降
基线(WAF 全关)
27,516
273%
174 MB
43ms
—
+ WAF(无 CC/POST)
22,912
304%
179 MB
34ms
16.7%
+ WAF + CC
23,287
304%
179 MB
30ms
15.4%
+ WAF 全开(生产)
21,240
268%
179 MB
35ms
22.8%
POST 请求
场景
req/s
CPU
RSS
P99
吞吐下降
form POST 基线
31,243
187%
174 MB
27ms
—
form POST + WAF
21,634
286%
183 MB
29ms
30.7%
JSON POST 基线
32,188
192%
174 MB
39ms
—
JSON POST + WAF
20,547
292%
182 MB
66ms
36.2%
WAF 关闭时内存 174MB,全开后 179-183MB,仅增加 5-9MB。所有场景 P99 延迟控制在 66ms 以内。
日志系统 同步写入 攻击日志采用同步写入策略,确保在 ngx.exit(403) 前已落盘:
1 2 3 4 local file = io .open (LOG_NAME, "a" )file:write (LOG_LINE .. "\n" ) file:flush () file:close ()
只有检测到攻击时才触发日志写入,正常流量零日志开销。
JSON 格式 每条日志为一行 JSON:
1 2 3 4 5 6 7 8 9 10 11 { "@timestamp" : "2026-08-21T08:30:00Z" , "client_ip" : "1.2.3.4" , "local_time" : "2026-08-21 16:30:00" , "server_name" : "www.example.com" , "user_agent" : "Mozilla/5.0..." , "attack_method" : "Deny_URL_Args" , "req_url" : "/?id=1+union+select+1" , "req_data" : "-" , "rule_tag" : "(?i:union).*select.*from" }
截断保护与日志轮转 单字段最大 4096 字节,超出追加 ...[truncated]。日志文件超过 100MB 自动轮转为 *.old,轮转检查每 60 秒执行一次,不影响写入性能。
测试覆盖 项目包含完整的测试体系,16 个测试脚本覆盖各种攻击场景:
测试脚本
覆盖范围
waf_full_test.sh
全量规则测试:URL/参数/UA/Cookie/POST/文件上传/IP/XFF
waf_regression_test.sh
回归测试:bodyless/无效cc_rate/日志截断/PUT方法/大body
waf_scenario_test.sh
场景测试:多域名/白名单/动态拉黑
waf_coverage_test.sh
覆盖率测试
test_all_rules.sh
逐条规则验证
benchmark.sh
性能基准测试
测试覆盖的攻击类型包括:SQL 注入、XSS、路径遍历、命令注入、NoSQL 注入、SSTI 模板注入、PHP 代码注入、Log4j、SSRF、XXE、Webshell 探测等。
快速开始 安装依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 git clone https://github.com/openresty/luajit2.git cd luajit2 && make -j$(nproc ) && make installln -sf /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2git clone https://github.com/openresty/lua-cjson.git cd lua-cjsonexport LUAJIT_LIB=/usr/local/libexport LUAJIT_INC=/usr/local/include/luajit-2.1make -j$(nproc ) && make install git clone --branch v0.10.31 https://github.com/openresty/lua-nginx-module.git git clone https://github.com/simplresty/ngx_devel_kit.git wget https://nginx.org/download/nginx-1.31.3.tar.gz tar -xvf nginx-1.31.3.tar.gz && cd nginx-1.31.3 export LUAJIT_LIB=/usr/local/libexport LUAJIT_INC=/usr/local/include/luajit-2.1./configure \ --add-module=../ngx_devel_kit \ --add-module=../lua-nginx-module \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-threads \ --with-file-aio make -j$(nproc ) && make install
配置 Nginx 1 2 3 4 5 6 7 8 9 10 http { lua_package_path "/apps/nginx/conf/waf/?.lua" ; lua_shared_dict limit 10m ; lua_shared_dict badGuys 10m ; lua_code_cache on ; lua_need_request_body off ; init_by_lua_file "/apps/nginx/conf/waf/init.lua" ; access_by_lua_file "/apps/nginx/conf/waf/access.lua" ; }
修改配置 编辑 config.lua 中的日志路径和规则目录:
1 2 config_log_dir = "/apps/nginx/log/" config_rule_dir = "/apps/nginx/conf/waf/rule-config"
Location 级别开关 如果某些路径需要关闭 WAF(如 gRPC 长连接、WebSocket),只需一行:
1 2 3 4 location /ws { set $waf_enable off ; proxy_pass http://backend; }
规则文件示例 URL 参数规则(args.rule / post.rule / cookie.rule) 三份文件内容相同,覆盖 OWASP Top 10 攻击向量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # SQL 注入 (?i:union)[\s/\*!0-9-]*(?:(?:all|distinct)[\s/\*!0-9-]*)?(?i:select) (?i:select).*from.*information_schema sleep\((\s*)(\d*)(\s*)\) benchmark\([^,]*,\s*[^)]*\) (?i:load_file\() # XSS \<(iframe|script|body|img|layer|div|meta|style|base|object|input|svg) (onmouseover|onerror|onload)\= javascript\: document\.cookie # 命令注入 (?:define|eval|shell_exec|system|passthru|exec)\( (gopher|doc|php|glob|file|phar)\:\/ # SSTI 模板注入 \{\{[^}]{0,100}(__class__|__subclasses__|__init__|__globals__)\}\} # Log4j log4j|jndi:|\$\{.*jndi \$\{.*lower \$\{.*env # NoSQL 注入 \$where[\(:"'] \$regex[\(:"'] \$gt[\(:"'] # SSRF 169\.254\.169\.254 metadata\.google\.internal
User-Agent 规则(useragent.rule) 拦截常见安全扫描工具:
1 2 3 (sqlmap|nikto|nmap|dirb|gobuster|ffuf|nuclei|httpx|feroxbuster|masscan|wpscan) (Acunetix|Nessus|OpenVAS|BurpSuite|AppScan|Metasploit|CobaltStrike) (Python-urllib|Python-requests|Go-http-client|scrapy)
白名单 UA(whiteua.rule) 搜索引擎爬虫放行(仅跳过 UA 黑名单检测,其他安全检测仍执行):
1 2 3 4 5 6 Googlebot Baiduspider bingbot YandexBot Applebot ...
安全设计:白名单 UA 仅跳过 useragent.rule 检测,不会跳过 URL/POST/CC/Cookie 等其他检测,防止攻击者伪造搜索引擎 UA 绕过全部防护。
白名单 URL 规则(whiteurl.rule) 支持纯路径和扩展两种格式,按需跳过特定检测项:
1 2 3 4 5 6 7 8 9 10 11 # 纯路径格式(默认只跳过 url_attack 路径检测) /static/ /api/public/ # 扩展格式(指定跳过的检测项,逗号分隔) /legacy/ user_agent,referer,url_attack,url_args /api/old/ post,cookie # 可用检测项: user_agent,referer,url_attack,url_args,cookie,post,file_upload,cc # 路径匹配: 前缀匹配(最长匹配优先) # 修改后自动热加载(10秒内生效,无需重启 nginx)
安全设计:纯路径格式默认只跳过 url_attack,不再像旧版那样跳过全部检测。扩展格式允许按需组合跳过项,实现最小权限原则。路径采用前缀匹配而非子串匹配,防止 /path/ 匹配到 /malicious/path/etc/passwd 这类绕过攻击。
设计思考 为什么不使用 OpenResty? NginxGuard 只依赖 lua-nginx-module + LuaJIT + lua-cjson,不需要完整的 OpenResty 发行版。对于已经在用原版 Nginx 的团队,只需加两个动态模块即可获得 WAF 能力,迁移成本极低。
为什么用同步日志? WAF 日志的核心需求是”不丢”。如果用异步批量写入,在 ngx.exit(403) 后请求立即终止,尚未刷盘的日志可能丢失。同步写入 + flush() 确保每条攻击日志在返回 403 前已落盘。由于只在攻击时触发,正常流量不受影响。
为什么规则文件用纯文本而非 JSON/YAML? 纯文本规则文件的优势在于:
每行一条正则,极致简洁
支持 # 注释,方便分组和说明
vim / sed 直接编辑,无需格式化工具
文件修改后通过 mtime 检查自动热加载,无需重启
fail open 设计 整个检测流程包裹在 pcall 中。即使某条规则有语法错误、某个函数抛出异常,请求也不会返回 500,而是被捕获并记录到 error.log。在安全性和可用性之间,NginxGuard 选择了可用性优先(fail open),避免 WAF 自身故障导致全站不可用。
总结 NginxGuard 是一个”够用就好”的 WAF:
轻量 :4 个 Lua 文件,核心代码不到 1000 行,不引入额外进程
高性能 :GET 吞吐下降约 16%,内存仅增加 5-9MB
多域名 :基于 domain.json 的差异化配置,支持通配符和规则回退
热加载 :修改规则文件后 10 秒内自动生效,无需 reload nginx
精细化白名单 :whiteurl.rule 支持纯路径(默认只跳过 URL 路径检测)和扩展格式(指定跳过 user_agent,referer,url_attack,url_args,cookie,post,file_upload,cc 任意组合),前缀匹配防绕过
全面防护 :覆盖 SQLi/XSS/SSTI/SSRF/Log4j/NoSQL/命令注入/Webshell 探测等
IPv6 友好 :全链路支持 IPv4/IPv6/CIDR/通配符
CDN 适配 :通过 cdnip.rule 构建 XFF 信任链,防止 IP 伪造
如果你的团队已经在用 Nginx,需要一个可控、可审计、不拖慢业务的 WAF 方案,NginxGuard 值得一试。
项目地址:GitHub - qist/nginxguard