网站静态资源被恶意盗刷,本质上不是带宽被“偷”了,而是你的服务器在替别人的网站买单。最常见的场景是,第三方网站直接在你的图片、CSS、JS 文件地址,用你的资源去加载他们的页面。每一次用户访问他们的页面,都会向你的服务器发起一次请求,你的带宽和服务器资源就这样被白白消耗。这不仅仅是钱的问题,当盗刷流量大到一定程度,你的服务器响应速度会变慢,甚至直接瘫痪,正常用户反而无法访问,严重影响业务。

盗刷的底层逻辑与识别

要解决问题,先得看清问题。盗刷者通常不会下载你的资源再传到自己的服务器,那样太麻烦且成本高。他们会直接在你的资源 URL 放到自己网站的源代码里。从服务器日志看,这些请求有几个明显特征:请求的 HTTP Referer 头信息要么是盗用者的网站域名,要么被刻意伪造或置空;请求的 User-Agent 可能千奇百怪,但请求模式非常固定;短时间内,来自某个特定 IP 或 IP 段的请求量会异常飙升,而且请求的往往是你网站上体积较大的图片、视频或字体文件。

核心防线:Nginx 的 Referer 校验模块

最有效、成本最低的第一道防线,就是利用 Web 服务器自带的 Referer 校验功能。Nginx 的 ngx_http_referer_module 模块就是专门干这个的。它的原理很简单:当一个请求到达时,Nginx 会检查请求头里的 Referer 字段。如果这个字段的值不在你允许的白名单内,或者为空,Nginx 就可以直接拒绝提供服务。这个判断发生在请求处理的早期阶段,对服务器性能的影响微乎其微。

基础配置实战:精确控制访问来源

下面是一段典型的 Nginx 配置,你可以把它放在 server 块或者专门处理静态资源的 location 块里。核心指令是 valid_referers,它用来定义哪些 Referer 是合法的。none 代表请求头中没有 Referer 字段的情况,比如用户直接在浏览器地址栏输入图片地址访问。blocked 代表 Referer 字段存在但被防火墙或代理给删除了值。server_names 会自动允许本站所有的域名。后面的 *.example.com 则是你允许的第三方域名白名单。

location ~* \.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico|css|js|woff|woff2|ttf|eot|svg)$ {
    valid_referers none blocked server_names *.example.com ~\.google\. ~\.bing\.;
    if ($invalid_referer) {
        return 403;
    }
    expires 30d;
    add_header Cache-Control "public, immutable";
}
深入理解 none 和 blocked 的陷阱

上面的配置里,none 和 blocked 这两个参数需要特别小心。如果你允许了 none,意味着任何没有 Referer 头的请求都能直接访问你的资源。这虽然能避免误伤一些正常的用户访问和部分桌面端下载工具,但也给盗刷者留下了巨大的漏洞。他们只需在自己的网站里使用 meta 标签设置 referrer 策略为 no-referrer,或者使用简单的服务端脚本转发请求,就能轻松绕过你的 Referer 校验。因此,如果你的资源具有高度私密性或商业价值,强烈建议在 valid_referers 中移除 none 和 blocked,只保留明确的域名白名单。

进阶防护:结合 Map 模块实现精细化控制

简单的 if 判断和 return 403 虽然直接,但不够灵活。更优雅、更强大的方式是结合 Nginx 的 map 模块。你可以定义一个变量,根据 Referer 的不同状态来动态决定后续的处理逻辑。这样做的好处是,你可以针对不同的无效 Referer 采取不同措施,比如对空 Referer 返回 403 禁止访问,对来自特定恶意域名的请求返回一张体积极小的“警告图片”,而不是直接硬性阻断,这样既能节省带宽,又能起到警示作用。

map $http_referer $referer_policy {
    default valid;
    "~*malicious-site.com" blocked;
    "" blocked;
}

