中小站长也能做的源站IP隐身术:CDN + 秘密Header 实战

源站IP暴露问题

将CDN(例如Cloudflare)作为安全防护手段,可能会给用户一种虚假的安全感。攻击者非常清楚,只要他们能够发现你源站服务器的IP地址,就能完全绕开CDN,直接攻击你的服务器。不幸的是,目前存在大量工具专门用来实现这一目的。攻击者通常会通过广泛的IP扫描(如利用SNI(Server Name Indication)以及Host header技巧)来确定哪些IP会响应你的网站域名。例如,他们可能借助Shodan或Censys等互联网扫描服务来搜寻互联网上带有你SSL证书的服务器,或者在TLS握手阶段,批量扫描主机IP段,观察哪些IP地址对你的域名有响应。一位白帽黑客曾表示:“通过找到源站IP绕过Cloudflare……可能是最简单的方法,根本不需要技术含量”——一旦发现IP,“你就不用再操心WAF或DDoS防护了” 。实际上,攻击者的侦察过程往往包括收集可能的IP地址(通过DNS记录、历史数据等途径),检查这些地址对Web请求是否有响应,再判断其中哪一个IP返回了受保护网站的内容。如果某个IP上未配置你的域名,攻击者看到的只是默认服务器页面;但如果域名确实被配置,那么攻击者就成功发现了你的真实源站。

即便是精心隐藏的基础设施也可能暴露。 熟练的攻击者思维类似扫描工具:他们会枚举子域名、查找泄露的DNS记录,甚至可能利用其他服务的漏洞来找到源站。例如,源站IP可能会通过电子邮件头信息或者某个开放端口泄露出去——一旦IP被发现,攻击者就可以直接对该IP进行攻击,或者使用流量将其彻底压垮,从而完全绕过CDN的防护机制。

常见方案:IP 白名单和隧道方案

针对源站IP暴露问题,防御手段主要包括:

上述方法固然是重要的初级防护措施,但在实际应用中仍可能存在漏洞

尽管IP白名单和隧道技术非常有效,但并非万无一失。即使Cloudflare自身的官方指南也指出,如果只单纯使用基本IP白名单,“可能会给攻击者提供绕过Cloudflare保护并发现你源站IP的机会”。因此,建议除了上述基础防护外,还应额外设置一层纵深防御机制,以应对攻击者可能绕过初级防护的情况。

引入基于HTTP header的伪装防护

但如果你的CDN提供商并不会告知你返回的真实CDN IP怎么办呢?例如阿里云的全站加速(ESA) 服务,只有升级到 高级版套餐 才能获得返回源站的具体CDN IP地址,而这一功能的费用高达 2880元。在这种情况下,我们还能怎样保护自己的源站服务器呢?

阿里云ESA的源站保护功能。
阿里云ESA的源站保护功能。

只有高级版套餐才提供返回源站的具体CDN IP。
只有高级版套餐才提供返回源站的具体CDN IP。

高级版套餐的费用为2880元。
高级版套餐的费用为2880元。

一种聪明的补充性防护策略是,使用一个秘密的自定义HTTP header 作为访问源站服务器的“守门员”。具体做法是:源站服务器只有在请求中携带特定的HTTP头并包含正确的秘密值时,才会返回真实的网页或应用内容;如果请求缺少这个头或者header值不正确,那么源站会假装自己并不是你想访问的网站——转而返回一个无害的诱饵页面(Decoy Response)。

这种策略有效的原因是,当攻击者扫描你的源站时,通常只会发送普通请求(他们并不知道你的CDN在内部使用了某个秘密header)。当你的服务器接收到没有正确header的请求时,可以返回一些无关紧要的内容,比如一个通用的“欢迎”页面,甚至是一个HTTP 404或444响应(不返回任何内容) 。对未经授权的请求而言,你的源站看起来只是一个普通、空置的默认服务器,没有托管任何实际应用。这种伪装可以有效迷惑攻击者,或者至少让自动扫描工具认为已经碰壁,无需继续探索。

为什么这很有用? 你可以把它理解为服务器的“双因素验证(2FA)”。即便攻击者发现了你的IP地址,他们仍需知道秘密的“密码”(即header中的token)才能获取任何真正有价值的信息。如果他们不知道这个秘密,就只能看到伪装页面。这大大降低了特定攻击的风险:

如何实现基于HTTP header的伪装防护

下面我们分别介绍两种具体的实现方法:一种是自定义的应用服务器(我们将以Rust的Axum框架为例),另一种是常见的Nginx服务器。在这两种场景下,我们都会要求每个请求中必须携带特定的HTTP头(例如:Only-This-Return: <secret-token>​)。CDN会为所有代理的流量自动插入带有正确密钥的header,而攻击者的直接请求通常不会包含这个header。

Rust示例(Axum中间件)

