CC攻击打静态资源这条路,很多运维一开始没想通。攻击者不碰你的登录接口,不碰搜索,不碰数据库查询,就盯着JS、CSS、图片、字体文件反复请求。表面看这些资源走CDN,源站压力不大,实际上多数中小站点静态资源回源策略宽松,一旦命中回源规则,源站带宽和连接数很快被占满。静态资源恶意请求的本质不是打垮业务逻辑,而是用海量合法请求消耗边缘节点和源站之间的回源带宽、文件句柄以及TLS握手资源。

要解决这个问题,先得明确一个判断:静态资源请求的“合法性”不能只看URL对不对。攻击请求的URL往往完全正确,User-Agent也可以伪造得很正常,甚至Referer都能带上你的域名。所以防护思路必须从“这个请求像不像攻击”转向“这个请求在资源消耗上是否合理”。换句话说,静态资源CC防护的核心不是识别坏人,而是限制单个访问主体对静态资源的消耗速率和回源频率。

先把静态资源与动态请求的防护面拆开

很多站点把WAF或限流规则一把梭,动态接口和静态文件共用一套策略。这样做的后果是要么静态资源被误伤,导致页面白屏或样式丢失,要么动态接口的防护阈值被静态流量拉高,攻击者稍微控制一下速率就能穿透。正确的做法是在接入层就把静态资源单独标记出来。比如按扩展名划分:css、js、png、jpg、jpeg、gif、webp、svg、woff、woff2、ttf、mp4、mp3、zip、pdf等。对于这些路径,可以配置独立的频率阈值和回源保护策略。

具体操作上,在Nginx层面可以用location匹配静态目录或扩展名,单独设置limit_req和limit_conn。例如:

location ~* \.(css|js|png|jpg|jpeg|gif|webp|svg|woff2?|ttf|mp4|pdf)$ {
    limit_req zone=static burst=50 nodelay;
    limit_conn static_conn 20;
    expires 30d;
    add_header Cache-Control "public, max-age=2592000, immutable";
    try_files $uri =404;
}

这里的关键不是限速数值本身,而是把静态资源的连接数和请求速率压到一个源站能轻松承受的范围。攻击者哪怕用大量IP分散请求,单个IP的连接数限制也会让攻击成本大幅上升。如果攻击者用代理池,那就要配合更上层的策略,比如CDN边缘的限速和挑战机制。

回源保护是静态资源CC防护的核心

静态资源最大的优势是可以被缓存。只要边缘节点缓存住了,源站根本不需要响应后续请求。攻击者之所以能对静态资源造成压力,往往是因为缓存命中率不够高,或者缓存规则有漏洞。常见的漏洞包括:URL带随机参数时回源、Accept-Encoding变化导致回源、Cookie变化导致回源、以及CDN缓存键配置不当导致每个请求都回源。

检查缓存键配置是第一优先级的动作。静态资源的缓存键应该只包含scheme、host和URI路径,不应该包含查询字符串、Cookie、User-Agent等变化频繁的字段。比如在CDN控制台把缓存键设置为“忽略查询字符串”,或者在Nginx自建缓存时使用proxy_cache_key明确指定:

proxy_cache_key "$scheme$host$uri";

如果业务上确实需要版本号参数来更新静态资源,建议把版本号放进路径里,比如/assets/v123/app.js,而不是/assets/app.js?v=123。这样缓存键天然稳定,攻击者加随机参数也不会导致回源。

另一个容易被忽视的点是HTTP/2和TLS握手。静态资源CC攻击常常用大量短连接反复建立TLS握手,即使请求被缓存命中,握手本身也会消耗CPU。对于这种情况,可以在边缘节点开启TLS会话复用,提高session ticket的复用率,同时限制单IP的TLS握手速率。Nginx里可以用limit_req配合ssl_handshake阶段,或者用fail2ban类工具在系统层面对高频握手IP做临时封禁。

针对静态资源恶意请求的行为特征做精准限制

静态资源CC攻击的请求模式往往有几个明显特征:单IP请求的资源数量极大,但请求的资源种类集中;请求间隔极短,几乎没有正常用户的思考时间;User-Agent集中在少数几种,甚至全部相同;Referer要么缺失,要么固定指向某个页面;请求的静态资源往往是大文件,比如视频、大图、字体包。针对这些特征,可以在接入层做组合判断。

