<p>access_by_lua* 阶段是唯一安全拦截位置,因它在路由匹配后、向上游转发前执行;需用 access_by_lua_file 全局加载,显式读取并限制请求体,分层检测(哈希查表优先+限步正则),精准提取用户可控参数,白名单基于 realip_remote_addr 和 URI 豁免,避免编码歧义。</p>

必须在 access_by_lua* 阶段拦截,不能靠 Nginx 的 if 或 rewrite_by_lua 临时补丁应付
为什么 access_by_lua_file 是唯一安全位置
云原生环境里,请求常经 Ingress → Service → Pod 多层转发,任何延迟拦截都可能让恶意 SQL 到达上游应用。OpenResty 的 access_by_lua* 阶段在路由匹配完成、但尚未向 upstream 转发时执行,是唯一能「干净阻断」的位置。常见错误包括:
- 用
if ($args ~* "union") { return 403; }—— Nginx 正则不支持 PCRE JIT,高并发下易回溯爆炸,且无法解析 POST body - 把检测逻辑写在
content_by_lua—— 此时后端可能已拼接并执行了 SQL - 依赖
header_filter_by_lua—— 它只改响应头,完全不干预请求流
真正有效的做法是:在 http 块中全局启用 access_by_lua_file /path/to/waf.lua,所有流量无条件过检。
ngx.req.read_body() 必须显式调用,且仅对非 GET 请求生效
OpenResty 默认不读取 request body,尤其对 POST/PUT 请求,不主动读就拿不到参数值。但盲目 ngx.req.read_body() 有风险:
- GET 请求无需读 body,调用会失败并报 warning
- 未限制 body 大小时,攻击者可发超大 payload(如 100MB JSON)拖垮 worker 进程
- application/json 类型需手动解析,不能直接用
ngx.req.get_post_args()
实操建议:
if ngx.var.request_method == "POST" or ngx.var.request_method == "PUT" then
ngx.req.read_body()
local body = ngx.req.get_body_data()
if not body then
-- 尝试读取大 body(需提前配置 client_max_body_size)
body = ngx.req.get_body_file()
end
if body and #body > 20480 then -- 限 20KB
ngx.log(ngx.ERR, "body too large: ", #body)
ngx.exit(403)
end
-- 后续检测逻辑...
end
检测逻辑必须分层:先查表 + 再轻量正则,禁用 .* 类回溯表达式
云原生网关对延迟敏感,Lua 正则一旦写成 .*union.*select.*,遇到构造的恶意 payload(如嵌套注释+编码)极易触发回溯爆炸,QPS 可从数千跌至百级。正确策略是:
- 高频关键字走哈希查表:
local dangerous = {["union"] = true, ["sleep("] = true, ["--"] = true} - 复杂模式用
ngx.re.find并设步数上限:ngx.re.find(val, [[\b(?:union\s+select|extractvalue\()]], "jo", nil, 1000) - 只扫描「用户可控片段」:比如从
WHERE name = 'xxx'中提取xxx再检测,避免误杀产品名 “Union Jack” - 拒绝全量匹配整个参数值或 URI,优先拆解
ngx.var.args、ngx.req.get_post_args()、JSON body 字段值
白名单绕过和规则优先级必须显式控制
云原生场景下,健康检查探针(如 /healthz)、服务网格 mTLS 元数据(如带 X-Forwarded-For 的内部调用)常被误拦。不能靠“默认放行内网 IP”这种粗粒度方案:
- 白名单应基于
ngx.var.realip_remote_addr(需配合set_real_ip_from),而非$remote_addr - 对已知路径做豁免,需在检测函数开头加判断:
if ngx.var.uri == "/healthz" or ngx.var.uri:match("^/api/v1/internal/") then return end - 规则优先级靠 Lua 执行顺序控制,不要依赖 JSON 配置里的
"priority"字段——OpenResty 不解析它 - 若用共享字典做动态白名单,记得用
ngx.shared.xxx:get(key)查,别每次 reload 都重载文件
最易被忽略的是:云原生环境里 ngx.var.args 和 ngx.var.query_string 可能已被上游代理解码或二次编码,检测前务必确认当前值是否为原始输入态。

















