侧边栏壁纸
  • 累计撰写 185 篇文章
  • 累计收到 2 条评论

搜自己文章全是别人的镜像站还被降权?排查 Nginx default_server 漏配、Host 头伪造与 443 证书穿透我踩过的几个坑

2026-10-10 / 0 评论 / 14 阅读
AI 摘要由 AI 生成

发现自身文章被恶意镜像站侵权,通过排查Nginx配置问题成功应对。文章指出Nginx默认server处理请求存在隐患,导致恶意用户通过垃圾域名建立镜像站,窃取内容权重。作者分享了如何利用Nginx的默认server规则和SSL穿透问题进行防护,通过配置444状态码和SSL默认server策略来阻止恶意行为。

前阵子看站点的收录与展现量监控,曲线突然跌了将近六成。我第一反应是搜索算法调整或者近期改版漏掉了什么重定向。结果把最近几天发布的文章标题原样放到百度和 Google 里搜索,排在第一位的根本不是我的原站,而是一个完全没见过的 .top 域名。

点进那个陌生域名一看,页面布局、文章内容、排版、甚至站内链接全部一模一样。我一开始以为是有人用脚本抓取采集,但当我在原站随便改动一个字并刷新那个域名时,内容居然瞬间同步变了。

这不是常规的离线爬虫采集,而是对方直接把一个垃圾域名解析到了我的服务器公网 IP 上,做了一个活生生的“镜像站”。更恶心的是,搜索引擎看到两套一模一样的页面,把那个老域名的权重判定得更高,原站反而被判定为重复内容遭到降权。

在排查和封堵这种恶意镜像绑定的过程中,我前前后后踩了五个隐蔽的坑,把排查细节和最终配置梳理出来。


陷阱一:以为没绑域名就打不开?Nginx 默认匹配规则在背刺你

很多人以为,只要在 Nginx 配置文件里写了 server_name example.com www.example.com;,只要请求里的域名不是这两个,Nginx 就应该拒绝访问或者报错。

事实完全相反。

Nginx 处理 HTTP 请求的流程是这样的:当请求进入某个端口(比如 80 或 443)后,Nginx 会遍历该端口绑定的所有虚拟主机(server 块)。如果请求头中的 Host 字段匹配到了某个 server_name,就交给它处理;但如果没有任何一个 server_name 能匹配上,Nginx 会默默把这个请求交给该端口上的第一个 server 块处理。

这意味着什么?

如果你服务器上只有一个主站配置,或者把主站配成了第一顺位加载的文件,那么任意野域名只要把 A 记录指向你的服务器 IP,Nginx 就会自动用你的主站去响应它。

对方不需要买服务器,不需要搭环境,只要花几块钱买个垃圾域名,把解析一改,就能坐享你整站的内容,并利用搜索引擎收录机制分流你的权重。

最直接的反制是加一个专门的兜底块:

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

这里有两个细节:

  1. default_server 明确告诉 Nginx,只要匹配不到域名的 HTTP 请求,统一路由到这里。
  2. 状态码写 444。这是 Nginx 独有的非标状态码,它的含义是直接关闭 TCP 连接,不向客户端发送任何 HTTP 响应头。对比返回 403 或 404,444 不消耗服务器带宽,不给恶意探测者留任何响应特征,爬虫抓几次遇到连接被重置(Connection reset),就会迅速放弃。

陷阱二:80 端口配了 444,但 443 端口被 TLS 握手和 SNI 穿透

配完 80 端口的 return 444; 之后,我用 curl -I -H "Host: rogue-domain.top" http://SERVER_IP 测试,终端立刻报错 curl: (52) Empty reply from server,直接断开连接,效果符合预期。

但我顺手测了一下 HTTPS:

curl -k -I -H "Host: rogue-domain.top" https://SERVER_IP

返回的结果居然依然是 HTTP/2 200 OK,并且照样返回了主站首页!

为什么 80 端口生效了,443 端口却漏了?