location ~* \.(jpg|png|gif)$ {
    valid_referers none blocked server_names *.yourdomain.com;
    if ($invalid_referer) {
        set $referer_policy invalid;
    }
    if ($referer_policy = invalid) {
        return 403;
    }
    if ($referer_policy = blocked) {
        rewrite ^ /warning.jpg last;
    }
}
Referer 校验的天然缺陷与绕过手法

必须清楚,Referer 校验并非银弹,它只是一种低成本的基础防护。Referer 头信息完全由客户端浏览器生成,可以被轻易伪造。任何有一定技术能力的攻击者,都可以通过 cURL 命令行工具、编写 Python 脚本或在服务端发起 HTTP 请求时,自定义 Referer 头,将其伪造成你的白名单域名,从而轻松绕过校验。此外,一些老旧或不标准的浏览器、企业级防火墙、安全软件,可能会出于“隐私保护”目的而修改或完全删除 Referer 头,这会导致部分真实用户被误封。

多维度组合策略:构建纵深防御体系

正因为 Referer 校验有缺陷,所以绝不能单靠它。一个健壮的防盗刷体系,必须是多种技术的组合拳。第一层是 IP/UA 限频,通过 limit_req 模块,对单个 IP 地址在单位时间内请求静态资源的频率进行限制,可以有效对抗自动化脚本的暴力盗刷。第二层是 User-Agent 黑名单,分析日志,将那些明显是爬虫或脚本工具的 User-Agent 加入拒绝列表。第三层是业务层签名,这是最硬核的防护,原理是后端在生成页面时,为每个静态资源 URL 动态附加一个包含时间戳和签名的参数,资源服务器在收到请求时验证签名和时效性,这能从根本上防止资源被外部长期盗用。

配置 limit_req 模块限制请求频率

这个模块基于“漏桶算法”工作,能有效平滑流量尖峰。首先在 http 块中定义一个共享内存区域,用于存储请求状态,然后在你需要保护的 location 块中引用它。下面的配置定义了一个名为 static_req_zone 的区域,限制每个 IP 每秒只能发起 10 个请求,对于静态资源来说这通常足够了。burst=20 表示允许在短时间内有 20 个请求的突发队列,超过这个数的请求会被直接拒绝。

http {
    limit_req_zone $binary_remote_addr zone=static_req_zone:10m rate=10r/s;
    server {
        location ~* \.(jpg|png|gif|css|js)$ {
            limit_req zone=static_req_zone burst=20 nodelay;
            valid_referers server_names *.yourdomain.com;
            if ($invalid_referer) {
                return 403;
            }
        }
    }
}
终极方案:动态令牌与 CDN 边缘计算

对于版权图片、付费视频等极高价值的资源,业务层签名是必选项。其工作流程是:当用户访问包含受保护资源的页面时,应用服务器使用一个私有密钥,对资源路径、用户 IP、当前时间戳等信息生成一个哈希签名,并将这个签名作为查询参数附加到资源 URL 上。当请求到达 Nginx 或 CDN 节点时,通过一个模块(如 Nginx 的 set_misc 和 Lua 模块)重新计算签名并与请求中的签名进行比对。任何签名缺失、过期或伪造的请求都将被拒绝。更进一步,可以将这套逻辑直接部署到 CDN 的边缘节点上,利用边缘计算能力在请求到达源站之前就完成校验和阻断,把恶意流量挡在门外,这是目前最彻底的解决方案。

监控与日志分析是持续运营的关键

任何策略配置完都不是一劳永逸的。你需要持续监控带宽使用情况和 Nginx 日志。重点关注 403 状态码的激增、来自某个特定域名的 Referer 高频出现、以及异常的用户代理字符串。可以搭建简单的 ELK 或使用 GoAccess 这类工具对日志进行实时分析,生成可视化的报表。当发现新的盗刷源时,迅速将其域名或 IP 加入黑名单。同时,定期审查你的 valid_referers 白名单,确保没有过期的、不再使用的域名留在里面,减少攻击面。防护是一个动态博弈的过程,攻击手法在变,你的策略也需要不断迭代。