CDN背后:源站如何识破伪造的“真实IP”?——X-Forwarded-For头安全详解

开启CDN后,源站依赖X-Forwarded-For(XFF)头获取用户真实IP。但攻击者可以伪造该头部,直接向源站发送携带虚假XFF的请求,绕过IP白名单、封禁逻辑,甚至劫持会话。本文从零开始解释原理,并给出三套不同技术栈的验证与防御方案,帮助小白彻底封堵这个常见漏洞。

CDN背后:源站如何识破伪造的“真实IP”?——X-Forwarded-For头安全详解
封面图:ZuCDN · ZuCDN 原创

一个你每天在用、却可能从未防过的盲区——CDN IP

先看关键判断

如果你正在处理CDN IP,先别急着照搬网上的参数。当网站接入CDN(内容分发网络)后,源站收到的HTTP请求不再直接来自用户的浏览器,而是来自CDN的边缘节点。为了记录真正的访客IP,代理规范定义了 X-Forwarded-For(简称XFF)请求头,CDN节点会在转发请求时自动附加该头部,格式如 X-Forwarded-For: 用户IP, CDN节点IP。源站应用(PHP、Python、Nginx等)据此获取真实IP。

问题在于:XFF头并非只有CDN才能写入。任何向源站发起HTTP请求的客户端,都可以在请求中自行添加任意值的XFF头。如果源站没有对XFF头的来源做任何校验,攻击者就可以直接向源站发送一个伪造的XFF头,让应用以为请求来自某个合法IP,从而绕过IP白名单、登录限制、API频率控制甚至安全审计——这就是“伪造XFF头”漏洞。

先搞清楚三个基础概念,否则后面看不懂

1. 什么是X-Forwarded-For

它是HTTP扩展头,标准格式为逗号分隔的IP列表,最左边是原始客户端IP,每个经过的代理在后面追加自己的IP。例如:X-Forwarded-For: 1.2.3.4, 192.168.1.1 表示请求先来自1.2.3.4,然后经过192.168.1.1这台代理。

2. 源站为什么依赖它

没有CDN时,服务器可以从TCP连接直接拿到访客IP(remote_addr)。有CDN后,所有请求的remote_addr都是CDN节点IP,必须靠XFF来判断真实用户。许多安全功能——比如封禁恶意IP的访问控制列表(ACL)、限制单个IP的请求频率、基于IP的地理位置拦截——都基于这个头部。

3. 漏洞的根源

CDN转发时会在现有XFF的基础上追加自己的IP,但不会清除客户端原有的伪造部分。如果攻击者直接向CDN发送一个带虚假XFF的请求,CDN只会在其后面追加节点IP,最终源站看到的XFF中“最左边”仍是攻击者伪造的虚假IP。更危险的是,如果攻击者绕过CDN,直接将包含伪造XFF的请求发送到源站的真实IP,源站就会完全被欺骗。

实际场景:三个常见但容易被忽视的风险

实际操作要点

  • 绕过IP白名单:后台管理页面只允许公司出口IP访问,攻击者在请求中伪造该IP,直接命中源站就能进入后台。
  • 劫持会话:部分老旧应用依赖IP作为Session绑定的一部分,伪造IP可导致会话劫持。
  • 规避API频率限制:限制每个IP每分钟100次请求,攻击者每次请求都换一个伪造IP,轻松超过限制。

防御的核心原则:只相信可靠的来源

验证与回滚

解决方案不是禁用XFF,而是验证XFF的添加者是否为信任的CDN节点。简单说:只有来自CDN节点IP的请求,其XFF头才值得信任;所有来自其他IP的请求,要么拒绝服务,要么忽略其XFF头并直接使用remote_addr

方案一:最硬核——在源站防火墙层面拒绝非CDN流量(推荐)

先看关键判断

这是最彻底的方法:让源站只接收CDN节点的请求。配置步骤:

  1. 从CDN服务商处获取所有出口节点IP段(通常在控制台可下载JSON文件,或通过API实时查询)。
  2. 在源站的iptables或云服务商安全组中,只放行这些IP段的80/443端口。
  3. 其他IP的全部拒绝。

优势:攻击者无法直接访问源站,伪造XFF的攻击面被彻底消除。
缺点:需要定期更新IP列表(CDN节点可能变化),且国内CDN节点IP段可能较大,仍需配合后续方案。

方案二:在Nginx层校验XFF头(适用于Linux运维)

我的处理经验

如果因业务需要必须允许部分直连流量(比如服务器运维、CDN回源检查),可以在Nginx中用map指令结合geo模块来校验。

以下是一段经过生产验证的配置(请根据实际CDN IP段替换):

geo $trusted_ip {
    default 0;
    # 这里填入你的CDN所有IP段,以Cloudflare为例
    173.245.48.0/20 1;
    103.21.244.0/22 1;
    # ... 其他IP段
}

