使用OpenResty实现Web应用防护
我们的WAF系统采用模块化设计,主要包含以下几个核心组件:
1. 请求生命周期管理
OpenResty提供了多个执行阶段,我们需要在不同阶段执行不同的安全检测:
-- init_by_lua_block: 初始化阶段,加载配置和规则
local config = require "waf.config"
local rules = require "waf.rules"
-- 初始化共享内存
local limit_req_store = ngx.shared.limit_req_store
local blacklist = ngx.shared.blacklist
local whitelist = ngx.shared.whitelist
-- 加载安全规则
function init_waf()
-- 加载SQL注入规则
rules.sql_patterns = {
"select.*from",
"union.*select",
"insert.*into",
"delete.*from",
"update.*set",
"drop.*table",
"exec.*\\(",
"execute.*\\(",
"script.*>",
"javascript:",
"vbscript:",
"onload=",
"onerror=",
"onclick="
}
-- 编译正则表达式以提高性能
for i, pattern in ipairs(rules.sql_patterns) do
rules.sql_patterns[i] = ngx.re.compile(pattern, "joi")
end
end
init_waf()2. IP访问控制模块
IP访问控制是最基础也是最有效的防护手段。我们实现了动态的IP黑白名单管理:
-- ip_filter.lua
local _M = {}
local blacklist = ngx.shared.blacklist
local whitelist = ngx.shared.whitelist
function _M.check_ip()
local client_ip = ngx.var.remote_addr
-- 白名单优先
local is_white = whitelist:get(client_ip)
if is_white then
return true
end
-- 检查黑名单
local is_black = blacklist:get(client_ip)
if is_black then
ngx.log(ngx.ERR, "Blocked IP: ", client_ip)
ngx.status = 403
ngx.say('{"code": 403, "msg": "Access Denied"}')
ngx.exit(403)
return false
end
return true
end
-- 动态添加黑名单
function _M.add_blacklist(ip, expire_time)
expire_time = expire_time or 3600 -- 默认封禁1小时
local succ, err = blacklist:set(ip, true, expire_time)
if not succ then
ngx.log(ngx.ERR, "Failed to add blacklist: ", err)
end
return succ
end
return _M3. 速率限制模块
速率限制是防御DDoS和暴力破解攻击的重要手段。我们实现了基于令牌桶算法的限速器:
-- rate_limiter.lua
local _M = {}
local limit_req_store = ngx.shared.limit_req_store
function _M.is_limited(key, rate, burst)
local now = ngx.now()
local token_key = "tokens:" .. key
local time_key = "time:" .. key
-- 获取上次请求时间和剩余令牌
local last_time = limit_req_store:get(time_key) or 0
local tokens = limit_req_store:get(token_key) or burst
-- 计算新增令牌
local elapsed = math.max(0, now - last_time)
local new_tokens = math.min(burst, tokens + elapsed * rate)
if new_tokens < 1 then
-- 令牌不足,限速
return true
end
-- 消耗一个令牌
limit_req_store:set(token_key, new_tokens - 1)
limit_req_store:set(time_key, now)
return false
end
-- 使用示例
function _M.check_rate_limit()
local client_ip = ngx.var.remote_addr
local uri = ngx.var.uri
-- 针对不同接口设置不同限速
local limits = {
["/api/login"] = {rate = 1, burst = 5}, -- 登录接口:1次/秒,突发5次
["/api/register"] = {rate = 0.5, burst = 3}, -- 注册接口:0.5次/秒,突发3次
["default"] = {rate = 10, burst = 50} -- 默认:10次/秒,突发50次
}
local limit = limits[uri] or limits["default"]
local key = client_ip .. ":" .. uri
if _M.is_limited(key, limit.rate, limit.burst) then
ngx.status = 429
ngx.header["Retry-After"] = "60"
ngx.say('{"code": 429, "msg": "Too Many Requests"}')
ngx.exit(429)
end
end
return _M4. SQL注入检测模块
SQL注入是最常见的Web攻击方式之一。我们通过多层检测机制来防御SQL注入:
-- sql_injection.lua
local _M = {}
-- SQL注入检测规则
local sql_patterns = {
-- 基础SQL语句
"select.*from", "union.*select", "insert.*into",
"delete.*from", "update.*set", "drop.*table",
-- SQL函数和操作符
"concat.*\\(", "group_concat.*\\(", "union.*all",
"information_schema", "sysobjects", "syscolumns",
-- 注释符
"--|#|/\\*|\\*/",
-- 特殊字符组合
"'.*or.*'='", "\".*or.*\"=\"", "1=1", "1=2",
-- 时间盲注
"sleep\\s*\\(", "benchmark\\s*\\(", "waitfor\\s+delay"
}
function _M.check_sql_injection()
-- 检查URL参数
local args = ngx.req.get_uri_args()
for key, val in pairs(args) do
if type(val) == "string" then
if _M.detect_sql_injection(val) then
_M.block_request("SQL Injection in URL parameter: " .. key)
return false
end
elseif type(val) == "table" then
for _, v in ipairs(val) do
if _M.detect_sql_injection(v) then
_M.block_request("SQL Injection in URL parameter: " .. key)
return false
end
end
end
end
-- 检查POST数据
ngx.req.read_body()
local post_args = ngx.req.get_post_args()
if post_args then
for key, val in pairs(post_args) do
if type(val) == "string" and _M.detect_sql_injection(val) then
_M.block_request("SQL Injection in POST parameter: " .. key)
return false
end
end
end
return true
end
function _M.detect_sql_injection(input)
if not input then return false end
-- 转换为小写进行检测
local lower_input = string.lower(input)
-- URL解码
local decoded = ngx.unescape_uri(lower_input)
for _, pattern in ipairs(sql_patterns) do
if ngx.re.find(decoded, pattern, "joi") then
ngx.log(ngx.WARN, "SQL injection detected: ", pattern, " in: ", input)
return true
end
end
return false
end
function _M.block_request(reason)
ngx.log(ngx.ERR, "Blocked request: ", reason)
ngx.status = 403
ngx.say('{"code": 403, "msg": "Potential SQL Injection Detected"}')
ngx.exit(403)
end
return _M5. XSS攻击防护模块
跨站脚本攻击(XSS)是另一个常见威胁。我们的XSS防护模块采用多重策略:
-- xss_filter.lua
local _M = {}
local xss_patterns = {
-- JavaScript事件处理器
"on(load|error|click|mouse|key|submit|focus|blur)\\s*=",
-- Script标签
"<script[^>]*>.*</script>",
"<script[^>]*/>",
-- JavaScript协议
"javascript:\\s*",
"vbscript:\\s*",
-- 危险的HTML标签
"<iframe[^>]*>", "<object[^>]*>", "<embed[^>]*>",
"<applet[^>]*>", "<meta[^>]*>", "<link[^>]*>",
-- 数据URI
"data:text/html",
-- 表达式求值
"eval\\s*\\(", "expression\\s*\\(",
-- 其他危险模式
"document\\.(cookie|write|location)",
"window\\.(location|open)",
"alert\\s*\\(", "prompt\\s*\\(", "confirm\\s*\\("
}
function _M.check_xss()
-- 检查所有用户输入
local args = ngx.req.get_uri_args()
for key, val in pairs(args) do
if _M.detect_xss(val) then
_M.block_xss("XSS in URL parameter: " .. key)
return false
end
end
-- 检查POST数据
ngx.req.read_body()
local post_args = ngx.req.get_post_args()
if post_args then
for key, val in pairs(post_args) do
if _M.detect_xss(val) then
_M.block_xss("XSS in POST parameter: " .. key)
return false
end
end
end
-- 检查Cookie
local cookies = ngx.var.http_cookie
if cookies and _M.detect_xss(cookies) then
_M.block_xss("XSS in Cookie")
return false
end
-- 检查Referer
local referer = ngx.var.http_referer
if referer and _M.detect_xss(referer) then
_M.block_xss("XSS in Referer")
return false
end
return true
end
function _M.detect_xss(input)
if not input then return false end
if type(input) == "table" then
for _, v in ipairs(input) do
if _M.detect_xss(v) then
return true
end
end
return false
end
local decoded = ngx.unescape_uri(input)
local lower = string.lower(decoded)
for _, pattern in ipairs(xss_patterns) do
if ngx.re.find(lower, pattern, "joi") then
ngx.log(ngx.WARN, "XSS detected: ", pattern)
return true
end
end
return false
end
function _M.block_xss(reason)
ngx.log(ngx.ERR, "XSS blocked: ", reason)
ngx.status = 403
ngx.say('{"code": 403, "msg": "XSS Attack Detected"}')
ngx.exit(403)
end
return _M6. 智能CC攻击防护
CC攻击是通过大量看似正常的请求来消耗服务器资源。我们实现了基于行为分析的CC攻击检测:
-- cc_defense.lua
local _M = {}
local cc_store = ngx.shared.cc_store
function _M.check_cc_attack()
local client_ip = ngx.var.remote_addr
local uri = ngx.var.uri
local now = ngx.now()
-- 统计请求频率
local req_key = client_ip .. ":reqs"
local reqs, _ = cc_store:incr(req_key, 1, 0, 60) -- 60秒过期
-- 检测异常行为
if reqs > 100 then -- 1分钟超过100次请求
-- 进一步分析
if _M.analyze_behavior(client_ip) then
_M.block_cc(client_ip)
return false
end
end
return true
end
function _M.analyze_behavior(ip)
-- 分析请求模式
local pattern_key = ip .. ":pattern"
local patterns = cc_store:get(pattern_key)
if not patterns then
patterns = {}
else
patterns = cjson.decode(patterns)
end
-- 记录请求特征
local ua = ngx.var.http_user_agent or ""
local uri = ngx.var.uri
local method = ngx.var.request_method
table.insert(patterns, {
time = ngx.now(),
uri = uri,
method = method,
ua_hash = ngx.md5(ua)
})
-- 只保留最近100条记录
if #patterns > 100 then
table.remove(patterns, 1)
end
cc_store:set(pattern_key, cjson.encode(patterns), 300)
-- 检测异常模式
local suspicious_score = 0
-- 检查User-Agent变化
local ua_set = {}
for _, p in ipairs(patterns) do
ua_set[p.ua_hash] = true
end
local ua_count = 0
for _ in pairs(ua_set) do
ua_count = ua_count + 1
end
if ua_count > 5 then -- 短时间内使用超过5个不同UA
suspicious_score = suspicious_score + 30
end
-- 检查请求间隔
local intervals = {}
for i = 2, #patterns do
local interval = patterns[i].time - patterns[i-1].time
table.insert(intervals, interval)
end
-- 计算间隔标准差
if #intervals > 10 then
local avg = 0
for _, v in ipairs(intervals) do
avg = avg + v
end
avg = avg / #intervals
local variance = 0
for _, v in ipairs(intervals) do
variance = variance + (v - avg) ^ 2
end
variance = variance / #intervals
if variance < 0.01 then -- 请求间隔过于规律
suspicious_score = suspicious_score + 40
end
end
return suspicious_score > 50
end
function _M.block_cc(ip)
ngx.log(ngx.ERR, "CC attack detected from: ", ip)
-- 加入黑名单
local blacklist = ngx.shared.blacklist
blacklist:set(ip, true, 1800) -- 封禁30分钟
ngx.status = 503
ngx.say('{"code": 503, "msg": "Service Temporarily Unavailable"}')
ngx.exit(503)
end
return _M配置文件集成
将所有模块集成到Nginx配置中:
http {
# 定义共享内存区域
lua_shared_dict limit_req_store 10m;
lua_shared_dict blacklist 10m;
lua_shared_dict whitelist 10m;
lua_shared_dict cc_store 50m;
# 初始化WAF
init_by_lua_block {
require "resty.core"
cjson = require "cjson"
-- 加载WAF模块
ip_filter = require "waf.ip_filter"
rate_limiter = require "waf.rate_limiter"
sql_injection = require "waf.sql_injection"
xss_filter = require "waf.xss_filter"
cc_defense = require "waf.cc_defense"
}
server {
listen 80;
server_name example.com;
# 访问阶段执行WAF检查
access_by_lua_block {
-- IP黑白名单检查
ip_filter.check_ip()
-- 速率限制
rate_limiter.check_rate_limit()
-- CC攻击防护
cc_defense.check_cc_attack()
-- SQL注入检测
sql_injection.check_sql_injection()
-- XSS攻击检测
xss_filter.check_xss()
}
location / {
proxy_pass http://backend;
}
# WAF管理接口
location /waf/admin {
content_by_lua_block {
-- 验证管理员权限
local auth = ngx.var.http_authorization
if auth ~= "Bearer your-secret-token" then
ngx.status = 401
ngx.say('{"code": 401, "msg": "Unauthorized"}')
return
end
local method = ngx.var.request_method
local action = ngx.var.arg_action
if method == "POST" then
if action == "add_blacklist" then
local ip = ngx.var.arg_ip
local expire = tonumber(ngx.var.arg_expire) or 3600
ip_filter.add_blacklist(ip, expire)
ngx.say('{"code": 200, "msg": "Success"}')
elseif action == "add_whitelist" then
local ip = ngx.var.arg_ip
local whitelist = ngx.shared.whitelist
whitelist:set(ip, true)
ngx.say('{"code": 200, "msg": "Success"}')
end
elseif method == "GET" then
if action == "stats" then
-- 返回统计信息
local stats = {
blacklist_count = ngx.shared.blacklist:count(),
whitelist_count = ngx.shared.whitelist:count(),
cc_store_count = ngx.shared.cc_store:count()
}
ngx.say(cjson.encode(stats))
end
end
}
}
}
}监控与告警
一个完善的WAF系统必须配备监控和告警机制:
-- monitoring.lua
local _M = {}
function _M.log_attack(attack_type, details)
local log_data = {
timestamp = ngx.now(),
type = attack_type,
client_ip = ngx.var.remote_addr,
uri = ngx.var.uri,
method = ngx.var.request_method,
user_agent = ngx.var.http_user_agent,
details = details
}
-- 写入日志文件
ngx.log(ngx.ERR, cjson.encode(log_data))
-- 发送到监控系统
local http = require "resty.http"
local httpc = http.new()
httpc:request_uri("http://monitoring-system/api/alert", {
method = "POST",
body = cjson.encode(log_data),
headers = {
["Content-Type"] = "application/json",
}
})
end
-- 统计攻击次数
function _M.update_stats(attack_type)
local stats = ngx.shared.stats
local key = "attack:" .. attack_type .. ":" .. os.date("%Y%m%d")
stats:incr(key, 1, 0, 86400) -- 保留一天
-- 检查是否需要告警
local count = stats:get(key)
if count > 1000 then -- 一天超过1000次攻击
_M.send_alert(attack_type, count)
end
end
function _M.send_alert(attack_type, count)
-- 实现告警逻辑,比如发送邮件或短信
ngx.log(ngx.ALERT, "High attack volume detected: ", attack_type, " count: ", count)
end
return _M性能优化技巧
在生产环境中,性能优化至关重要。以下是我们的优化经验:
1. 使用JIT编译优化
确保LuaJIT正常工作,可以显著提升性能:
-- 在init_by_lua_block中添加
require "resty.core" -- 启用LuaJIT FFI加速2. 合理使用共享内存
共享内存是OpenResty的高效数据存储方式,但需要合理规划大小:
# 根据实际需求调整大小
lua_shared_dict limit_req_store 10m; # 存储限速信息
lua_shared_dict blacklist 10m; # IP黑名单
lua_shared_dict whitelist 5m; # IP白名单
lua_shared_dict cc_store 50m; # CC攻击检测数据
lua_shared_dict stats 10m; # 统计信息3. 避免阻塞操作
使用非阻塞的I/O操作,比如使用cosocket API:
-- 异步发送告警
local function async_alert(data)
ngx.timer.at(0, function()
local http = require "resty.http"
local httpc = http.new()
httpc:request_uri("http://alert-service/notify", {
method = "POST",
body = cjson.encode(data)
})
end)
end4. 规则预编译
将正则表达式预编译可以大幅提升匹配性能:
-- 初始化时编译规则
local compiled_rules = {}
for _, pattern in ipairs(raw_patterns) do
table.insert(compiled_rules, ngx.re.compile(pattern, "joi"))
end
-- 使用时直接匹配
for _, regex in ipairs(compiled_rules) do
if ngx.re.match(input, regex) then
return true
end
end实战案例分析
让我分享一个真实的案例。去年黑色星期五期间,我们的电商平台遭受了大规模的攻击。攻击者采用了多种手段:
第一波:暴力破解攻击 攻击者尝试对登录接口进行暴力破解。我们的速率限制模块立即生效,将登录接口的请求限制在每IP每秒1次,成功阻止了99.8%的暴力破解尝试。
第二波:分布式CC攻击 攻击者使用了上千个IP地址发起CC攻击。我们的智能CC防护模块通过分析请求模式,识别出了这些伪装成正常用户的攻击流量。通过检测请求间隔的规律性和User-Agent的异常变化,系统自动将可疑IP加入了临时黑名单。
第三波:SQL注入尝试 在攻击失败后,攻击者尝试通过SQL注入获取数据库信息。我们的SQL注入检测模块成功拦截了所有注入尝试,包括一些使用了编码和混淆技术的高级注入手法。
整个攻击持续了约6小时,我们的WAF系统成功拦截了超过500万次恶意请求,保证了业务的正常运行。更重要的是,整个防护过程完全自动化,没有需要人工干预。
部署建议
基于我们的实践经验,以下是部署OpenResty WAF的建议:
1. 逐步部署 不要一次性启用所有防护规则,建议先在监控模式下运行,收集数据并调优规则,然后逐步启用拦截功能。
2. 建立白名单机制 为内部系统、监控服务、搜索引擎爬虫等建立白名单,避免误拦。
3. 定期更新规则 安全威胁不断演变,需要定期更新检测规则。建议建立规则更新机制,可以从威胁情报源自动获取最新的攻击特征。
4. 做好降级预案 虽然OpenResty性能优秀,但在极端情况下仍可能影响业务。建议准备降级开关,必要时可以快速关闭部分或全部WAF功能。
5. 日志分析与优化 定期分析WAF日志,识别误报和漏报,持续优化规则。我们使用ELK栈来收集和分析WAF日志,通过可视化dashboard实时监控攻击趋势。
总结与展望
通过OpenResty构建WAF系统,我们以极低的成本实现了企业级的Web应用防护。这套系统不仅能够有效抵御常见的Web攻击,还具有良好的扩展性和性能表现。
未来,我们计划引入机器学习技术,通过分析历史攻击数据来自动生成和优化防护规则。同时,我们也在探索将WAF与容器化部署结合,实现更灵活的安全防护架构
Last updated