跳转到帖子

咖啡馆实战:网站加速链路中的真实客户端 IP、权限边界与日志修复

精选回复

发布于

背景

网站接入 CDN、WAF 或四层负载均衡后,源站看到的连接地址通常是代理节点,而不是访问者地址。很多站点会直接读取 X-Forwarded-For,并将它用于后台 IP 白名单、限速、审计甚至登录风控。只要代理信任链没有定义清楚,就可能出现伪造客户端 IP、绕过访问控制、日志无法溯源等问题。

本文以“客户端 → CDN/WAF → Nginx 源站 → 应用服务”为例,说明如何在授权测试中验证这条链路,并给出源站隔离、Nginx realip 配置和日志修复方法。

测试前应确认域名、源站地址和测试时间均在授权范围内。针对源站直连的验证应控制请求频率,避免绕过 CDN 后给后端造成额外压力。

问题定位

这类问题通常集中在三个位置:

  • 源站 80、443 端口直接暴露在公网,任何地址都能绕过 CDN 连接。
  • Nginx 无条件信任 X-Forwarded-For、X-Real-IP 等客户端可控请求头。
  • 应用自行拆分 X-Forwarded-For,并直接取最左侧地址作为真实客户端 IP。

先在源站确认实际监听情况、realip 模块和当前生效配置。不要只检查某个配置文件,nginx -T 才能看到 include 后的完整结果。

nginx -V 2>&1 | grep -o http_realip_module
nginx -T 2>&1 | grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|proxy_set_header|log_format'
ss -lntp | grep -E ':(80|443)[[:space:]]'
nft list ruleset

如果 Nginx 编译参数中没有 realip 模块,需要安装发行版提供的完整构建包或重新构建,不能用应用层随意读取请求头代替。

技术分析:代理信任链如何失效

X-Forwarded-For 本质上只是一个 HTTP 请求头。客户端可以主动发送如下内容:

X-Forwarded-For: 127.0.0.1
X-Real-IP: 10.0.0.8

安全性取决于最靠近源站、且受控制的代理如何处理这些字段。常见情况有三种:

  • 边缘代理覆盖请求头:源站收到的值由 CDN 重新生成,可信度最高。
  • 边缘代理在原值右侧追加真实地址:源站必须从右向左解析,并仅跳过明确受信任的代理地址。
  • 边缘代理原样透传:该字段不可用于鉴权或审计,应改用厂商提供且会被覆盖的专用客户端地址头。

Nginx 的 set_real_ip_from 用于声明哪些连接来源有资格提供真实地址,real_ip_recursive on 会从地址链右侧开始跳过可信代理,选出最后一个非可信地址。这里最危险的配置是 set_real_ip_from 0.0.0.0/0,它等价于允许任意公网客户端声明自己的来源地址。

授权验证步骤

第一步通过正常域名访问,在请求中加入一个测试地址,然后检查源站日志。预期结果是最终识别出的客户端地址仍为测试机的公网出口地址,而不是主动注入的 127.0.0.1。

curl -sk -o /dev/null -D - \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -H 'X-Real-IP: 10.0.0.8' \
  '

第二步验证源站是否可以绕过 CDN 直连。下面的 ORIGIN_IP 仅替换为授权范围内的源站地址:

curl -sk --connect-timeout 5 \
  --resolve app.example.com:443:ORIGIN_IP \
  -H 'X-Forwarded-For: 127.0.0.1' \
  '

修复后的预期结果应是连接超时、被防火墙拒绝,或者由源站额外认证机制返回拒绝响应。如果直连请求正常进入应用,就不能只依赖 Nginx 的请求头解析,还需要先收紧网络边界。

第三步检查依赖 IP 白名单的管理入口。若 /ops、/admin 等路径只通过 allow、deny 或应用内 IP 判断进行保护,应分别测试正常 CDN 路径、伪造请求头和源站直连。IP 白名单只能作为附加控制,不能代替账号认证、多因素认证或管理 VPN。

Nginx 可信代理配置

以下网段使用的是文档示例地址,部署时必须替换为 CDN 或负载均衡厂商发布的当前出口网段,并同时处理 IPv4、IPv6。不要从访问日志中临时收集地址后直接加入信任列表。

# /etc/nginx/conf.d/realip.conf
# 示例地址,禁止原样用于生产
set_real_ip_from 203.0.113.0/24;
set_real_ip_from 2001:db8:1234::/48;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

如果 CDN 提供专用客户端地址头,并明确保证会在边缘覆盖该字段,优先使用专用头:

set_real_ip_from 203.0.113.0/24;
set_real_ip_from 2001:db8:1234::/48;
real_ip_header CDN-Connecting-IP;

向应用转发时,建议在 Nginx 这一信任边界重新生成请求头,不要把外部传入的整条链原样交给应用。若后端只需要最终客户端地址,可以直接覆盖:

location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Request-ID $request_id;
    proxy_pass 
}

应用侧应读取反向代理传入的固定字段,并确保应用端口只允许 Nginx 或内部负载均衡访问。如果应用端口也暴露给普通客户端,请求头依然可以被绕过。

限制源站只能由加速节点访问

网络层限制优先于请求头判断。下面是 nftables 的结构示例,执行前应确认有控制台或带外管理通道,避免错误策略导致服务器失联:

table inet edge_filter {
    set cdn4 {
        type ipv4_addr
        flags interval
        elements = { 203.0.113.0/24 }
    }

    chain input {
        type filter hook input priority 0
        policy drop

        ct state established,related accept
        iifname lo accept

        # 管理网段示例
        ip saddr 10.20.0.0/16 tcp dport 22 accept

        # 仅允许 CDN 到达 Web 端口
        ip saddr @cdn4 tcp dport { 80, 443 } accept
    }
}

