蓝易云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。sqlmapNmapmasscanzgrab 则是已知的安全扫描和漏洞探测工具。
  • 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分析等高级防护模块留出充足的系统资源 🛡️

THE END