XFF 可信代理模型:反代/CDN 场景下 IP 不失真
X-Forwarded-For(简称 XFF,代理链路上逐跳追加的客户端 IP 列表)本身不可信,真正可信的是「你自己能控制的那一跳」;只要按可信代理白名单从右往左取第一个非可信 IP,反代和 CDN 场景下的 IP 就不会失真。
结论先行:XFF 是「追加」而不是「覆盖」
结论:XFF 的安全模型建立在「每一跳只追加、不删除」这个约定上,而客户端可以随便伪造初始值,所以最左边的地址永远最不可信。
一个典型的请求头可能是 `XFF: 1.2.3.4, 10.0.0.5, 172.16.0.9`,其中 `1.2.3.4` 是用户自己塞进去的假地址,后面两个才是真实链路。如果你直接取第一个值,等于把 IP 判定权交给了攻击者——IP 封禁、登录失败拦截、审计日志全部失效。
所以正确姿势只有一条:从右往左扫,跳过所有可信代理,取第一个不可信地址。
可信代理模型的算法长什么样
结论:标准算法是「从右向左找第一个不在白名单里的地址」,找不到就回退到 TCP 层的 `REMOTE_ADDR`。
伪代码:
ip = REMOTE_ADDR
if REMOTE_ADDR 不在可信代理集合: return REMOTE_ADDR
parts = XFF.split(",").map(trim)
for i = parts.length-1 down to 0:
if parts[i] 不在可信代理集合: return parts[i]
return REMOTE_ADDR // 全链可信,兜底
两个细节容易翻车:一是 XFF 里可能混入 `ip:port` 甚至 IPv6 带方括号,取到后要剥壳再校验;二是私有地址(10./172.16./192.168.)在做「同一 IP 高频失败拦截」时基本无意义,因为它们本身就是内网代理地址,需要区分对待。
Nginx 层最省事的做法
结论:在 Nginx 用 `real_ip` 模块改写 `REMOTE_ADDR`,后端代码一行都不用改。
set_real_ip_from 10.0.0.0/8; # 自建反代
set_real_ip_from 173.245.48.0/20; # CDN 回源段,按官方列表更新
real_ip_header X-Forwarded-For;
real_ip_recursive on; # 开启后会做「从右往左」扫描
`real_ip_recursive on` 就是上面那套算法的实现,搭配 `set_real_ip_from` 白名单才有意义;只开 `recursive` 不配白名单,等于信任所有来源。改完之后 PHP 里 `$_SERVER['REMOTE_ADDR']` 拿到的就是真实客户端 IP,`X-Real-IP`、`XFF` 保持原样供排查用。
反过来,如果后端应用自己解析 XFF,就必须保证 Nginx 传下去的 XFF 不被外部污染——建议在入口层直接用 `proxy_set_header X-Forwarded-For $remote_addr;` 覆盖,而不是 `$proxy_add_x_forwarded_for` 追加。
CDN 场景:优先用厂商专用头
结论:CDN 场景优先读 `CF-Connecting-IP`、`True-Client-IP`、`Ali-CDN-Real-IP` 这类专用头,比解析 XFF 更可靠。
这些头由 CDN 边缘节点直接写入,不参与追加链条,伪造难度高得多。对应 Nginx 配置:`real_ip_header CF-Connecting-IP;`。同时要正视一个前提——源站必须只允许 CDN 回源段访问(防火墙白名单、安全组,或启用 CDN 的回源鉴权)。否则攻击者直接解析源站 IP 打过来,伪造什么头都拦不住,因为请求根本不经过 CDN。
另外 CDN 的 IP 段会变,回源白名单建议做成计划任务定期同步,别写死一次管三年。
别忘了兜底校验
结论:可信代理模型解决的是「取哪个值」,取到之后还得校验格式与合理性。
- 用 `filter_var($ip, FILTER_VALIDATE_IP)` 卡掉非法串;
- 对 XFF 长度设上限(比如 20 段),超长直接截断或降级为 `REMOTE_ADDR`,防止超长头部打日志;
- 内网封禁规则里排除私有网段,避免把自家反代封了;
- 日志同时记录 `REMOTE_ADDR` 和原始 XFF,出现争议时可回溯。
这套东西对依赖 IP 的功能是刚需:IP 封禁、同 IP 登录失败拦截、登录审计日志,判错一个字段,防密码喷洒就等于没做。
收束
可信代理模型的核心只有三句话:白名单里只写你自己控制的代理、从右往左取第一个非可信 IP、取完再校验格式和私有网段。Nginx 的 `set_real_ip_from` + `real_ip_recursive` 是最省事的落地方案,CDN 场景换成厂商专用头并锁死源站回源白名单。头是不可信的,拓扑才是可信的。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





