蓝易云CDN:Web应用防火墙配置CC攻击防护规则防御CC攻击
蓝易云CDN:Web应用防火墙配置CC攻击防护规则
CC攻击的本质是用看似合法的HTTP请求淹没服务器的应用层处理能力。攻击者不需要海量带宽,只需要持续不断地向动态接口发起高频请求,就能让后端数据库连接池耗尽、PHP-FPM进程占满、Java线程池阻塞,最终整个站点陷入无响应状态 ⚠️
蓝易云CDN的WAF模块基于OpenResty构建,CC防护规则直接运行在边缘节点的Lua层,在恶意请求到达源站之前就完成识别和拦截。下面从规则设计逻辑到具体配置方法,完整讲解如何在WAF中构建一套多维度的CC防护体系。

一、WAF中CC防护规则的设计思路
有效的CC防护不能只靠单一维度判断,需要从请求频率、访问路径、客户端特征、行为模式四个层面建立规则矩阵,多条规则协同工作才能兼顾防护精度和业务兼容性 🎯
核心原则:宁可分层递进处置,也不要一刀切封禁。第一层限速告警,第二层JS验证,第三层临时封禁——逐级升级响应力度,把误伤控制在最低。
二、基于URI路径的精细化限速规则
不同路径承受CC攻击的风险完全不同。静态资源几乎不消耗后端资源,而登录、搜索、支付等动态接口每次请求都涉及数据库操作。WAF规则必须按路径分级设置限速阈值:
http {
# 动态API接口 - 严格限速
limit_req_zone $binary_remote_addr zone=api_zone:20m rate=10r/s;
# 页面浏览 - 适度限速
limit_req_zone $binary_remote_addr zone=page_zone:15m rate=40r/s;
# 静态资源 - 宽松限速
limit_req_zone $binary_remote_addr zone=static_zone:10m rate=120r/s;
}
解释: 这里定义了三个独立的限速区域,每个区域使用客户端IP地址($binary_remote_addr,二进制格式占用更少内存)作为限速键。zone=api_zone:20m 表示分配20MB共享内存用于存储IP访问状态,足以同时追踪约16万个不同IP。rate=10r/s 表示API接口每个IP每秒只允许10次请求,而静态资源放宽到120次。三个区域完全独立计数互不干扰。
接下来在对应的location中启用:
server {
location /api/ {
limit_req zone=api_zone burst=15 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location ~ \.(php|jsp|do)$ {
limit_req zone=page_zone burst=30 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location ~* \.(css|js|png|jpg|gif|ico|woff2)$ {
limit_req zone=static_zone burst=80;
proxy_pass http://backend;
}
}
解释: burst=15 允许在短时间内积攒最多15个突发请求,超过速率限制的请求进入这个缓冲队列。nodelay 指示队列中的请求立即处理而非均匀延迟释放,超出burst容量的请求直接被拒绝。limit_req_status 429 将响应码设为429(Too Many Requests),这是HTTP标准中专门用于限速场景的状态码,便于客户端和监控系统准确识别 📊
三、基于请求特征的WAF识别规则
CC攻击工具的HTTP请求往往在头部字段上暴露出明显破绽。WAF可以针对这些特征建立过滤规则:
-- WAF CC特征识别模块
access_by_lua_block {
local headers = ngx.req.get_headers()
local ua = headers["User-Agent"] or ""
local referer = headers["Referer"] or ""
local accept_lang = headers["Accept-Language"] or ""
-- 规则1: 空UA或已知攻击工具UA
local bad_ua_patterns = {
"^$", -- 空User-Agent
"python%-requests", -- Python requests库
"Go%-http%-client", -- Go默认HTTP客户端
"curl/", -- curl命令行工具
"ab/%d", -- Apache Bench压测工具
"wrk", -- wrk压测工具
"siege" -- siege压测工具
}
for _, pattern in ipairs(bad_ua_patterns) do
if ngx.re.find(ua, pattern, "ijo") then
ngx.log(ngx.WARN, "CC-WAF: bad UA blocked, IP=",
ngx.var.remote_addr, " UA=", ua)
return ngx.exit(403)
end
end
-- 规则2: 缺少Accept-Language头
-- 真实浏览器几乎都会发送此头部
if #accept_lang == 0 and ngx.re.find(ua, "Mozilla", "jo") then
ngx.var.cc_suspicious = "1"
end
}
逐段解释:
- 首先通过
ngx.req.get_headers()获取完整的请求头表,分别提取UA、Referer和Accept-Language字段。 bad_ua_patterns列表中定义了多个已知的自动化工具标识。python%-requests中的%-是Lua正则中对连字符的转义,匹配Python requests库的默认UA。ngx.re.find(ua, pattern, "ijo")使用Nginx正则引擎进行匹配,i表示不区分大小写,j启用PCRE JIT编译加速,o表示只编译一次正则表达式缓存复用。- 规则2的逻辑是:如果请求声称自己是Mozilla系浏览器(UA中包含Mozilla字样),却没有发送Accept-Language头部,这在真实浏览器中几乎不会出现,标记为可疑请求进入下一步验证 🔍
四、动态处置——Cookie验证与滑动窗口封禁
仅依靠静态规则拦截远远不够,WAF需要对可疑请求执行动态验证,并对确认的攻击源实施自动封禁:
access_by_lua_block {
local limit = ngx.shared.waf_cc_dict
-- 阶段一:滑动窗口计数
local ip = ngx.var.remote_addr
local uri = ngx.var.uri
local key = ip .. ":" .. uri
local count, err = limit:incr(key, 1, 0, 60)
-- 阶段二:超过阈值触发Cookie验证
if count > 120 then
local token = ngx.var.cookie_waf_cc_token
local expected = ngx.md5(ip .. "lanyiyun_salt_2026" .. os.date("%Y%m%d%H"))
if token ~= expected then
ngx.header["Content-Type"] = "text/html; charset=utf-8"
ngx.header["Cache-Control"] = "no-store"
ngx.say(string.format([[
<html><body>
<script>
(function(){
var t = "%s";
document.cookie = "waf_cc_token=" + t
+ ";path=/;max-age=3600;SameSite=Lax";
setTimeout(function(){ location.reload(); }, 100);
})();
</script>
<noscript>请启用JavaScript后刷新页面</noscript>
</body></html>
]], expected))
return ngx.exit(200)
end
end
-- 阶段三:验证后仍然高频 - 临时封禁
if count > 500 then
local block_key = "blocked:" .. ip
limit:set(block_key, 1, 300) -- 封禁5分钟
ngx.log(ngx.ERR, "CC-WAF: IP blocked for 300s, IP=", ip,
" count=", count, " uri=", uri)
return ngx.exit(444)
end
-- 检查是否在封禁名单中
local blocked = limit:get("blocked:" .. ip)
if blocked then
return ngx.exit(444)
end
}
分阶段解释:
阶段一 使用OpenResty共享内存字典 waf_cc_dict 进行滑动窗口计数。limit:incr(key, 1, 0, 60) 的含义是:对指定key加1,初始值为0,生存时间60秒。也就是统计每个IP在60秒内对每个URI的访问次数,60秒后自动清零重新计算。
阶段二 当60秒内同一IP对同一URI的请求超过120次时,触发Cookie验证。服务端计算一个基于IP、密钥和当前小时数的MD5值作为预期token,返回一段JS让浏览器设置Cookie后刷新。真实浏览器会自动完成验证并携带正确Cookie继续访问,攻击脚本无法执行JS则会被持续拦截在验证环节 ✅
阶段三 如果某个IP即便通过了Cookie验证,60秒内请求仍然超过500次,说明这是具备JS执行能力的高级攻击工具(如Puppeteer、Playwright等),直接写入封禁名单,有效期300秒(5分钟),期间所有请求以444状态码直接断开连接 🚫
五、WAF规则调优的关键要点 ⚡
共享内存容量规划——waf_cc_dict 的大小直接决定能同时跟踪多少个IP+URI的访问状态。建议至少分配32MB,按每条记录约200字节计算,可支撑约17万条并发跟踪记录。在 nginx.conf 的http段声明:
lua_shared_dict waf_cc_dict 32m;
解释: 这条指令在Nginx的worker进程之间创建一块32MB的共享内存区域,所有worker进程读写同一份数据,确保限速计数在多进程间保持一致。
真实IP获取必须前置处理——CDN多级转发场景下,ngx.var.remote_addr 拿到的可能是上游代理节点的IP。必须在WAF逻辑之前通过 set_real_ip_from 指令或Lua代码从 X-Forwarded-For 头中提取真实客户端地址,否则所有规则的IP维度判断都会失准。
分时段动态调整阈值——业务高峰期正常用户的请求频率本身就较高,固定不变的阈值容易造成误封。建议在WAF规则中加入时段判断逻辑,高峰时段适当放宽限速上限,低谷时段收紧,保持防护效果的同时最大程度减少对正常访问的影响 📋
WAF层面的CC防护核心在于分层递进、动态响应。从路径级限速到请求特征过滤,再到Cookie人机验证和自动封禁,四道防线逐级收紧。蓝易云CDN将这套规则体系部署在边缘节点的OpenResty/Lua层,每一次恶意请求都在距离源站最远的位置被拦截,从而确保业务服务器始终保持充裕的处理余量 🛡️