lua_waf学习笔记
字数 3291 2025-08-29 08:30:36

Lua WAF 学习与实现指南

一、前言

Lua 是一种轻量级脚本语言,具有高可扩展性特点,搭配 Nginx 可以实现对 HTTP 请求包过滤的效果。本文通过分析一个开源 Lua-WAF 项目,学习 Nginx 从攻击检测到请求拦截的全过程。

二、核心概念

2.1 Nginx 处理机制

HTTP 请求生命周期

Nginx 处理 HTTP 请求的逻辑是:

  1. 解析请求行、请求头
  2. 处理 HTTP 请求
  3. 将结果过滤返回给客户端

多阶段处理请求

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 模块

  1. 准备 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
  1. 准备 NDK 模块
wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
tar -zxvf v0.3.0.tar.gz
  1. 准备 LNM 模块
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.9rc7.tar.gz
tar -zxvf v0.10.9rc7.tar.gz
  1. 添加 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
  1. 测试 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

  1. 下载 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/
  1. 修改配置文件
-- /etc/nginx/conf.d/waf/config.lua
RulePath = "/etc/nginx/conf.d/waf/wafconf/"
attacklog = "on"
logdir = "/var/log/nginx/attacklog/"
  1. 配置 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;
}
  1. 测试 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)

主要功能:

  1. 日志记录
  2. 定义检查函数
-- 日志记录函数
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)

实现多种安全检查:

  1. 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
  1. 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
  1. 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
  1. 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
  1. 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 常见绕过方式

  1. 不规则的 HTTP 请求包

    • Content-Disposition 大小写变异
    • Content-Disposition 值为空
    • Content-Disposition 值包含多余数据
  2. 不安全的 API 接口

    • ngx.req.get_uri_args() 默认只接收前 100 个参数
    • 恶意参数放在第 101 个位置可绕过检查
  3. 请求长度限制

    • POST 检查只取前 4KB 数据
    • 恶意数据放在 4KB 之后可绕过

5.2 防护建议

  1. 更新 lua-nginx-module 到 0.10.13+ 版本
  2. 检查 Content-Disposition 时使用更严格的正则
  3. 增加对完整请求体的检查
  4. 对参数数量进行限制

六、总结

Lua WAF 通过利用 Nginx 的 access 阶段和 Lua 脚本的强大功能,实现了灵活高效的 Web 应用防护。其核心在于:

  1. 利用 Nginx 多阶段处理机制,在适当阶段插入安全检查
  2. 使用 Lua 脚本实现复杂的检测逻辑
  3. 通过规则库实现可扩展的防护策略
  4. 利用共享内存实现 CC 攻击防护等高级功能

通过深入理解其实现原理,可以更好地部署、定制和优化 Lua WAF,为 Web 应用提供更全面的安全防护。

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 环境 准备 NDK 模块 准备 LNM 模块 添加 Lua 模块到 Nginx 测试 Lua 模块 3.2 加载 Lua-WAF 下载 Lua-WAF 修改配置文件 配置 Nginx 测试 WAF 四、Lua-WAF 实现分析 4.1 文件结构 4.2 配置文件 (config.lua) 4.3 初始化文件 (init.lua) 主要功能: 日志记录 定义检查函数 4.4 主执行文件 (waf.lua) 实现多种安全检查: IP 白名单检查 IP 黑名单检查 CC 攻击防护 URL 检查 POST 数据检查 五、绕过分析与防护 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 应用提供更全面的安全防护。