一个有效的策略是对静态资源实施“访问节奏”限制。正常用户打开一个页面,会在短时间内请求一批静态资源,然后进入浏览期,请求频率骤降。攻击者则持续高频请求,没有节奏变化。可以设置这样的规则:单IP在10秒内请求静态资源超过200次,且连续两个10秒窗口都超过阈值,则触发验证码或封禁。这个逻辑在Nginx里用limit_req的delay和nodelay参数配合自定义错误页来实现,或者在CDN的WAF规则里配置“速率限制+惩罚动作”。

更精细的做法是根据资源类型设置不同的阈值。图片和CSS这类小文件,正常页面一次可能加载几十个,阈值可以放宽;视频和压缩包这类大文件,正常用户不可能在短时间内请求几十次,阈值应该收紧。比如对mp4、zip、pdf等扩展名,单IP每分钟请求超过10次就直接拒绝,因为这些资源在正常浏览中不会频繁触发。

利用浏览器行为验证过滤非浏览器流量

静态资源请求通常由浏览器自动发起,攻击脚本则用requests、curl、headless浏览器等工具。在边缘节点对可疑请求下发JavaScript挑战,可以过滤掉大部分脚本流量。不过要注意,静态资源请求本身不能全部加JS挑战,否则会破坏页面加载。正确的做法是:先放行正常请求,当单IP触发速率阈值后,对该IP后续的静态资源请求下发JS挑战。如果客户端能执行JS并携带正确的cookie回来,说明是真实浏览器,可以继续放行;否则直接拒绝。

这个方案在CDN层面实现比较方便,很多CDN的WAF都支持“速率限制+JS挑战”的组合动作。如果是自建Nginx,可以用lua-resty-waf或OpenResty写一段逻辑:当某个IP的静态资源请求速率超过阈值时,返回一个带JS挑战的HTML页面,同时设置一个短期cookie。后续请求必须携带这个cookie才能访问静态资源。攻击脚本如果不处理JS和cookie,就会被持续拦截。

源站层面的兜底策略

即使边缘节点做了各种限制,攻击者仍然可能直接攻击源站IP。所以源站本身也要有静态资源的防护能力。除了前面提到的limit_req和limit_conn,还应该配置以下措施:

第一,源站只允许来自CDN节点或指定代理的IP访问静态资源,其他来源一律拒绝。这可以通过防火墙规则或Nginx的allow/deny指令实现。如果攻击者不知道源站IP,这个策略能挡住大部分直接攻击。

第二,对静态资源开启操作系统层面的文件句柄优化。大量并发请求会消耗文件句柄,调整系统的file-max、ulimit和Nginx的worker_rlimit_nofile,确保源站在高并发下不会因为句柄耗尽而拒绝服务。

第三,对静态资源做内容分发隔离。把大文件静态资源单独放到对象存储或专用的静态资源服务器上,与主站源站分离。这样即使静态资源被打满,也不会影响核心业务接口。

监控和日志分析是持续防护的关键

静态资源CC攻击的形态会不断变化,攻击者会调整请求频率、资源类型和IP池。所以防护策略不能一成不变。需要持续监控静态资源的请求量、回源带宽、缓存命中率、单IP请求分布等指标。重点看几个数据:缓存命中率是否突然下降、回源带宽是否异常升高、静态资源请求的IP集中度是否变高、特定资源的请求量是否远超正常水平。

日志分析可以用简单的awk命令统计单IP请求次数:

awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

也可以统计请求最频繁的静态资源路径:

awk '$7 ~ /\.(css|js|png|jpg|jpeg|gif|webp|svg|woff2?|ttf|mp4|pdf)$/ {print $7}' access.log | sort | uniq -c | sort -rn | head -20

一旦发现异常,可以快速调整阈值或封禁相关IP段。对于长期存在的攻击源,可以在边缘节点设置黑名单,或者通过GeoIP限制某些地区的访问,前提是业务本身没有这些地区的用户。

静态资源CC攻击的防护不是一次性配置就能一劳永逸的事情。它需要从缓存策略、速率限制、行为验证、源站兜底到持续监控形成一套完整的闭环。核心原则是:让边缘节点尽可能多地承担请求,让源站尽可能少地参与静态资源响应,让攻击者的请求成本尽可能高,让正常用户的访问体验尽可能不受影响。做到这四点,静态资源恶意请求的威胁就能被控制在可接受的范围内。