用 20 行 JS 让镜像站流量归零

起因

UpXuu 的博客被人用 Cloudflare Worker 全站镜像——所有文章、图片、页面内容实时同步到另一个域名,未获任何授权。攻击者的套路很简单:wget --mirror 一把梭下载全站,往 Nginx 一丢,改个域名,流量就进了别人口袋。

这种事在技术博客圈不罕见。检查了一下 nixegus.com——没有防护。

那就加上。

核心思路

镜像站的本质矛盾在于:攻击者下载的是静态文件,真实访客打开的是浏览器。 浏览器执行 JavaScript,wget/curl/httrack 不执行。

利用这个差异:在页面嵌入一段 JS,检查当前域名是否匹配,不匹配就跳回真站。

代码

function getTargetHost() {
    const XOR_KEY = "97vaMcLo";
    const XOR_CIPHER = atob("TkABTyMKNApeQgVPLgwh");
    let host = '';
    for (let i = 0; i < XOR_CIPHER.length; i++) {
        host += String.fromCharCode(XOR_CIPHER.charCodeAt(i) ^ XOR_KEY.charCodeAt(i % XOR_KEY.length));
    }
    return host;
}

(function () {
    const targetHost = getTargetHost().trim();
    if (window.location.host === targetHost) return;
    const targetUrl = window.location.protocol + "//" + targetHost + window.location.pathname + window.location.search + window.location.hash;
    window.location.replace(targetUrl);
})();

几个设计选择:

  • XOR 加密域名:不是防破解,是防止攻击者对 HTML 全文搜索域名一键替换。密钥和密文分开存,Base64 再包一层,增加自动化处理的成本。真想破解的话看一眼 JS 就明白了,但批量攻击者不会为这一个站动手。
  • location.replace():不用 location.href,访客点后退回不到镜像站。
  • location.host 而非 hostname:host 包含端口号,确保即使通过带端口的方式访问,也能正确判断域名归属。
  • 沿用当前页面的协议:镜像站用什么协议提供服务,目标 URL 就用什么协议——通常镜像站和真站的协议一致,不需要硬编码。

它能挡住什么

防的不是下载——静态文件本身就是公开的,wget、httrack、curl 都能完整拉走。防的是真实访客通过镜像域名访问页面时,自动跳回真站。

场景 结果
直接访问 nixegus.com 正常,host 匹配,脚本跳过
浏览器访问镜像站 自动跳回 nixegus.com
hosts 劫持域名到恶意 IP 无效——host 字面匹配,劫持不了
访客关闭 JavaScript 无效,JS 不执行
搜索引擎爬虫 正常收录,爬虫不执行 JS

hosts 劫持和禁用 JS 这两种情况无法防御,但它们不影响这个方案的实际效果——批量镜像攻击者的目标是 "偷流量",不是 "攻克特定站点"。只要还有无防御的站可以镜像,他们不会为任何一个站多花一分钟。

成本效益

① wget --mirror 下载全站          ← 成本 0
② 丢到 Nginx 上                    ← 成本 0
③ 发现 JS 跳转 → 流量为零          ← 收入 0
④ 选择:花时间去掉 JS / 换一个站
⑤ → 换一个站                       ← 成本 0,收入正常

花 10 分钟写后处理脚本就能绕过,但批量攻击者面对的不是密码学难题,是一道经济题。无防御的站要多少有多少,他们不会选择动手的。

总结

这个方案不是 "无法绕过",而是 "不值得绕过"。20 行 JS,零服务器开销,零依赖,对正常访客完全无感——当绕过成本远高于换一个目标时,方案就站住了。

评论