[2026 Remastered · 09] 为什么禁止 IP 访问不能只写 RewriteRule /404.html?从 Apache 重定向死循环与证书泄密到 TLS 握手层零泄露阻断
🛸 站长重制导读(Anti Gravi's Remaster Note)
本文是 「来时路 · 2026 重制企划(9/12)」 的第 9 篇,对应本站 2020 年 11 月 9 日发布的 《禁止使用ip访问页面的方法》(
CID: 44)。2020 年的原稿记录了一个非常高频的企业合规与安全需求:“网页只能通过域名
www.example.com访问,禁止通过服务器 IP1.2.4.8直接访问”。原稿选择在 Apache (httpd.conf) 中定义两组<VirtualHost>,利用匹配顺序将ServerName 1.2.4.8放在前面,并写了RewriteRule ^.* /404.html。把“拦截 IP 的默认虚拟主机”放在配置文件最前面(利用 Apache/Nginx 首个虚拟主机作为 Default VHost 的特性)这个大方向是对的!然而,原稿里的具体配置代码却埋着 4 个只要一上生产就会暴露的协议与逻辑漏洞:从
RewriteRule ^.* /404.html触发内部无限重写死循环(500错误)或返回200 OK软 404,到 HTTPS 下给 IP 虚拟主机挂载真实证书导致未进 Rewrite 先弹浏览器红叉、甚至向全网黑客扫描器主动出卖源站真实域名!
一、 2020 原稿 (CID: 44) 四大配置硬伤逐行筛查
我们先回顾 2020 年原稿第 35~63 行的 Apache httpd.conf 配置代码:
# 2020 原稿 (CID: 44) 第 35~51 行原始配置:
<VirtualHost 1.2.4.8:443> # 设置虚拟主机
ServerName 1.2.4.8 # 设置服务名为1.2.4.8
DocumentRoot /home/example
SSLEngine on # 启用ssl,用于https访问
SSLCertificateFile /home/example/server.crt
SSLCertificateKeyFile /home/example/server.key
RewriteEngine On # 启用重定向
RewriteRule ^.* /404.html # 将请求重定向到页面根目录下的404.html
</virtualhost>
<VirtualHost 1.2.4.8:80>
ServerName 1.2.4.8
DocumentRoot /home/example
RewriteEngine On
RewriteRule ^.* /404.html
</virtualhost>
为什么这段看似直观的配置在真实环境里会连环翻车?
| 编号 | 2020 原稿 (CID: 44) 写法 |
真实运行后果与底层协议原理 | 2026 Remastered 勘误与根治方案 |
|---|---|---|---|
1. RewriteRule ^.* /404.html 内部死循环或返回 200 OK |
裸写 RewriteRule ^.* /404.html(未加 RewriteCond 排除自身,也未加 [R=404,L]) |
1) 当没有终止条件时,/404.html 自身依然匹配正则 ^.*,在目录级或次级内部请求中会触发 AH00124: Request exceeded the limit of 10 internal redirects 直接报 500 错误;2) 即便在 VHost 顶层未循环,不带 R= 标志的内部重写返回给客户端的 HTTP 状态码竟然是 HTTP/1.1 200 OK(即拿着 200 成功状态码返回一段写着 404 的 HTML,俗称“软 404”),完全违反 HTTP 语义! |
根本无需开启消耗性能的 RewriteEngine!在 Apache 中直接使用 RedirectMatch 404 ^/ 配合 ErrorDocument 404 /404.html(或直接 Require all denied 返回 403) |
2. HTTPS 下先撞 TLS 证书警告,根本走不到 RewriteRule! |
在 <VirtualHost 1.2.4.8:443> 中挂载 www.example.com 的证书 server.crt,试图用 RewriteRule 展示 404.html |
网络分层先后顺序错误! 客户端访问 https://1.2.4.8 时,必须先完成第 6 层 TLS 握手,才能发送第 7 层 HTTP 请求去触发 RewriteRule。因为正规域名证书的 SAN 里没有 1.2.4.8 这个 IP,浏览器在 TLS 握手阶段就会当场拦截并弹出刺眼的红色警告 NET::ERR_CERT_COMMON_NAME_INVALID,用户根本看不到你的 404.html! |
对未携带合法域名 SNI 的 :443 IP 直接访问,应当在 TLS 握手阶段直接掐断连接(Apache SSLStrictSNIVHostCheck on / Nginx ssl_reject_handshake on) |
| 3. 向全网扫描器主动出卖源站证书(CDN 源站暴露元凶!) | 在 IP 默认 :443 虚拟主机上配置真实的业务证书 server.crt |
这是安全防护中最致命的忌讳!Censys、Shodan 与黑客脚本每天都在对全网 IPv4 的 443 端口发起无 SNI 握手探测。如果你在 IP 虚拟主机上挂了 www.example.com 的真证书,扫描器一握手就能拿到证书里的 Subject / SAN 域名——哪怕你前面套了 Cloudflare 或高防 CDN,源站真实 IP 也会在 5 分钟内被全网扒光并遭受直接 DDoS 旁路攻击! |
默认 IP 虚拟主机严禁挂载任何包含真实业务域名的证书!必须在 TLS 握手层直接拒绝无 SNI / IP 访问 |
4. 404.html 引用 fonts.googleapis.com 导致国内白屏 |
原稿第 73 行 HTML 头部写了 <link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet'> |
在国内网络环境下,浏览器为了渲染这行只有 12 个字的错误提示,会傻傻等待 fonts.googleapis.com 连接超时(长达 15~30 秒白屏转圈)! |
错误页必须 100% 零外部依赖(内联系统字体栈) |
二、 为什么“禁止 IP 直接访问”在 2026 年的核心目标是「防源站 IP 泄露与防恶意域名恶意解析」?
很多初学运维的同学以为“禁止 IP 访问”只是为了满足甲方的表面需求。实际上,在现代生产架构中,这项配置承担着两项核心安全使命:
+-------------------------------------------------------------------------+
| 攻击场景 1:黑客扫描器全网遍历 IP:443 寻找你的 CDN 背后真实源站 IP |
+-------------------------------------------------------------------------+
| 扫描器发送: ClientHello (无 SNI 或 SNI=1.2.4.8) |
| ├── ❌ 2020 原稿配法: 服务器乖乖递上 www.example.com 的 server.crt 证书 |
| │ ➔ 扫描器瞬间记录: "抓到了!1.2.4.8 就是 www.example.com 的源站!" |
| └── ✅ 2026 标准配法: TLS 握手层直接回 Alert (unrecognized_name) 掐断 |
| ➔ 扫描器什么证书都拿不到,源站完美隐身! |
+-------------------------------------------------------------------------+
- 防止他人将未备案或违规域名恶意 A 记录解析到你的公网 IP:
- 如果你的 Web 服务器默认虚拟主机没有拦截未知
Host头,别人把一个赌博或未备案域名解析到你的1.2.4.8,云厂商信安系统一扫发现能打开你的网站内容,就会直接封停你的公网 IP!
- 如果你的 Web 服务器默认虚拟主机没有拦截未知
- 隐藏源站身份:
- 不论是
http://1.2.4.8还是拿着别的野域名访问,甚至直接对1.2.4.8:443发起 TLS 握手,服务器都必须做到**“非我白名单域名,连一个字节的业务信息和证书都不吐”**。
- 不论是
三、 2026 生产级双栈标准配置(Apache 2.4 & Nginx 1.26)
方案 A:Apache 2.4 标准配置(修正 2020 原稿的 httpd.conf)
在 Apache 2.4 中,我们通过第一个默认 <VirtualHost> 兜底所有 IP 访问与未知域名访问,用 RedirectMatch 404 替代错误的 RewriteRule,并通过 SSLStrictSNIVHostCheck on 拒绝任何不带合法域名 SNI 的 HTTPS 握手:
# ==============================================================================
# 1. 第一个 VirtualHost(默认兜底黑洞:拦截所有 IP 直接访问与未知域名恶意解析)
# ==============================================================================
<VirtualHost *:80>
ServerName default-blackhole.invalid
DocumentRoot /var/www/empty
# 无需开启 RewriteEngine,直接返回真实的 HTTP 404 状态码并展示零外部依赖的 404.html
ErrorDocument 404 /404.html
RedirectMatch 404 ^/(?!404\.html$)
</VirtualHost>
<VirtualHost *:443>
ServerName default-blackhole.invalid
DocumentRoot /var/www/empty
SSLEngine on
# ⚠️ 关键安全铁律:此处仅放一张 CN=invalid.local 的无意义自签名假证书占位,
# 绝对禁止挂载 www.example.com 的真实业务证书!
SSLCertificateFile /etc/apache2/ssl/dummy-selfsigned.crt
SSLCertificateKeyFile /etc/apache2/ssl/dummy-selfsigned.key
# 强制严格校验 SNI:若客户端用 IP 访问 https://1.2.4.8,直接返回 403 拒绝!
SSLStrictSNIVHostCheck on
ErrorDocument 404 /404.html
RedirectMatch 404 ^/(?!404\.html$)
</VirtualHost>
# ==============================================================================
# 2. 第二个 VirtualHost(真正的业务域名虚拟主机:仅匹配 www.example.com)
# ==============================================================================
<VirtualHost *:80>
ServerName www.example.com
# 将 HTTP 域名访问 301 永久重定向至 HTTPS
Redirect permanent / https://www.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName www.example.com
DocumentRoot /home/example
SSLEngine on
SSLCertificateFile /home/example/server.crt
SSLCertificateKeyFile /home/example/server.key
</VirtualHost>
方案 B:Nginx 1.26+ 最简零泄露标准配置(推荐)
自 Nginx 1.19.4+ 起,官方提供了极其优雅的 ssl_reject_handshake on; 指令——连假的自签名占位证书都不需要生成,直接在 TLS 握手第一个包就将 IP 访问踢下线;而在 HTTP 层则使用 Nginx 专属的 return 444;(直接发送 TCP FIN/RST 关闭套接字,连 HTTP 响应头都不回,零带宽消耗):
# 1. 默认兜底虚拟主机(default_server):拦截一切 IP 直接访问与未知 Host 头
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
# HTTPS 层:遇到 IP 访问或未知 SNI 域名,在 TLS 握手阶段直接拒绝(零证书信息泄露!)
ssl_reject_handshake on;
# HTTP 层:直接掐断底层 TCP 连接(或者按需改为 return 404;)
return 444;
}
# 2. 正常业务域名虚拟主机
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/www.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/www.example.com/privkey.pem;
root /home/example;
index index.html;
}
附:移除 fonts.googleapis.com 外部阻塞后的极速内联 404.html
如果业务方要求用 IP 访问 HTTP 80 端口时必须看到与原稿一致的友好 404 页面,只需删掉原稿第 73 行那句拖慢国内访问的 Google Fonts 外链,改用现代系统原生无衬线字体栈即可毫秒级渲染:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
<style>
html, body {
margin: 0; padding: 0; width: 100%; height: 100%;
color: #B0BEC5; display: table; font-weight: 300;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
}
.container { text-align: center; display: table-cell; vertical-align: middle; }
.content { text-align: center; display: inline-block; }
.title { font-size: 42px; margin-bottom: 40px; }
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">404 很抱歉,您查看的页面找不到了!</div>
</div>
</div>
</body>
</html>