21
10
月
2026
本文由财富云计算提供,我们提供高防服务器与 CDN 防护服务,NAT 型无独立公网 IP 价格比独立 IP 省一半以上,源站自研 WAF 是我们的高防方案里源站加固的标配,见文末。
一、为什么需要 Lua 层 WAF
Nginx 配置防护的天花板:
| 需求 | Nginx 配置 | Lua |
|---|---|---|
| 静态频率限制 | ✅ rate 参数 | ✅ |
| 按接口不同阈值 | ⚠️ 每 location 写死 | ✅ 代码动态判断 |
| 检查 POST 请求体 | ❌ 做不到 | ✅ 逐字节检查 |
| 多维度关联(IP+UA+路径) | ❌ | ✅ |
| 全局黑名单(多机同步) | ❌ | ✅ Redis 共享 |
| 滑动窗口限流 | ❌(漏桶) | ✅ 精确实现 |
| 学习正常流量基线 | ❌ | ✅ |
OpenResty = Nginx + LuaJIT,Lua 跑在请求处理的各个阶段,性能和 C 模块接近——防护逻辑零成本嵌入。
二、安装 OpenResty
# Ubuntu/Debian
apt install -y curl gnupg2
curl -fsSL https://openresty.org/package/pubkey.gpg | gpg --dearmor -o /usr/share/keyrings/openresty.gpg
echo "deb [signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/openresty.list
apt update && apt install -y openresty
# CentOS/RHEL
yum install -y yum-utils
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
yum install -y openresty
# 验证
/usr/local/openresty/bin/openresty -v
# 显示 nginx version: openresty/x.x.x = 成功# 设为系统服务(用 systemd 管理,不是原版 nginx 命令)
# openresty 自带 service 文件,或者软链:
ln -sf /usr/local/openresty/bin/openresty /usr/local/bin/nginx # 这样能用 nginx 命令
nginx -v && systemctl enable openresty目录说明:
- 配置:
/usr/local/openresty/nginx/conf/nginx.conf - Lua 代码:
/usr/local/openresty/nginx/lua/(自建) - 日志:
/usr/local/openresty/nginx/logs/
三、WAF 第一层:访问日志与 IP 还原
CDN/高防场景,先拿真实 IP,否则后面全错:
# nginx.conf http 块
http {
# 信任的回源段(换成你的 CDN/高防 IP 段)
set_real_ip_from 203.0.113.0/24;
set_real_ip_from 198.51.100.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
lua_shared_dict waf_cache 64m; # 共享内存(计数、黑名单缓存)
lua_code_cache on; # 生产开缓存(改 Lua 要 reload)
resolver 223.5.5.5 valid=300 ipv6=off; # 内部用
init_by_lua_block {
-- 全局初始化(加载模块、读配置)
waf = require "waf.init"
waf.init()
}
}四、WAF 核心:动态限流模块
比 Nginx rate 更强:滑动窗口 + 按维度 + 动态阈值。
-- /usr/local/openresty/nginx/lua/limit.lua
local _M = {}
local dict = ngx.shared.waf_cache
-- 滑动窗口限流
-- key: 限流键(如 "ip:1.2.3.4:/api/search")
-- limit: 窗口内最大次数 window: 窗口秒数
function _M.sliding_window(key, limit, window)
local now = ngx.time()
-- 用有序结构太复杂,简化为「分桶滑动」
local bucket = math.floor(now / window)
local rk = key .. ":" .. bucket
local cnt, err = dict:incr(rk, 1, 0)
if not cnt then
ngx.log(ngx.ERR, "limit incr failed: ", err)
return true -- 出错时放行(fail-open,宁误放不误杀)
end
-- 设置过期(两个窗口,保证滑动边界)
dict:expire(rk, window * 2)
if cnt > limit then
return false
end
return true
end
-- 按 IP + URI 分级限流
function _M.check_request()
local ip = ngx.var.remote_addr
local uri = ngx.var.uri
-- 1. 全局 IP 限制:单 IP 全站 200 次/分钟
if not _M.sliding_window("ip:" .. ip, 200, 60) then
return "global_ip_limit"
end
-- 2. 贵接口单独限:/api/search 10 次/分钟
if uri:match("^/api/search") or uri:match("^/api/query") then
if not _M.sliding_window("search:" .. ip, 10, 60) then
return "search_limit"
end
end
-- 3. 登录接口:5 次/分钟(防爆破)
if uri == "/login" or uri == "/api/login" then
if not _M.sliding_window("login:" .. ip, 5, 60) then
return "login_limit"
end
end
-- 4. 短信/邮件接口:1 次/分钟
if uri:match("/sms/") or uri:match("/email/") then
if not _M.sliding_window("sms:" .. ip, 1, 60) then
return "sms_limit"
end
end
return nil -- 通过
end
return _M用法(在 access 阶段调用):
server {
location / {
access_by_lua_block {
local limit = require "limit"
local hit = limit.check_request()
if hit then
ngx.log(ngx.WARN, "rate limited: ", hit, " ip=", ngx.var.remote_addr)
ngx.status = 429
ngx.header["Retry-After"] = 60
ngx.say('{"code":429,"msg":"请求过于频繁,请稍后再试"}')
return ngx.exit(429)
end
}
proxy_pass http://backend;
}
}五、WAF 第二层:攻击特征检测
5.1 请求体内容检查(Nginx 配置做不到的)
-- /usr/local/openresty/nginx/lua/inspect.lua
local _M = {}
-- SQL 注入特征
local sql_patterns = {
"union%s+select", "select%s+.*%s+from", "insert%s+into", "drop%s+table",
"'%s+or%s+'", "or%s+1%s*=%s*1", "sleep%s*%(", "benchmark%s*%(",
"information_schema", "xp_cmdshell", "%/\\*.+\\*\\/",
}
-- XSS 特征
local xss_patterns = {
"<script", "javascript:", "onerror%s*=", "onload%s*=",
"<iframe", "document%.cookie", "eval%s*%(",
}
-- 路径遍历
local traversal_patterns = {
"%.%.%/", "%.%.\\\\", "/etc/passwd", "/proc/self",
"boot%.ini", "win%.ini",
}
local function check_any(str, patterns)
if not str then return false end
local lower = str:lower()
for _, p in ipairs(patterns) do
if lower:find(p) then
return true
end
end
return false
end
-- 检查 URI + 参数 + POST body
function _M.check_payload()
local uri = ngx.var.request_uri
-- 1. URI 和查询参数
if check_any(uri, sql_patterns) then return "sqli" end
if check_any(uri, xss_patterns) then return "xss" end
if check_any(uri, traversal_patterns) then return "traversal" end
-- 2. POST body(按需开启,读 body 有开销)
if ngx.req.get_method() == "POST" then
ngx.req.read_body()
local body = ngx.req.get_body_data()
if body and #body > 0 then
if check_any(body, sql_patterns) then return "sqli_body" end
if check_any(body, xss_patterns) then return "xss_body" end
-- 大 body 采样检查(性能优化)
if #body > 102400 then
local sample = body:sub(1, 102400)
if check_any(sample, sql_patterns) then return "sqli_body" end
end
end
end
return nil
end
return _M5.2 接入 WAF 主流程
-- /usr/local/openresty/nginx/lua/waf/init.lua
local limit = require "limit"
local inspect = require "inspect"
local _M = {}
function _M.init()
ngx.log(ngx.NOTICE, "WAF initialized")
end
-- 每个请求的入口
function _M.access()
local ip = ngx.var.remote_addr
-- 1. 黑名单(Redis 联动,见第六节)
local blocked = require "blocklist"
if blocked.check(ip) then
return ngx.exit(403)
end
-- 2. 频率限制
local hit = limit.check_request()
if hit then
return ngx.exit(429)
end
-- 3. 攻击特征
local attack = inspect.check_payload()
if attack then
ngx.log(ngx.WARN, "attack detected: ", attack, " ip=", ip, " uri=", ngx.var.request_uri)
-- 命中特征的 IP 记一次「违规」,累计进黑名单
blocked.strike(ip)
return ngx.exit(403)
end
end
return _Mserver {
location / {
access_by_lua_block {
require("waf.init").access()
}
proxy_pass http://backend;
}
}六、WAF 第三层:Redis 全局黑名单
多台服务器共享黑名单、违规计分自动封禁:
-- /usr/local/openresty/nginx/lua/blocklist.lua
local redis = require "resty.redis"
local _M = {}
local REDIS_HOST = "127.0.0.1"
local REDIS_PORT = 6379
local function connect()
local red = redis:new()
red:set_timeout(200)
local ok, err = red:connect(REDIS_HOST, REDIS_PORT)
if not ok then
ngx.log(ngx.ERR, "redis connect failed: ", err)
return nil
end
return red
end
-- 检查 IP 是否被封
function _M.check(ip)
-- 先查本地共享内存(快),没有再查 Redis
local cached = ngx.shared.waf_cache:get("blocked:" .. ip)
if cached then return true end
local red = connect()
if not red then
return false -- Redis 挂了放行(或按策略 fail-close)
end
local res, err = red:get("waf:blocked:" .. ip)
red:set_keepalive(10000, 100)
if res and res ~= ngx.null then
ngx.shared.waf_cache:set("blocked:" .. ip, true, 60) -- 本地缓存 60 秒
return true
end
return false
end
-- 违规计分:3 次违规封 1 小时
function _M.strike(ip)
local red = connect()
if not red then return end
local key = "waf:strikes:" .. ip
local n, err = red:incr(key)
red:expire(key, 3600)
if n and n >= 3 then
red:set("waf:blocked:" .. ip, "1")
red:expire("waf:blocked:" .. ip, 3600)
ngx.log(ngx.WARN, "IP banned for 1h: ", ip, " strikes=", n)
end
red:set_keepalive(10000, 100)
end
return _M# 装 Redis
apt install -y redis-server
systemctl enable --now redis-server
# 手动封禁/解封
redis-cli set waf:blocked:1.2.3.4 1 EX 3600
redis-cli del waf:blocked:1.2.3.4
redis-cli keys "waf:strikes:*" | head # 看违规记录七、性能与调优
Lua WAF 虽好,别把机器跑垮:
7.1 分阶段控制开销
server {
# 静态资源不做 payload 检查(检查也白检查,攻击者不打静态)
location ~* \.(jpg|css|js|png|woff2?)$ {
access_log off;
expires 30d;
}
# 动态请求全量 WAF
location / {
access_by_lua_block { require("waf.init").access() }
proxy_pass http://backend;
}
}7.2 共享内存调优
lua_shared_dict waf_cache 128m; # 黑名单缓存 + 计数,按 QPS 调7.3 Lua 代码缓存
lua_code_cache on; # 生产必须 on(改代码后 reload 生效)
# 开发调试可临时 off(每次请求重新编译,极慢)7.4 性能基准参考
2 核 4G 机器跑上述 WAF:单请求增加 0.1–0.3ms、QPS 上限比裸 Nginx 降 10–20%——换来的防护能力值这个价。追求极限性能可以把特征检测放 CDN/WAF 产品层,源站 Lua 只做兜底限流。
八、部署检查清单
□ openresty -t 通过(语法)
□ 测试正常请求全部放行(没被规则误杀)
□ 测试 limit:for i in $(seq 1 300); do curl -s -o /dev/null https://域名/; done → 后段 429
□ 测试 SQLi:curl "https://域名/?id=1' union select 1,2,3--" → 403
□ 测试 XSS:curl "https://域名/?q=<script>alert(1)</script>" → 403
□ Redis 联通:redis-cli ping → PONG
□ 黑名单生效:redis-cli set waf:blocked:测试IP 1 → 访问 403
□ 日志有 WARN 记录(攻击日志)
□ CDN 场景 real_ip 生效(日志是真实用户 IP)
□ reload 后功能正常(lua_code_cache on)九、小结
OpenResty Lua WAF 三板斧:滑动窗口动态限流(按 IP/接口分级,比 Nginx rate 精细)、请求体深度检测(SQLi/XSS/遍历,Nginx 配置够不着的地方)、Redis 全局黑名单(多机同步 + 违规计分自动封)。性能代价是每请求 0.1–0.3ms,对防护价值可以忽略。定位上它是源站最后一道自研防线——前面的 CDN/高防挡大流量,Lua WAF 挡漏过来的精细攻击,两层配合才是完整的纵深。
相关阅读:
本文由财富云计算整理,我们提供高防服务器、CDN 防护与 NAT 型云服务器,无独立公网 IP 机型价格比独立 IP 省一半以上,需要「高防 + CDN + 源站 Lua WAF」完整方案的咨询。更多教程见端口速查表。
延伸阅读
- 高防误区与避坑清单:25 个坑+下单前必问 14 问+到货验货流程
- DNS 攻击防护专题:劫持、隧道、投毒、放大四类攻击与 DNSSEC
- 电商大促防攻击预案:T-14 到当天的分级响应与降级开关
- 源站真实 IP 隐藏完全指南:六大暴露面排查+NAT 型终极方案
- 日志分析找攻击源:Nginx 日志三维度分析法与证据固定
- 攻击应急响应手册:六阶段响应流程,从确认到复盘的照做清单
- iptables 抗 DDoS 配置模板:状态过滤+SYN 防护+防扫描+游戏服版
- Nginx 进阶防护:连接层、请求层、行为层三层生产级配置

[...]netsh advfirewall firewall add rule name="GameRange" dir=in action=allow protocol=UDP localport=26901-26903完整的 Windows 环境准备(远程桌面连接、运行库安装、SteamCMD 用法、开机自启、8 个常见坑)见:《Windows 云服务器开服通用前置教程》→ htt[...]
2026-10-21 回复