如果你的Web服务器使用Rust语言的Axum框架,我们可以通过简单的中间件(middleware)来检查请求是否带有秘密header。Axum基于Tower middleware,可以在请求抵达实际的业务处理函数前进行拦截。以下是一个简化且经过验证的实现示例:

static GUARD_HEADER: HeaderName = HeaderName::from_static("only-this-return");
const PSK: &str = "hkb9-42eFvNq"; 
const DECOY_PAGE: &str = include_str!("../public/static/nginx.html");

/// 中间件函数:拦截并验证请求头
async fn guard(mut req: Request<Body>, next: Next) -> Response {
    // 验证秘密header
    if req
        .headers()
        .get(&GUARD_HEADER)
        .and_then(|h| h.to_str().ok())
        .is_some_and(|v| v == PSK)
    {
        next.run(req).await
    } else {
        // 返回默认页面
        (StatusCode::OK, Html(DECOY_PAGE)).into_response()
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let site = Router::new()
        //...你的其他路由...
        .layer(from_fn(guard)); // 将中间件应用到所有路由上

    // 启动服务器的其他配置...
}

在以上示例代码中,guard​函数会检查请求中是否携带名为Only-This-Return​的秘密header,并验证该header的值。如果请求缺少这个header,或header值与预期的秘密不匹配,服务器会立即返回一个默认页面(这里我们用一个简单的HTML页面模拟默认的“欢迎”页面)。我们将这个中间件附加到所有路由(.layer(from_fn(guard))​),因此每一个请求都必须通过这个检查。只有在正确携带秘密header时,实际的业务处理函数才会运行。

CDN配置: 你需要在CDN中配置,让其在所有返回源站的请求中自动添加Only-This-Return: my-very-secret-token-123​header。例如在Cloudflare,你可以通过Transform Rule或Worker功能来插入这个header。如此一来,合法通过CDN的流量都会携带这个秘密header并通过检查,获得真实内容;任何直接访问源站且缺少这个header的请求,都会得到默认页面(务必确保所有流量都使用HTTPS,以防止此秘密header在传输过程中被窃听)。

Nginx 配置示例

如果你的源站使用的是 Nginx 服务器(或你使用 Nginx 作为应用程序前端的反向代理),你也可以通过简单的 Nginx 配置达到类似的伪装效果。我们可以通过 Nginx 的 map​ 模块结合 if​ 条件,控制对真实站点的访问(以下配置尚未经过实际验证)。

首先,在 http​ 块级别定义一个 map 来检查秘密header:

http {
    map $http_only_this_return $pass_allowed {
        default                          0;
        "my-very-secret-token-123"       1;
    }
    # ... 其他 http 配置 ...
}

上述配置定义了一个变量 $pass_allowed​,只有当请求头中名为 Only-This-Return​ 的值精确匹配预设的秘密令牌时,这个变量才为 1​,否则默认为 0​(务必将实际的秘密令牌替换掉示例中的 "my-very-secret-token-123"​)。

然后,在你的服务器(server)配置块中,利用这个 map 结果决定返回什么内容:

server {
    listen 443 ssl;
    server_name yoursite.com;
    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/cert.key;

    # 如果秘密header缺失或错误,则返回诱饵页面
    if ($pass_allowed = 0) {
        return 200 '<h1>Welcome to nginx!</h1><p>Nothing to see here.</p>';
        # 你也可以使用 return 444; 来直接断开连接,不做任何回应
    }

    # 只有header正确的请求才会继续执行以下代理配置
    location / {
        proxy_pass http://127.0.0.1:3000;  # 你的实际应用的上游地址
        # ... 其他代理设置 ...
    }
}

在上述配置中,任何未携带正确的 Only-This-Return​ header的请求都会触发 if​ 条件,立即返回一个 HTTP 200 状态码和一个简单的虚假欢迎页面(你也可以直接返回 return 444;​ 来中止连接而不做任何回应,这样会使服务器对未授权访问几乎完全隐身)。这里采用简单的欢迎页面返回示例,是为了看起来更加可信,让扫描工具误以为只是默认服务器页面。只有当变量 $pass_allowed​ 为 1​(即header正确)时,才会真正将请求代理到实际的应用程序。

注意: 确保 if​ 条件位于 location​ 块的代理配置前面。虽然 Nginx 的 if​ 指令有一些注意事项和特殊情况,但在此场景下,仅用于简单的header检查和返回响应是合理的。map​ 指令确保了字符串的比较操作在请求处理阶段安全有效地进行。

另外,你也需要考虑直接通过IP或其他主机名发起的请求。推荐在 Nginx 配置中专门定义一个默认服务器(即监听配置中设置 default_server​ 参数),针对所有未经明确授权的请求返回诱饵页面或直接丢弃连接。这样,即便攻击者直接访问你的源站IP地址,或使用其他无效的Hostheader发起请求,也无法得到任何有效信息。实际上,任何未通过秘密header明确认证的请求,都应该得到一个无意义的响应。

你可能也感兴趣