因为 HTTPS 请求在传输应用层 HTTP 头之前,必须先完成 TLS 握手。在握手阶段,客户端会通过 SNI(Server Name Indication)传递目标域名。如果你的 443 端口没有单独配置 default_server,Nginx 在建立 TLS 会话时,会默认 fallback 到当前端口配置的第一个 SSL 站点证书。

更麻烦的是,如果直接在 443 端口简单写:

server {
    listen 443 ssl default_server;
    server_name _;
    return 444;
}

执行 nginx -t 会直接报错退回:nginx: [emerg] no "ssl_certificate" is defined for the "listen ... ssl" directive。因为 Nginx 规定只要加了 ssl 参数,就必须指定有效的证书和密钥文件,否则无法完成握手前的配置加载。

解决这个问题的正确做法是,在服务器本地生成一张自签名的“哑证书”(Dummy Certificate),专门用来作为 443 端口兜底挂载:

mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/dummy.key \
  -out /etc/nginx/ssl/dummy.crt \
  -subj "/CN=untrusted.domain"

然后把兜底的 443 虚拟主机补全:

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    ssl_certificate /etc/nginx/ssl/dummy.crt;
    ssl_certificate_key /etc/nginx/ssl/dummy.key;

    # 遇到未匹配的 HTTPS 流量直接断开
    return 444;
}

这样不管是直接拿 IP 访问 HTTPS,还是套了其他未授权域名发起 HTTPS 请求,TLS 握手阶段拿到的都是不受信任的自签名证书,即使客户端强行忽略证书错误,进入 HTTP 阶段也会被 return 444; 立即切断,再也不会穿透到真实网站。


陷阱三:Canonical 标签写了相对路径,反向给镜像站背书

在修复 Nginx 配置后,我复盘了另一个核心疑问:就算镜像站能抓到页面,搜索引擎凭什么认定镜像站才是权威页面?

我查看了原站输出的 HTML 源码,立刻发现了一个严重的疏忽:

<link rel="canonical" href="/post/123.html" />

很多前端模板或者博客系统在输出规范标签(Canonical Tag)时,图省事写了相对路径 /post/123.html。

对浏览器来说相对路径能正常跳转,但对搜索引擎爬虫来说,相对路径意味着在当前请求的域名上下文下解析规范 URL。

当百度或 Google 爬虫通过 https://rogue-domain.top/post/123.html 访问时,它把相对路径解析出来的 Canonical 结果也是 https://rogue-domain.top/post/123.html。

这等于我们自己的网站在亲口告诉搜索引擎:“没错,这个页面的正版 URL 就是当前这个垃圾域名”。

更糟糕的是一些动态博客程序,后端代码这样写:

$current_domain = $_SERVER['HTTP_HOST'];
$canonical_url = 'https://' . $current_domain . $_SERVER['REQUEST_URI'];

当恶意域名发来请求时,后端从 HTTP_HOST 动态读取域名,把垃圾域名原封不动地拼进了 Canonical 标签和 Open Graph 分享标签中,甚至把生成的内链全部换成了对方的域名并写入了 Redis 页面缓存。

防御方案必须从根源纠正:

  1. Canonical 标签必须全量输出绝对路径:必须硬编码或从站点系统变量中读取唯一的主域名,如 https://www.llbbs.cn/post/123.html。
  2. 所有站内核心链接、图片地址、Sitemap 统一使用权威全路径,即便被透传镜像,爬虫抓到的所有规范指向也始终死死锁定在主站域名上。

陷阱四:图省事直接 301 重定向到主站,把毒流量引回家

发现被镜像绑定后,有些站长的第一想法往往是:“既然他帮我解析了域名,我为什么不直接在 Nginx 里把所有非我域名的请求 301 重定向到我的主站?这样岂不是白捡他的流量?”

千万不要这么做。

