蓝易云CDN:UA拦截和URL关键词拦截,大大降低因CC攻击导致的资源消耗
蓝易云CDN:UA拦截和URL关键词拦截降低CC攻击资源消耗
CC攻击防御中有一个常被忽视的关键原则:拦截动作越靠前,资源消耗越小。如果恶意请求已经进入了限速计数、Cookie验证、AI语义分析等复杂处理流程,即便最终被拦截,服务器依然付出了CPU和内存的代价。而UA拦截和URL关键词拦截恰恰工作在请求处理链的最前端——只需简单的字符串匹配,几微秒内就能完成判定并丢弃请求,几乎零开销 ⚡
蓝易云CDN在OpenResty节点的 access_by_lua 阶段最先执行这两类规则,把大量无效请求在入口处就消灭掉,后续的限速模块和验证模块只需要处理真正需要判断的流量。

一、UA拦截:从请求头特征识别攻击工具
User-Agent是HTTP请求中标识客户端身份的头部字段。真实浏览器的UA内容丰富且格式固定,而CC攻击工具的UA往往呈现出几种典型特征:完全为空、使用默认的编程语言标识、或者大量请求携带完全相同的非主流UA字符串 🔍
基础UA黑名单拦截
-- waf_ua_filter.lua 在access阶段最先执行
local ua = ngx.var.http_user_agent or ""
-- 空UA直接拦截
if #ua == 0 then
return ngx.exit(444)
end
-- 已知攻击工具和自动化框架UA特征
local blocked_ua = {
"python%-requests",
"Go%-http%-client",
"Java/",
"okhttp/",
"axios/",
"node%-fetch",
"curl/",
"wget/",
"Scrapy",
"HttpClient",
"libwww%-perl",
"winhttp",
"HTTrack",
"MJ12bot",
"AhrefsBot",
"SemrushBot",
"sqlmap",
"Nmap",
"masscan",
"zgrab"
}
for _, pattern in ipairs(blocked_ua) do
if ngx.re.find(ua, pattern, "ijo") then
ngx.log(ngx.WARN, "[UA-BLOCK] ", ngx.var.remote_addr, " | ", ua)
return ngx.exit(444)
end
end
逐段解释:
ngx.var.http_user_agent获取当前请求的UA字符串,如果该头部缺失则赋值为空字符串。UA完全为空的请求在正常浏览场景中几乎不存在,直接以444状态码断开连接(不返回响应体,比返回403更节省带宽)。blocked_ua表中列出了常见的攻击工具和爬虫标识。python%-requests中%-是Lua模式匹配中对连字符的转义,匹配Python requests库发出的默认UA。sqlmap、Nmap、masscan、zgrab则是已知的安全扫描和漏洞探测工具。ngx.re.find(ua, pattern, "ijo")执行正则匹配,其中i不区分大小写,j启用PCRE JIT加速编译,o让正则表达式只编译一次后缓存复用,后续匹配直接使用编译结果,性能极高。- 命中任意一条规则即记录告警日志并立即终止请求,不会进入后续的任何处理环节 🛡️
进阶:UA频率异常检测
单纯的黑名单无法拦截伪造了真实浏览器UA的攻击流量。此时可以结合UA出现频率进行辅助判断:
local ua_dict = ngx.shared.ua_counter
local ua_key = ngx.md5(ua)
local count = ua_dict:incr(ua_key, 1, 0, 30)
-- 30秒内同一UA出现超过2000次视为异常
if count > 2000 then
ua_dict:set("ban:" .. ua_key, ua, 600)
ngx.log(ngx.ERR, "[UA-FREQ] blocked UA appearing ", count,
" times/30s: ", ua:sub(1, 80))
return ngx.exit(444)
end
-- 检查是否已被频率封禁
local banned = ua_dict:get("ban:" .. ngx.md5(ua))
if banned then
return ngx.exit(444)
end
解释: 利用共享内存字典 ua_counter 对每个UA字符串的MD5哈希值进行计数,incr(ua_key, 1, 0, 30) 表示计数加1、初始值0、30秒后过期。当某个UA在30秒内出现超过2000次时,将其写入封禁记录,有效期600秒(10分钟)。正常情况下即使是Chrome浏览器的UA,30秒内来自不同用户的请求也不太可能完全一致地达到如此高频——因为浏览器版本号、操作系统版本、补丁级别的差异会使UA略有不同 📊
需要在
nginx.conf中声明共享内存:lua_shared_dict ua_counter 16m;
二、URL关键词拦截:精准过滤高危路径
CC攻击集中火力的目标几乎都是动态接口。通过分析攻击日志可以发现,攻击者倾向于反复请求特定的URL路径或携带特定查询参数。URL关键词拦截就是针对这些高频攻击目标建立快速过滤规则 🎯
路径级关键词拦截
local uri = ngx.var.request_uri or ""
-- 高危路径关键词列表
local blocked_uri_keywords = {
"/wp%-login", -- WordPress登录页
"/wp%-admin", -- WordPress后台
"/xmlrpc%.php", -- WordPress XML-RPC接口
"/administrator", -- Joomla后台
"/phpmyadmin", -- 数据库管理面板
"/eval%-stdin", -- 远程代码执行探测
"%.env", -- 环境变量文件探测
"/%.git", -- Git仓库泄露探测
"/config%.json", -- 配置文件探测
"/actuator", -- Spring Boot端点
"/solr/admin", -- Solr管理接口
"/console", -- 调试控制台
}
local uri_lower = string.lower(uri)
for _, keyword in ipairs(blocked_uri_keywords) do
if ngx.re.find(uri_lower, keyword, "jo") then
ngx.log(ngx.WARN, "[URL-BLOCK] ", ngx.var.remote_addr,
" | ", uri:sub(1, 200))
return ngx.exit(403)
end
end
解释: 这段规则拦截的都是在CDN代理的实际业务中大概率不存在、但经常被攻击工具批量扫描的路径。比如当蓝易云CDN代理的客户网站并非WordPress时,大量涌入的 /wp-login.php 请求显然是攻击扫描,直接拦截即可。%. 是Lua模式中对点号的转义,避免匹配到不相关的路径。uri:sub(1, 200) 在日志中只记录URI前200个字符,防止超长恶意URI撑爆日志文件 📋
查询参数关键词拦截
CC攻击有时不改变路径,而是通过不断变化查询参数来生成"不同"的请求以绕过缓存:
local args = ngx.var.args or ""
-- 拦截查询参数中的可疑特征
local blocked_arg_patterns = {
"union%s+select", -- SQL注入特征
"concat%(.-%)%s", -- SQL函数注入
"<%s*script", -- XSS攻击特征
"javascript%s*:", -- JS协议注入
"%.%.%/%.%.%/", -- 路径穿越攻击
"etc%/passwd", -- 文件读取探测
"base64_decode", -- PHP代码注入
"${jndi:", -- Log4Shell探测
}
for _, pattern in ipairs(blocked_arg_patterns) do
if ngx.re.find(args, pattern, "ijo") then
ngx.log(ngx.ERR, "[ARGS-BLOCK] ", ngx.var.remote_addr,
" | pattern=", pattern, " | args=", args:sub(1, 150))
return ngx.exit(403)
end
end
解释: 这层规则同时覆盖了CC攻击和常见的Web漏洞探测行为。union%s+select 匹配SQL注入中的联合查询关键字,%s+ 匹配一个或多个空白字符。${jndi: 匹配2021年底曝出的Log4Shell漏洞(CVE-2021-44228)的利用载荷特征,至今仍然是互联网上最频繁的自动化扫描目标之一。这些请求一旦大规模涌入就构成CC攻击,在参数级别直接拦截效率最高 🚫
三、两套规则的协同执行顺序
将UA拦截和URL关键词拦截组合在一起,建立一个清晰的执行优先级:
server {
# 第一层:UA拦截(最快)
access_by_lua_file /etc/openresty/waf/ua_filter.lua;
# 第二层:URL关键词拦截
access_by_lua_file /etc/openresty/waf/url_filter.lua;
# 第三层:频率限速(仅处理前两层未拦截的请求)
limit_req zone=cc_zone burst=30 nodelay;
location / {
proxy_pass http://backend;
}
}
解释: access_by_lua_file 按声明顺序执行。UA过滤在最前面,因为它只需读取一个头部字段做字符串匹配,开销最低。通过UA过滤后才进入URL关键词检查,两层都通过的请求最后才交给 limit_req 限速模块。这样的顺序确保 limit_req 的共享内存计数器中只记录真正需要判断的有效请求,避免大量垃圾请求占用限速区域的内存空间 ⚡
注意:如果使用多个
access_by_lua_file,后一个会覆盖前一个。实际部署时应将两套规则合并到同一个Lua文件中按顺序执行,或使用access_by_lua_block内嵌dofile()依次加载。
四、资源消耗对比与调优建议
实际测试数据可以直观说明前置拦截的价值:当CC攻击流量为每秒5万个请求时,如果全部进入限速模块处理,Nginx的worker进程CPU占用率会飙升到70%以上,共享内存锁竞争加剧。而在前面加上UA+URL关键词拦截后,通常能直接过滤掉60%到80%的攻击请求,进入限速模块的流量降到每秒1万到2万,CPU占用率回落到30%以下 📉
规则维护建议:
定期从WAF拦截日志中提取高频出现的新攻击UA和URL特征,补充到拦截列表中。建议每周做一次日志审计,用简单的统计命令即可:
# 统计被拦截最多的UA前20名
grep "UA-BLOCK" /var/log/openresty/waf.log | \
awk -F'|' '{print $NF}' | sort | uniq -c | sort -rn | head -20
解释: 这条命令从WAF日志中筛选出所有UA拦截记录,用 awk 提取UA字段,然后通过 sort | uniq -c | sort -rn 进行频次统计并倒序排列,最后取前20条。根据统计结果可以发现新出现的攻击工具UA特征,及时补入黑名单。
UA拦截和URL关键词拦截是CC防护体系中性价比最高的两道防线——规则简单、执行极快、误伤率低。蓝易云CDN将这两层过滤部署在请求处理链的最前端,用最小的计算代价拦截最大比例的恶意流量,为后续的限速、验证、AI分析等高级防护模块留出充足的系统资源 🛡️