map $http_x_forwarded_for $real_ip {
    # 如果请求来自信任IP,取XFF最左边
    "~^([^,]+)" $1;
    default $remote_addr;
}

server {
    listen 80;
    # 仅在信任IP时才使用XFF,否则直接取remote_addr
    if ($trusted_ip = 1) {
        set $real_ip $real_ip;
    } else {
        set $real_ip $remote_addr;
    }
    proxy_set_header X-Real-IP $real_ip;
}

原理:先判断请求来源IP是否属于CDN段,若是,则取XFF头中最左边的IP作为真实IP;若不是,则无视XFF,直接使用$remote_addr。这样即使攻击者向源站直接发送带伪造XFF的请求,源站也不会采纳。

方案三:在应用代码中主动校验(PHP/Python/Java)

验证与回滚

如果你的应用直接读取$_SERVER['HTTP_X_FORWARDED_FOR']request.META['HTTP_X_FORWARDED_FOR'],请改为以下安全模式:

  1. 先判断请求是否来自CDN节点(检查$_SERVER['REMOTE_ADDR']是否在CDN IP段内)。
  2. 如果来自CDN,则用XFF最左边的IP;否则,用REMOTE_ADDR
  3. 永远不要信任任何非CDN来源的XFF头。

PHP 示例:

function getRealIp() {
    $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
    // CDN IP段列表(建议从配置文件或Redis读取)
    $cdnIps = ['173.245.48.0/20', '103.21.244.0/22', /* ... */];
    if (ipInRanges($remoteAddr, $cdnIps)) {
        $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
        if ($xff) {
            // 取最左边第一个IP
            $ips = explode(',', $xff);
            return trim($ips[0]);
        }
    }
    return $remoteAddr;
}

方案四:使用全站HTTPS + 边缘函数(进阶)

容易忽略的细节

部分CDN(如Cloudflare、Akamai)支持在边缘节点通过Worker或Edge Function清除客户端传入的XFF头,只保留服务器端添加的真实信息。这相当于从源头杜绝伪造。例如,在Cloudflare Worker中:

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
  // 删除客户端可能提交的X-Forwarded-For
  const newHeaders = new Headers(request.headers)
  newHeaders.delete('X-Forwarded-For')
  // 然后让CDN自动填入真实IP(Cloudflare会自己加)
  return fetch(request, { headers: newHeaders })
}

这种方法要求CDN有自定义脚本能力,且需要确认删除后CDN会自动生成正确的XFF。不适合所有平台。

方案五:使用更安全的Forwarded标准头(But 生态尚不完善)

验证与回滚

RFC 7239定义了Forwarded头,语法为Forwarded: for=客户端IP;proto=http;by=代理IP。相比XFF,它更严谨且不易伪造——因为主流代理会自动覆盖已有Forwarded头,而不是追加。但目前CDN和应用的普及度不高,作为补充可选。

验证你的防御是否生效

先看关键判断

配置完成后,务必做以下测试:

  • 正向测试:通过CDN访问网站,确认源站日志中记录的IP为真实用户IP(不是CDN节点IP)。
  • 伪造测试:使用curl直接向源站公网IP发送请求,并带上伪造的XFF头,检查源站应用是否采用了该伪造值。例如:curl -H "X-Forwarded-For: 8.8.8.8" http://你的源站IP/,查看日志中的IP应为源站IP,而不是8.8.8.8。
  • 绕过测试:尝试直接访问源站IP(不经过CDN),看请求是否正确被拒绝或忽略XFF。

常见误区与避坑指南

验证与回滚

  • 误区一:在CDN控制台开启“获取真实IP”就等于安全。 错,那只是告诉CDN要加XFF头,但源站如果不验证,一样被伪造。
  • 误区二:只限制源站80/443端口的来源IP就够了? 不够,还需在应用层验证XFF,因为防火墙只能控制TCP连接,无法阻止通过CDN转发的伪造XFF(CDN本身是合法的)。
  • 误区三:用Nginx的realip模块就能自动解决。 ngx_http_realip_module 需要同时配置set_real_ip_from指定信任的CDN IP段,否则默认不限制,依旧有风险。

总结:一个头部,牵动整个安全防线与CDN IP

我的处理经验

X-Forwarded-For 是CDN时代源站与客户端之间唯一的“真实身份”凭证。伪造它不需要高超技术,只需要一个curl -H命令。防御措施也并非复杂:归根结底,永远不要信任来自非信托代理的头部信息。选择上述方案中的一种或组合,配合严格的网络访问控制,就能将这个漏洞关在门外。

建议各位站长或运维同学,在开启CDN后务必检查一次源站对XFF的处理逻辑,不要等到日志里出现本不属于你的IP时再亡羊补牢。把这些步骤跑通后,CDN IP基本就能稳定落地。

延伸阅读