lua_waf学习笔记
字数 3291 2025-08-29 08:30:36
Lua WAF 学习与实现指南
一、前言
Lua 是一种轻量级脚本语言,具有高可扩展性特点,搭配 Nginx 可以实现对 HTTP 请求包过滤的效果。本文通过分析一个开源 Lua-WAF 项目,学习 Nginx 从攻击检测到请求拦截的全过程。
二、核心概念
2.1 Nginx 处理机制
HTTP 请求生命周期
Nginx 处理 HTTP 请求的逻辑是:
- 解析请求行、请求头
- 处理 HTTP 请求
- 将结果过滤返回给客户端
多阶段处理请求
Nginx 处理 HTTP 请求分为 11 个阶段:
| 阶段 | 功能 | 相关模块 | 相关指令 |
|---|---|---|---|
| POST_READ | 接受完请求头后的第一个阶段 | ngx_http_realip_module | set_real_ip_from, real_ip_header, real_ip_recursive |
| SERVER_REWRITE | 处理 server 块内 location 块外的重写命令 | ngx_http_rewrite_module | break, if, return, rewrite, set |
| FIND_CONFIG | 根据 rewrite 后的 uri 匹配对应 location | ngx_http_core_module | location |
| REWRITE | 处理 location 块内的重写命令 | ngx_http_rewrite_module | break, if, return, rewrite, set |
| POST_REWRITE | 检查上一阶段是否存在重写操作 | - | - |
| PREACCESS | 限制客户端访问频率和数量 | http_limit_req_module, http_limit_conn_module | limit_req, limit_req_zone, limit_conn, limit_conn_zone |
| ACCESS | 限制客户端访问 | ngx_http_access_module, ngx_http_auth_basic_module | allow, deny, auth_basic, auth_basic_user_file |
| POST_ACCESS | 配合 access 阶段实现 satisfy 命令 | - | - |
| PRECONTENT | 生成结果前的预处理 | http_try_files_module, ngx_http_mirror_module | try_files, mirror |
| CONTENT | 生成原始 HTTP 响应数据 | 多个模块 | root, alias, index, autoindex |
| LOG | 生成日志 | ngx_http_log_module | log_format, access_log, error_log |
Filter 输出过滤
CONTENT 阶段生成的数据经过 filter 处理后再发送给客户端,主要 filter 模块包括:
- ngx_http_not_modified_filter_module: 处理 If-Modified-Since
- ngx_http_range_body_filter_module: 处理 Range 请求
- ngx_http_headers_filter_module: 处理 HTTP 头部
- ngx_http_sub_filter_module: 字符串替换
- ngx_http_gzip_filter_module: Gzip 压缩
2.2 Lua-Nginx 模块
原生 Nginx 不支持 Lua 代码,需要添加 Lua-Nginx-Module 提供指令和 API 支持。
主要指令
| 指令 | 阶段 | 功能 |
|---|---|---|
| init_by_lua* | loading-config | Nginx 读取配置时执行 |
| set_by_lua* | rewrite | 设置 Lua 变量 |
| rewrite_by_lua* | rewrite | 跳转、重定向相关 |
| access_by_lua* | access | 访问控制相关 |
| content_by_lua* | content | 内容生成阶段 |
| header_filter_by_lua* | output-header-filter | 修改 HTTP 响应头 |
| body_filter_by_lua* | output-body-filter | 修改 HTTP 响应体 |
| log_by_lua* | log | 日志相关 |
常用 API
| API | 功能 |
|---|---|
| ngx.req.raw_header | 获取原始 HTTP 请求头 |
| ngx.req.get_method | 获取 HTTP 请求方法 |
| ngx.req.get_uri_args | 获取 URI 中的 args |
| ngx.req.get_body_data | 获取 HTTP 请求体 |
| ngx.var.xxxx | 调用 Nginx 变量 |
| ngx.status | 获取 HTTP 响应状态码 |
| ngx.md5 | 计算 MD5 哈希值 |
| ngx.log | 日志操作 |
三、环境搭建
3.1 添加 Lua 模块
- 准备 Lua 环境
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar -zxvf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make install PREFIX=/usr/local/luajit
# 设置环境变量
echo "export LUAJIT_LIB=/usr/local/luajit/lib" >> /etc/profile
echo "export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0" >> /etc/profile
source /etc/profile
echo "/usr/local/luajit/lib" >> /etc/ld.so.conf
ldconfig
- 准备 NDK 模块
wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
tar -zxvf v0.3.0.tar.gz
- 准备 LNM 模块
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.9rc7.tar.gz
tar -zxvf v0.10.9rc7.tar.gz
- 添加 Lua 模块到 Nginx
# 查看原始编译参数
nginx -V
# 添加编译参数
./configure \
--add-module=/waf/ngx_devel_kit-0.3.0 \
--add-module=/waf/lua-nginx-module-0.10.9rc7 \
[其他原有参数]
# 解决依赖问题
yum -y install openssl openssl-devel libxslt-devel gd gd-devel gperftools
# 编译安装
make
cd ./objs
mv /usr/sbin/nginx /usr/sbin/nginx.old
cp nginx /usr/sbin/nginx
systemctl restart nginx
- 测试 Lua 模块
server {
location /lua {
default_type 'text/html';
content_by_lua 'ngx.say("hello lua")';
}
}
curl 127.0.0.1/lua
3.2 加载 Lua-WAF
- 下载 Lua-WAF
wget https://github.com/loveshell/ngx_lua_waf/archive/refs/tags/v0.7.2.tar.gz
tar -zxvf v0.7.2.tar.gz
cp -r ngx_lua_waf-0.7.2 /etc/nginx/conf.d/waf/
- 修改配置文件
-- /etc/nginx/conf.d/waf/config.lua
RulePath = "/etc/nginx/conf.d/waf/wafconf/"
attacklog = "on"
logdir = "/var/log/nginx/attacklog/"
- 配置 Nginx
http {
# lua_waf
lua_package_path "/etc/nginx/conf.d/waf/?.lua";
lua_shared_dict limit 10m;
init_by_lua_file /etc/nginx/conf.d/waf/init.lua;
access_by_lua_file /etc/nginx/conf.d/waf/waf.lua;
}
- 测试 WAF
http://ip/?id=<script>alert(1)</script>
四、Lua-WAF 实现分析
4.1 文件结构
ngx_lua_waf/
├── config.lua # 配置文件
├── init.lua # 初始化文件
├── waf.lua # 主执行文件
└── wafconf/ # 规则库
├── args # args规则
├── cookie # cookie规则
├── post # post规则
├── url # url规则
├── user-agent # UA规则
└── whiteurl # 白名单规则
4.2 配置文件 (config.lua)
RulePath = "/usr/local/nginx/conf/waf/wafconf/"
attacklog = "on"
logdir = "/usr/local/nginx/logs/hack/"
UrlDeny = "on"
CookieMatch = "on"
postMatch = "on"
whiteModule = "on"
black_fileExt = {"php", "jsp"}
ipWhitelist = {"127.0.0.1"}
ipBlocklist = {"1.0.0.1"}
CCDeny = "off"
CCrate = "100/60"
html = [[403]]
4.3 初始化文件 (init.lua)
主要功能:
- 日志记录
- 定义检查函数
-- 日志记录函数
function log(method, url, data, ruletag)
local realIp = getClientIp()
local ua = ngx.var.http_user_agent
local servername = ngx.var.server_name
local time = ngx.localtime()
local line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..data.."\" \""..ua.."\" \""..ruletag.."\"\n"
local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log"
write(filename, line)
end
-- args检查函数
function args()
for _, rule in pairs(argsrules) do
local args = ngx.req.get_uri_args()
for key, val in pairs(args) do
if type(val) == 'table' then
data = table.concat(val, " ")
else
data = val
end
if data and type(data) ~= "boolean" and rule ~= "" and ngxmatch(unescape(data), rule, "isjo") then
log('GET', ngx.var.request_uri, "-", rule)
say_html()
return true
end
end
end
return false
end
4.4 主执行文件 (waf.lua)
实现多种安全检查:
- IP 白名单检查
function whiteip()
if next(ipWhitelist) ~= nil then
for _, ip in pairs(ipWhitelist) do
if getClientIp() == ip then
return true
end
end
end
return false
end
- IP 黑名单检查
function blockip()
if next(ipBlocklist) ~= nil then
for _, ip in pairs(ipBlocklist) do
if getClientIp() == ip then
ngx.exit(403)
return true
end
end
end
return false
end
- CC 攻击防护
function denycc()
if CCDeny then
local uri = ngx.var.uri
CCcount = tonumber(string.match(CCrate, '(.*)/')) -- 100/
CCseconds = tonumber(string.match(CCrate, '/(.*)')) -- /60
local token = getClientIp()..uri
local limit = ngx.shared.limit
local req, _ = limit:get(token)
if req then
if req > CCcount then
ngx.exit(503)
return true
else
limit:incr(token, 1)
end
else
limit:set(token, 1, CCseconds)
end
end
return false
end
- URL 检查
function url()
if UrlDeny then
for _, rule in pairs(urlrules) do
if rule ~= "" and ngxmatch(ngx.var.request_uri, rule, "isjo") then
log('GET', ngx.var.request_uri, "-", rule)
say_html()
return true
end
end
end
return false
end
- POST 数据检查
-- 文件上传检查
local m = ngxmatch(data, [[Content-Disposition: form-data;(.+)filename=]], 'ijo')
if m then
fileExtCheck(m[3]) -- 检查文件后缀
filetranslate = true
else
if ngxmatch(data, "Content-Disposition:", 'isjo') then
filetranslate = false
end
if filetranslate == false then
if body(data) then -- 检查请求体
return true
end
end
end
五、绕过分析与防护
5.1 常见绕过方式
-
不规则的 HTTP 请求包
- Content-Disposition 大小写变异
- Content-Disposition 值为空
- Content-Disposition 值包含多余数据
-
不安全的 API 接口
- ngx.req.get_uri_args() 默认只接收前 100 个参数
- 恶意参数放在第 101 个位置可绕过检查
-
请求长度限制
- POST 检查只取前 4KB 数据
- 恶意数据放在 4KB 之后可绕过
5.2 防护建议
- 更新 lua-nginx-module 到 0.10.13+ 版本
- 检查 Content-Disposition 时使用更严格的正则
- 增加对完整请求体的检查
- 对参数数量进行限制
六、总结
Lua WAF 通过利用 Nginx 的 access 阶段和 Lua 脚本的强大功能,实现了灵活高效的 Web 应用防护。其核心在于:
- 利用 Nginx 多阶段处理机制,在适当阶段插入安全检查
- 使用 Lua 脚本实现复杂的检测逻辑
- 通过规则库实现可扩展的防护策略
- 利用共享内存实现 CC 攻击防护等高级功能
通过深入理解其实现原理,可以更好地部署、定制和优化 Lua WAF,为 Web 应用提供更全面的安全防护。