IPv6 不能遗漏。如果域名存在 AAAA 记录,而防火墙只限制 IPv4,攻击者仍可能通过 IPv6 直连源站。云服务器还应同步检查安全组、负载均衡监听器和容器端口映射。

日志留痕与排查

排查时至少要同时记录 TCP 对端地址、realip 解析后的客户端地址和原始转发头。Nginx 中 $realip_remote_addr 表示被替换前的连接对端,通常应是 CDN 节点;$remote_addr 是解析后的客户端地址。

log_format edge_json escape=json
  '{"time":"$time_iso8601",'
  '"request_id":"$request_id",'
  '"host":"$host",'
  '"peer":"$realip_remote_addr",'
  '"client":"$remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"method":"$request_method",'
  '"uri":"$uri",'
  '"status":$status,'
  '"upstream":"$upstream_addr",'
  '"upstream_status":"$upstream_status"}';

access_log /var/log/nginx/edge_access.json edge_json;

配置修改后先执行语法检查,再平滑加载:

nginx -t && nginx -s reload

tail -F /var/log/nginx/edge_access.json | \
  jq -c 'select(.host == "app.example.com") | {peer,client,xff,status,request_id}'

正常情况下,peer 应落在可信 CDN 网段内,client 应与测试出口一致。若 client 等于主动注入的 127.0.0.1,说明边缘没有覆盖请求头,或源站错误信任了连接来源。日志中不要记录 Authorization、Cookie、会话令牌和完整查询参数,避免修复溯源问题时引入新的凭据泄露风险。

权限边界与修复优先级

  • 第一优先级:在安全组或主机防火墙限制源站,仅允许 CDN、WAF 或内部负载均衡访问。
  • 第二优先级:准确维护 set_real_ip_from,禁止使用 0.0.0.0/0、::/0 或过宽的内部网段。
  • 第三优先级:确认边缘节点会覆盖或安全追加客户端地址头;无法确认时使用厂商专用头。
  • 第四优先级:应用不自行解析不受控的 X-Forwarded-For,后端端口只对反向代理开放。
  • 第五优先级:后台入口使用独立认证、MFA、管理 VPN 或 mTLS,IP 白名单只作为附加条件。
  • 第六优先级:保留 peer、client、xff 和 request_id,并将日志集中存储,防止源站被入侵后本地日志一并删除。

总结

网站加速链路中的真实 IP 不是简单读取一个请求头,而是代理身份、网络入口和解析规则共同构成的信任链。有效修复需要同时完成三件事:源站不可被普通客户端直连、Nginx 只信任明确的代理网段、应用只接收由受控反向代理重新生成的地址字段。验证完成后,再通过结构化日志确认连接对端与解析结果一致,才能兼顾权限边界、风控准确性和事件溯源。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意
服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意

这类链路最容易出问题的点,不是“有没有读取 X-Forwarded-For”,而是谁有权限写入,以及哪一层负责清洗。建议把加速节点设为唯一信任边界:源站只接受加速节点回源,并且只信任这些节点写入的客户端 IP 头。否则攻击者直连源站时,可以自行构造请求头绕过 IP 限制或污染审计日志。

Nginx 源站可以按下面的思路配置,信任网段应从加速服务商的官方接口定期同步,不要直接信任全部地址:

http {
    # 仅填写实际反向代理/CDN 的回源网段
    set_real_ip_from 192.0.2.0/24;
    set_real_ip_from 2001:db8:100::/48;

    # 按服务商实际使用的头调整,也可能是 CF-Connecting-IP 等
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    log_format upstream_audit
        '$time_iso8601 '
        'peer=$realip_remote_addr '
        'client=$remote_addr '
        'xff="$http_x_forwarded_for" '
        'host="$host" request="$request" '
        'status=$status bytes=$body_bytes_sent '
        'request_id="$request_id"';

    access_log /var/log/nginx/access.log upstream_audit;
}

这里建议同时记录 $realip_remote_addr 和 $remote_addr:前者保留实际连接到源站的代理地址,后者是经过 realip 模块解析后的客户端地址。出现误封、伪造或多层代理解析异常时,两者可以互相校验。不要只覆盖成一个“真实 IP”,否则后续很难还原链路。

权限边界还应落实到网络层。例如只允许 CDN 网段访问源站 80/443,管理入口单独走 VPN 或堡垒机:

# 示例,执行前按实际网段和现有防火墙规则调整
iptables -A INPUT -p tcp -s 192.0.2.0/24 --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j DROP

应用层也不要再次自行取 X-Forwarded-For 的第一个值。应用应优先使用 Web 服务器已经校验并重写后的远端地址;如果框架必须配置代理层数或可信代理列表,应显式填写网段,避免使用“信任任意代理”的配置。

修复后可以做三组验证:

# 1. 直连源站并伪造头,确认被网络层拒绝
curl -vk --resolve example.com:443:ORIGIN_IP \
  -H 'X-Forwarded-For: 1.1.1.1' 

# 2. 经正常域名访问并携带伪造头,确认加速节点会覆盖或规范化
curl -vk -H 'X-Forwarded-For: 1.1.1.1' 

# 3. 检查日志中的代理地址、解析后地址和原始头是否符合预期
tail -f /var/log/nginx/access.log

如果链路是“客户端 → CDN → WAF/负载均衡 → Nginx”,还要逐跳确认每一层到底是覆盖、追加还是透传请求头。比较稳妥的原则是:边界节点先删除外部传入的同名头,再写入可信值;后续节点只接受来自上一跳可信网段的数据。限流、后台白名单和风控规则也应统一使用经过该信任链解析后的地址,避免日志修好了,鉴权逻辑仍在读取可伪造头。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。