必须在access_by_lua阶段拦截,因该阶段在路由匹配后、上游转发前,可安全阻断恶意载荷;若延至content_by_lua或更晚,sql可能已被拼接执行。

SQL注入特征在OpenResty中该在哪一层拦截
必须在 access_by_lua* 阶段做检测,不能放在 content_by_lua* 或更晚阶段。因为此时请求已进入业务逻辑,恶意SQL可能已被拼接、透传甚至执行;而 access_by_lua* 在路由匹配之后、上游转发之前,是唯一能安全阻断且不干扰正常响应流程的位置。
常见错误是把检测逻辑写在 header_filter_by_lua* 里——那只是改响应头,完全无法阻止攻击载荷到达后端。
- GET 请求:检查
ngx.var.args和ngx.var.request_uri - POST/PUT(application/x-www-form-urlencoded):用
ngx.req.read_body()后读ngx.req.get_post_args() - POST/PUT(application/json):需手动解析 body,注意限制 body 大小,避免被大 payload 拖垮
哪些SQL关键字和模式必须立即拦截
光匹配 SELECT、UNION 这类基础关键字远远不够,攻击者大量使用编码绕过、大小写混写、内联注释、空白符变形等手法。真正有效的检测要覆盖:
- 典型恶意结构:
' OR '1'='1、" AND 1=1--、/*+ union select */ - 危险函数调用:
sleep(、benchmark(、extractvalue(、updatexml( - 注释干扰:
--+、#、/* ... */出现在参数值中间或末尾 - 非法嵌套括号与引号不平衡:比如单引号数量为奇数,或括号不成对(需轻量级计数,不建议全文法解析)
注意:不要正则全量匹配整个参数值,而应提取出「用户可控的字符串片段」再检测——例如从 WHERE name = 'xxx' 中只取 xxx 部分做规则扫描,避免误杀带 SQL 关键字的正常内容(如产品名 “Union Jack”)。
如何避免Lua正则导致OpenResty性能雪崩
OpenResty 的 string.match 是 NFA 实现,复杂正则在恶意 payload 下极易回溯爆炸。曾经有团队用一个 .*[union|select].* 表达式,让 QPS 从 8k 直降到 200。
- 禁用
.*、.+、嵌套量词(如(a+)+) - 用
ngx.re.find替代string.match,它基于 PCRE JIT,支持超时控制:ngx.re.find(val, [[\b(?:union\s+select|sleep\s*\()]], "io", nil, 1000)(最后参数为最大匹配步数) - 对每个参数单独检测,命中即
ngx.exit(403),不累积扫描 - 高危关键词优先走哈希查表(如
local dangerous = {["union"] = true, ["sleep("] = true}),再 fallback 到正则
为什么不能只靠Lua脚本完成SQL注入防御
OpenResty 的 Lua 层只能做「初步探针式过滤」,它看不到参数最终如何拼进 SQL 语句,也不知道后端用的是预编译还是字符串拼接。一个看似干净的 id=123,如果后端代码写成 "SELECT * FROM users WHERE id = " .. id,照样被绕过。
真实防御必须分层:
- OpenResty 层:拦掉明显畸形、高置信度攻击(如
1' AND SLEEP(5)--),减轻后端压力 - 应用层:强制使用参数化查询(
mysql:query("SELECT ... WHERE id = ?", id)),这是唯一可靠手段 - 数据库层:开启
sql_mode=STRICT_TRANS_TABLES,禁用危险函数(secure_file_priv设为空)
把所有希望押在 OpenResty 脚本上,等于在防火墙里装了个玩具警报器——响得勤,但门根本没锁。











