CC攻击的本质是耗尽服务器资源,而静态资源和动态请求对资源的消耗模式完全不同。静态资源是典型的IO密集型操作,请求量大但计算成本低,一旦命中缓存几乎不消耗CPU。动态请求则是CPU密集型操作,需要数据库查询、业务逻辑计算、页面渲染,单次请求成本远高于静态资源。攻击者往往利用这一点,通过大量请求动态接口快速压垮后端。所以,CC防护的第一步不是上高防,而是在架构层面把静态和动态彻底分开,让攻击流量尽可能打在静态缓存层,保护动态计算资源不被消耗。
前置条件:域名与DNS层面的分离分离的第一步不是配置服务器,而是域名规划。最理想的方案是静态资源使用独立的静态域名,动态请求使用主域名或API子域名。这样做的意义在于,你可以在CDN或反向代理层对两个域名设置完全不同的防护策略。静态域名可以开启极致的缓存和限流,动态域名则需要更精细的访问控制。如果所有流量都混在同一个域名下,WAF和限流规则会变得复杂且容易误伤。独立域名后,静态域名的CC防护阈值可以设得很高,因为即使有大量请求,CDN边缘节点也能轻松应对,不会回源到你的服务器。
反向代理层的路径分流策略如果无法拆分域名,次优方案是在反向代理层通过URL路径进行分流。以Nginx为例,你需要定义明确的location规则,将静态资源路径和动态请求路径分开处理。静态资源路径通常包括图片、CSS、JavaScript、字体文件等,这些路径可以配置极长的缓存过期时间和特殊的限流策略。动态请求路径则需要更严格的访问频率限制。一个常见的错误是只按文件扩展名分流,攻击者可以构造无扩展名的动态请求绕过规则,所以路径前缀匹配比扩展名匹配更可靠。
# 静态资源路径配置
location ~* ^/(static|assets|images|css|js|fonts)/ {
expires 30d;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options "nosniff";
# 静态资源限流可以非常宽松
limit_req zone=static_zone burst=1000 nodelay;
# 关键:静态资源请求禁止传递Cookie,减少带宽和安全隐患
proxy_set_header Cookie "";
# 如果CDN回源,这里可以设置更高的并发连接数
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}
# 动态请求路径配置
location /api/ {
# 动态请求使用严格的限流策略
limit_req zone=dynamic_zone burst=20 nodelay;
limit_conn conn_zone 10;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
静态资源缓存策略的硬核配置
静态资源防护的核心是让请求永远不要到达源站。这需要三层缓存协同工作:浏览器缓存、CDN缓存、反向代理缓存。浏览器缓存通过Cache-Control头控制,对于带版本号的静态资源,可以直接设置immutable指令,告诉浏览器这个资源永远不会变,一年内不需要重新验证。CDN缓存需要配合文件指纹或内容哈希实现永久缓存,一旦资源内容变化,文件名也随之变化,旧URL自然失效,无需手动刷新缓存。反向代理缓存作为最后的防线,即使CDN回源,也能在代理层命中缓存而不转发到后端应用。很多人忽略了Vary头的正确使用,如果静态资源会根据Accept-Encoding返回不同压缩版本,Vary头必须包含Accept-Encoding,否则可能导致浏览器收到错误的压缩格式。
# 静态资源缓存头配置示例
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
# 带版本号的资源设置长期缓存
if ($request_uri ~* ".*\.[a-f0-9]{8,}\.(js|css)$") {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 未带版本号的资源设置较短缓存
if ($request_uri !~* ".*\.[a-f0-9]{8,}\.(js|css)$") {
expires 7d;
add_header Cache-Control "public, must-revalidate";
}
# 禁止静态资源接收和发送Cookie
proxy_set_header Cookie "";
proxy_hide_header Set-Cookie;
}
动态请求的识别与隔离难点
真正棘手的是那些看起来像静态资源但实际需要动态生成的请求。比如用户头像裁剪、图片水印、动态合成的CSS主题文件。这些请求URL看起来指向一个图片或样式文件,但每次请求都需要服务器计算。攻击者会专门挑选这类接口进行CC攻击,因为它们的单次请求成本高,缓存命中率低。解决方案是在路径设计上就区分开:真正的静态资源放在/static/路径下,动态生成的资源放在/dynamic/或/resource/路径下,并在反向代理层对后者应用动态请求的防护策略。另一个常见漏洞是图片上传后的访问路径,如果上传目录直接被Nginx作为静态文件提供服务,攻击者可以上传大量文件然后并发请求这些URL,虽然单个请求成本低,但大量并发仍会消耗磁盘IO和带宽。应该将上传文件的访问也通过一个轻量级的动态接口代理,加入频率限制。
Cookie与Session的剥离CC攻击中有一个被忽视的攻击面是Cookie处理。每次HTTP请求,浏览器都会自动携带对应域名的Cookie,服务器需要解析Cookie并查找Session,这个操作涉及内存或数据库读写。如果静态资源请求也携带Cookie,不仅浪费带宽,还会触发不必要的Session处理逻辑。在反向代理层剥离静态资源请求的Cookie是必须做的优化。同时,动态请求的Session存储也应该从文件系统迁移到Redis等内存数据库,避免高并发下文件锁竞争。更进一步,对于不需要登录态的公开动态接口,应该完全禁用Session初始化,避免攻击者通过频繁请求消耗Session存储空间。
限流策略的分层设计限流不是简单设置一个全局QPS阈值。静态资源和动态请求需要完全不同的限流维度。静态资源限流应该基于IP加URL路径,阈值可以设得很高,因为CDN和缓存已经承担了大部分压力,能到达源站的静态请求本身就很少。动态请求限流需要更精细的维度:全局QPS限制、单IP限制、单接口限制、单用户限制。对于登录接口、注册接口、密码重置接口,限制应该更加严格,因为这类接口不仅消耗CPU,还涉及邮件发送或短信发送等外部调用,成本极高。限流算法上,滑动窗口比固定窗口更能应对突发流量,令牌桶算法适合需要一定弹性的场景。Nginx的limit_req模块默认使用漏桶算法,可以通过burst参数控制突发容量。
# 分层限流配置
# 定义限流区域
limit_req_zone $binary_remote_addr zone=static_zone:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=dynamic_zone:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=login_zone:10m rate=5r/m;
limit_req_zone $server_name zone=global_dynamic:50m rate=5000r/s;
# 全局动态请求限流
map $uri $is_dynamic {
~^/api/ 1;
default 0;
}
# 登录接口单独限流
location /api/login {
limit_req zone=login_zone burst=3 nodelay;
limit_req zone=global_dynamic burst=100;
proxy_pass http://backend;
}
# 通用API限流
location /api/ {
limit_req zone=dynamic_zone burst=10 nodelay;
limit_req zone=global_dynamic burst=200;
proxy_pass http://backend;
}
回源验证与边缘计算
当CDN缓存未命中需要回源时,攻击者可以构造大量不同参数的请求绕过CDN缓存直接打到源站。这就是缓存穿透攻击。防御缓存穿透需要在CDN层面或反向代理层面做回源验证。一种有效的方法是要求所有回源请求携带一个动态生成的Token,CDN边缘节点在回源前向一个验证服务请求Token,源站验证Token有效后才处理请求。这个Token可以基于时间戳和共享密钥生成,有效期很短。另一种思路是利用边缘计算能力,在CDN边缘节点直接执行简单的验证逻辑,比如检查请求头中的User-Agent是否正常、Referer是否合法、是否携带了合法的Session标识,不符合条件的请求直接在边缘节点返回403,根本不回源。目前主流CDN厂商都支持边缘脚本功能,可以在边缘执行自定义的JavaScript或Lua代码。
动静分离后的监控与告警分离架构部署后,监控必须跟上。需要分别监控静态资源和动态请求的QPS、回源率、缓存命中率、错误率。静态资源的回源率应该趋近于零,如果突然升高,说明缓存策略失效或有人在尝试缓存穿透。动态请求的QPS应该相对平稳,如果出现尖峰且缓存命中率没有同步变化,基本可以确定是CC攻击。告警阈值应该基于历史数据的动态基线,而不是固定值,因为业务流量本身有波峰波谷。监控粒度至少要达到每分钟级别,秒级监控对于捕捉瞬时攻击更有价值。同时,需要监控后端服务器的连接数、CPU负载、数据库慢查询数量,这些指标能反映动态请求的真实压力。
架构兜底:静态化降级当动态请求压力超过后端承载能力时,需要有降级方案。对于内容型网站,可以将热门动态页面预生成为静态HTML文件,在检测到后端负载过高时,自动切换到静态版本。这个切换可以在反向代理层实现,Nginx通过检查后端健康状态,在后端不可用时返回预先准备好的静态文件。对于电商类网站,商品详情页可以预渲染为静态页面,价格和库存信息通过异步接口加载,即使异步接口超时,用户仍能看到基本的商品信息。这种静态化降级不仅是对抗CC攻击的手段,也是应对突发流量的常规架构设计。关键是要提前规划哪些动态内容可以降级,哪些必须保持实时性,并在业务层面做好取舍。
动静分离不是一次性配置,而是一个持续优化的过程。每次业务迭代都可能引入新的动态接口,每次攻击手法都在演变。定期审查访问日志,分析请求分布,找出那些被频繁请求但本应被缓存的资源,调整缓存策略。对于新上线的动态接口,默认应用最严格的限流策略,观察正常流量后再逐步放宽。架构上做到静态资源零回源、动态请求有纵深防护,CC攻击就很难真正威胁到你的核心服务。
