OpenResty实战:用Lua脚本构建SQL注入防火墙
SQL注入就像快递员偷偷拆开你的包裹塞进危险品——攻击者通过输入恶意代码"污染"你的数据库查询。作为Nginx的超级魔改版,OpenResty凭借Lua脚本能力,能在请求到达业务系统前筑起安全屏障。让我们通过具体代码看看如何实现。
一、防御手段实战
技术栈:OpenResty + Lua + MySQL
1. 参数化查询拦截
location /query {
content_by_lua_block {
local mysql = require "resty.mysql"
local args = ngx.req.get_post_args()
-- 参数化查询示例
local sql = "SELECT * FROM users WHERE username = ? AND password = ?"
local res, err = mysql:query(sql, {args.username, args.password})
-- 错误处理逻辑
if not res then
ngx.log(ngx.ERR, "查询失败: ", err)
return ngx.exit(500)
end
ngx.say(cjson.encode(res))
}
}
通过问号占位符将用户输入与SQL语句分离,就像把快递单和货物分开处理,从根本上避免代码注入的可能。
2. 输入过滤机制
location /search {
access_by_lua_block {
local args = ngx.req.get_uri_args()
-- 正则过滤特殊字符
if string.match(args.keyword, "[;'\"\\%]") then
ngx.log(ngx.WARN, "检测到可疑输入: ", args.keyword)
return ngx.exit(403)
end
}
}
这种模式如同在快递站门口安装X光机,发现可疑包裹直接拒收。正则表达式过滤了常见的SQL元字符,但要注意避免过度过滤导致误伤。
3. 动态规则阻断
location / {
access_by_lua_block {
local rules = {
"UNION.*SELECT",
"DROP TABLE",
"-- "
}
local body = ngx.req.get_body_data()
for _, pattern in ipairs(rules) do
if string.find(body, pattern) then
ngx.log(ngx.ALERT, "SQL注入攻击被拦截: ", pattern)
return ngx.exit(403)
end
end
}
}
这种方案就像设置关键词黑名单,实时扫描请求内容。但要注意规则库需要持续更新,就像病毒库需要定期升级。
二、应用场景分析
电商秒杀系统:在百万级QPS场景下,OpenResty的过滤逻辑可以直接在Nginx层完成,避免无效请求穿透到数据库
多租户平台:通过Lua脚本实现租户级别的安全策略,不同客户可以配置不同的过滤规则
API网关:作为统一入口集中处理SQL注入防御,降低各微服务的重复开发成本
三、方案优缺点对比
优势:
- 性能损耗<3%(Nginx原生C模块加持)
- 支持动态加载规则(无需重启服务)
- 可组合多种防御策略(输入校验+参数化查询+规则匹配)
局限:
- 需要开发者具备Lua编程能力
- 复杂正则可能影响性能(需控制匹配复杂度)
- 无法防御二进制协议注入(需配合其他方案)
四、实施注意事项
不要过度依赖WAF:某电商曾因过滤规则过于严格,导致"O'Neil"这样的合法姓名被误判
警惕二次解码攻击:曾发生攻击者通过双重URL编码绕过检测的案例
最小权限原则:数据库账号应该只赋予必要权限,即使被注入也能限制破坏范围
日志记录与分析:建议记录拦截日志并接入SIEM系统,某金融公司通过日志分析发现了新型注入模式
五、总结
OpenResty的SQL注入防御就像给数据库操作加上三道锁:参数化查询是结构锁,输入过滤是材质锁,规则匹配是行为锁。但安全从来都不是单点问题,需要配合数据库权限控制、定期漏洞扫描、代码审计等形成完整防御体系。记住,最好的防御是让攻击者觉得"破解成本>收益"——通过OpenResty设置层层关卡,让恶意请求在到达业务核心前就败下阵来。