静态资源DDoS攻击和普通CC攻击最大的区别在于,攻击者往往不需要构造复杂的请求,只需反复拉取JS、CSS、图片、字体或视频切片等固定URL,就能让源站带宽、磁盘IO和CPU迅速耗尽。由于这些资源天然需要高可用、低延迟,很多团队又习惯把静态文件直接放在源站或普通CDN后面,一旦遇到每秒数十万乃至上百万请求的混合流量,常规限速策略很容易误伤正常用户。要解决这个问题,核心不是简单堆带宽,而是把静态资源的访问路径、缓存策略、校验机制和应急降级做成一个可独立切换的防护层。
先梳理静态资源的暴露面与请求特征
动手优化前,需要把站点所有静态资源按类型和路径做一次完整盘点。常见的高危目标包括:统一版本的bundle.js、首页大图、字体文件、PDF下载、视频首帧、验证码图片、头像缩略图以及接口返回的静态JSON。攻击者通常会挑选体积较大、源站生成成本高或缓存命中率低的资源下手。例如一张未经压缩的首页Banner可能有2MB,攻击者以10万QPS请求这个URL,瞬时出口带宽就会达到200Gbps级别。又比如某些图片接口需要实时裁剪尺寸,攻击者不断更换尺寸参数,就能绕过CDN缓存直接回源。梳理时建议记录每个资源的平均大小、是否可缓存、缓存键是否包含查询参数、回源频率、是否依赖数据库或动态生成。
把静态资源从主站域名彻底拆离
很多网站把静态资源和HTML页面混在同一个域名下,例如www.example.com/assets/logo.png。这种结构下,一旦静态资源被打垮,主站HTML也无法访问,攻击者用极低成本就能造成整站瘫痪。更合理的做法是启用独立静态域名,如static.example.com或cdn.example.com,并绑定独立的CDN加速区域和防护策略。主站HTML保持轻量,只负责加载逻辑和跳转,静态资源全部走独立域名。这样即使静态域名遭受大规模DDoS,主站核心页面仍可通过降级方案维持基本访问,攻击者也难以通过静态资源耗尽主站连接数。
独立域名还有一个好处:可以针对静态资源设置更激进的缓存头。主站HTML通常需要短缓存或协商缓存,而静态资源可以放心使用长缓存。建议对带版本号的资源设置Cache-Control: public, max-age=31536000, immutable,对不带版本号但内容稳定的资源设置max-age=86400。immutable指令能告诉浏览器资源不会变化,减少304请求。对于字体、图片等二进制文件,还可以配合ETag和Last-Modified,但不要同时使用过多验证头,避免增加响应头开销。
利用CDN边缘节点做第一道过滤
静态资源防护必须把压力挡在源站之外。CDN边缘节点是天然的分布式防火墙,但前提是配置正确。首先要开启CDN的DDoS防护模块,针对静态域名设置独立的阈值策略。很多团队习惯用全局阈值,结果静态资源流量一大就触发全站限速,正常用户被误伤。建议按资源类型设置差异化阈值:图片和CSS的请求频率可以放宽,但单IP对同一URL的重复请求要限制;字体文件体积较大,可以设置更严格的单IP速率;视频切片则需要按会话维度限制,而不是按IP简单封禁。
CDN层还需要开启智能压缩和格式转换。对图片启用WebP或AVIF实时转换,对文本资源启用Brotli压缩,能显著降低带宽消耗。攻击者即使持续请求,边缘节点返回的压缩后内容也远小于源站原始文件。同时开启HTTP/2或HTTP/3,减少连接开销,避免攻击者利用大量短连接拖垮边缘节点。对于支持Range请求的视频或大文件,要限制单次Range的字节范围,防止攻击者通过超长Range请求制造回源压力。
源站侧设置多级缓存与回源保护
CDN缓存命中率再高,也会有回源请求。源站必须做好回源保护,否则攻击者只需构造少量未命中缓存的请求就能打垮源站。第一级保护是源站本地缓存。Nginx可以配置proxy_cache或fastcgi_cache,把静态资源缓存到本地磁盘或内存。对于动态生成的图片,可以按尺寸、裁剪参数做缓存键,缓存时间设置为一小时到一天。第二级保护是回源限速。在Nginx中使用limit_req模块,对回源请求按URL模式限速,例如对/assets/目录下的请求限制每秒不超过200次,超出后返回444或503。第三级保护是连接数限制,使用limit_conn限制单IP到源站的并发连接数,静态资源域名的并发连接数可以设得较低,因为正常浏览器对同一域名的并发连接通常不超过6个。
更进一步的方案是在源站前部署一层轻量级反向代理,专门处理静态资源回源。这个代理层可以独立于主站应用服务器,只负责静态文件的读取、缓存和响应。即使攻击流量穿透CDN,也只会打在这个代理层上,不会影响数据库和业务逻辑。代理层可以使用OpenResty或Go语言编写,针对静态资源做内存缓存和快速响应,单机即可扛住数万QPS。
实施基于URL指纹的精准限速
传统按IP限速在静态资源防护中效果有限,因为攻击者可以使用大量代理IP,而正常用户可能共享同一出口IP。更有效的做法是基于URL指纹和行为特征做限速。URL指纹是指把请求路径、关键查询参数、资源类型组合成一个哈希值,统计单位时间内同一指纹的请求次数。例如/assets/js/app.js和/assets/css/style.css是两个不同指纹,攻击者集中请求app.js时,只对app.js的指纹做限制,不会影响style.css的正常访问。
具体实现可以在CDN边缘或源站代理层完成。以Nginx为例,可以使用Lua脚本在access阶段计算URL指纹,并写入共享内存字典做计数。当某个指纹在10秒内请求超过5000次时,对该指纹返回429或直接断开连接,同时记录日志。对于带随机查询参数的攻击,需要先做参数归一化,去掉无关参数或对参数排序后再计算指纹。例如图片裁剪接口/image?w=100&h=100&token=abc和/image?w=100&h=100&token=xyz应视为同一指纹,因为攻击者可能在不断更换token值。
引入浏览器行为验证与JavaScript挑战
对于穿透CDN和限速策略的高级攻击,可以在静态资源响应前加入轻量级JavaScript挑战。具体做法是:当某个URL指纹的请求量超过阈值时,边缘节点不再直接返回资源,而是返回一段极小的HTML或JS代码,要求客户端执行计算并携带结果重新请求。正常浏览器会自动执行这段代码,攻击脚本如果没有完整的浏览器环境,就无法通过验证。这种方案对用户体验影响很小,因为静态资源请求通常由浏览器自动发起,用户无感知。
实现时要注意挑战逻辑不能过于复杂,否则会拖慢首屏加载。建议使用简单的PoW(工作量证明)算法,客户端计算一个哈希值,耗时控制在50到100毫秒。对于不支持JavaScript的客户端,可以降级为Cookie验证或直接放行,但要设置更严格的速率限制。验证通过后,边缘节点下发一个短期有效的Cookie或签名参数,后续请求携带该凭证即可直接获取资源,避免重复挑战。
静态资源版本化与不可变缓存策略
版本化是静态资源防护中经常被忽视的一环。如果资源URL不包含版本号,每次更新内容都需要修改缓存策略,攻击者也可能利用旧版本URL绕过防护。建议在构建阶段为所有静态资源生成内容哈希,例如app.a1b2c3.js。这样每个版本的资源都有唯一URL,CDN和浏览器可以安全地使用immutable缓存。攻击者即使知道某个旧版本URL,也无法影响新版本资源的访问。同时,版本化让URL指纹更加精确,限速策略可以针对具体版本文件做独立计数。
对于无法版本化的资源,例如用户上传的图片,可以在URL中加入上传时间戳或用户ID哈希。例如/upload/20240101/abc123.jpg。这样每个用户的上传文件都有独立路径,攻击者难以集中攻击同一批文件。如果攻击者针对某个用户的上传文件发起攻击,限速策略可以快速定位并隔离该路径,不影响其他用户的资源加载。
应急降级与静态化兜底方案
任何防护策略都可能被突破,必须准备应急降级方案。当静态资源域名遭受超大流量攻击时,可以快速切换到一个极简的静态化版本。这个版本只包含最基础的CSS和少量内联脚本,所有图片替换为纯色占位或系统图标,视频和字体直接禁用。降级页面可以托管在一个独立的、带宽储备充足的对象存储服务上,通过DNS快速切换。切换时间控制在5分钟以内,保证核心内容仍可访问。
降级方案需要提前准备并定期演练。建议在代码中预留一个开关,当检测到静态资源加载失败率超过30%或响应时间超过5秒时,自动触发降级。前端可以监听资源加载错误事件,当错误率达到阈值时,动态移除外部样式表和脚本,注入内联的最小化样式,保证页面基本可读。这种客户端自动降级比DNS切换更快,能在攻击初期就减少用户体验损失。
监控与日志分析是持续优化的基础
静态资源DDoS防护不是一次性配置,需要持续监控和调整。重点监控指标包括:CDN边缘节点命中率、回源请求量、源站带宽、单URL请求频率、4xx和5xx比例、平均响应时间。当某个静态资源的请求量突然飙升时,要能快速定位是正常流量增长还是攻击行为。建议把CDN日志和源站日志接入统一分析平台,按URL指纹聚合统计,设置异常检测规则。例如某个资源过去7天平均每分钟请求1000次,突然达到每分钟10万次,就触发告警并自动执行临时限速。
日志分析还能帮助发现防护策略的误伤情况。如果某个正常用户群体因为共享IP或特殊网络环境被限速,需要及时调整阈值或加入白名单。对于移动端用户,由于网络切换频繁,IP变化较大,基于IP的限速要更加宽松,可以结合设备指纹或会话ID做辅助判断。定期复盘攻击事件,分析攻击流量特征和防护效果,把新的攻击模式更新到防护规则中,才能让静态资源防护体系越来越健壮。