灰产和黑帽 SEO 经常用批量注册的废弃老域名、被降权甚至挂过马的受罚域名来撞 IP 做镜像。如果对方的域名原本就带着搜索降权的历史包袱或者违规垃圾外链,你把这个域名的所有请求 301 重定向到自己的主站,搜索引擎会把垃圾域名的惩罚信号和历史负面记录全量传递给你的主站。

另外,301 重定向依然是一个合法的 HTTP 响应,爬虫会顺着重定向链路继续顺藤摸瓜,徒增抓取配额的消耗。

还是那句话:未绑定的域名和 IP 直连,一律在连接建立的第一时间使用 return 444; 直接切断。不响应、不解释、不跳转,让爬虫认定该域名节点彻底死掉。


陷阱五:配置了反向代理,却用 proxy_set_header 漏传了非法 Host

如果你的 Nginx 后面挂了 Node.js、Python 或 Go 后端服务,反向代理时通常会有类似这段配置:

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
}

这里隐藏着一个变量细节:$http_host 与 $host 的区别。

  • $http_host 是客户端请求头里原汁原味的 Host 字段值,带有客户端提交的一切字符(包括可能的端口号、伪造格式)。
  • $host 则是 Nginx 按照优先级归一化后的值:优先取请求行中的主机名,其次取 Host 头部中的主机名(去掉端口号),如果都没有,则取匹配到的虚拟主机的 server_name。

如果主站虚拟主机里缺少严格的 Host 校验,仅依赖反向代理把原始请求扔给后端,攻击者一旦绕过第一道防线,后端框架(比如 Django 的 ALLOWED_HOSTS、Express 的虚拟路由)如果也没有校验 Host 白名单,就很容易触发 Host 头注入漏洞。

在主站配置块内,务必严格指定 server_name,不留通配符漏洞:

server {
    listen 80;
    listen 443 ssl http2;
    server_name www.llbbs.cn llbbs.cn;

    # 非主域名的访问即便混进来,也会在这层被拦截
    if ($host !~ ^(www\.llbbs\.cn|llbbs\.cn)$) {
        return 444;
    }

    # ...
}

生产环境完整配置与验证清单

把上面的踩坑点整合到一起,标准且干净的 Nginx 防恶意镜像配置模板如下:

# 1. 80 端口兜底:未绑定域名与 IP 直连直接断开
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

# 2. 443 端口兜底:使用自签名证书承接 TLS,拿到请求后直接断开
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    ssl_certificate /etc/nginx/ssl/dummy.crt;
    ssl_certificate_key /etc/nginx/ssl/dummy.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    return 444;
}

# 3. 正常主站配置
server {
    listen 80;
    listen [::]:80;
    server_name www.llbbs.cn llbbs.cn;
    return 301 https://www.llbbs.cn$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.llbbs.cn;

    ssl_certificate /etc/letsencrypt/live/www.llbbs.cn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.llbbs.cn/privkey.pem;

    # 严格校验 Host
    if ($host != 'www.llbbs.cn') {
        return 444;
    }

    root /data/www/html;
    index index.html index.php;

    # 正常业务 location 块
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

修改并重新加载 Nginx(nginx -t && systemctl reload nginx)后,通过以下三条命令完成排查闭环:

# 测试 1: 直接用 IP 请求 80 端口,应直接返回 Empty reply
curl -I http://YOUR_SERVER_IP

# 测试 2: 伪造恶意域名请求 80 端口,应直接返回 Empty reply
curl -I -H "Host: fake-mirror.xyz" http://YOUR_SERVER_IP

# 测试 3: 伪造恶意域名请求 443 端口,应在 TLS 或连接初段直接断开
curl -k -I -H "Host: fake-mirror.xyz" https://YOUR_SERVER_IP

当三条测试全部返回连接中断、没有流出任何 HTML 响应头时,防线才算真正焊死。持续观察两周的爬虫日志,恶意域名的抓取频次会逐步归零,搜索索引库也会在下一次快照更新时彻底剥离镜像页面,把属于原站的排名权重还回来。

评论一下?

OωO
取消