Nginx+Lua实现自定义WAF的核心是在access_by_lua阶段介入,对URI、参数等毫秒级解析匹配规则,防御SQL注入、XSS等攻击;需避免在rewrite_by_lua中判断,慎用content_by_lua*,推荐预编译正则、减少解码、规则热加载。

用 Nginx + Lua 实现自定义 WAF,核心在于利用 ngx_lua 模块在请求生命周期的早期阶段(如 access_by_lua*)介入,对原始请求头、URI、参数、Body 等进行毫秒级解析与规则匹配。它不依赖外部进程或代理,延迟低、扩展性强,适合防御 SQL 注入、XSS、路径遍历、恶意 User-Agent 等常见攻击。
关键位置:在 access 阶段做深度扫描
Nginx 的 access_by_lua_block 或 access_by_lua_file 是最合适的钩子——此时请求已解析 URI 和 headers,但尚未转发给后端,也未读取完整 request body(需显式启用)。若需检查 POST body,必须配合 lua_need_request_body on(仅限开发/低流量场景)或使用 lua_shared_dict + 异步流式解析(生产推荐)。
- 避免在
rewrite_by_lua*中做安全判断:该阶段 URI 还未标准化(如未解码),易绕过规则 - 慎用
content_by_lua*:此时响应已生成,拦截成本高且无法统一返回 403 - 真实生产中建议用
set_by_lua_block做轻量预处理(如提取 host、method),再交由 access 阶段决策
高效特征提取:少 decode,多模式匹配
毫秒级响应的关键是减少字符串拷贝和重复解码。Lua 中应:
- 直接操作
ngx.var.request_uri和ngx.var.args,避免调用ngx.unescape_uri()全量解码(攻击者常利用双编码、混合编码绕过) - 对关键字段(如
Cookie、User-Agent)用预编译的 Lua pattern 或luautf8+ 正则做无状态扫描(例如:string.find(ua, "['";\%z]", 1, true)) - 对参数值做“语义无关”检测:比如匹配
selects+.*from(忽略空格、换行、注释)、javascript:前缀、..%2f..%2f路径穿越片段
规则热加载与性能隔离
硬编码规则不可维护,推荐将规则存为 JSON 文件或 Redis Hash,通过 lua_shared_dict 缓存并定时 reload:
- 用
shared_dict存规则版本号 + 规则集(如 50KB 规则可压缩为 table,首次加载耗时 - 借助
timer.at每 30 秒触发一次异步检查(不阻塞 worker),发现更新则原子替换 dict 中的 rule table - 每个匹配逻辑封装为独立函数(如
check_sql_injection()),失败立即 return,避免冗余扫描
精准拦截与日志协同
拦截不是目的,可追溯才是重点。每次匹配成功应:
- 记录原始请求五元组、匹配到的规则 ID、触发字段(如
args.id)、截断的 payload 片段(前 64 字节) - 用
ngx.log(ngx.WARN, ...)写入 error_log,或投递到lua-resty-logger-socket异步发往 ELK - 返回统一 JSON 错误页(
content_by_lua_block中生成),含 trace_id 方便关联日志,禁用默认 403 HTML 页面
不复杂但容易忽略:真正决定性能的是规则引擎设计,而非 Lua 语法本身。把正则拆成多层过滤(先长度/字符集筛,再语法模式匹配),用 bitop 快速判断 ASCII 控制字符,比单条 PCRE 更稳更快。


















