跳转到帖子

渗透攻防实战:反向代理客户端 IP 信任链绕过的验证、留痕与修复

精选回复

发布于

背景

在授权渗透测试中,经常遇到通过客户端 IP 限制管理接口的场景,例如仅允许办公网或 VPN 网段访问 /admin、/metrics、/actuator 等路径。实际链路通常不是客户端直接访问应用,而是经过 CDN、WAF、Nginx 或负载均衡器:

客户端 -> CDN/WAF -> Nginx -> 应用服务

如果代理层没有覆盖外部传入的 X-Forwarded-For、X-Real-IP,或者源站错误地信任任意地址提交的转发头,攻击者就可能伪造来源 IP。此类问题不只是访问控制绕过,还会影响限速、审计、封禁和告警。

以下测试应限定在书面授权资产内,优先选择只读健康检查接口,不执行配置修改、账号操作或数据导出。

问题定位

先明确真实流量路径和每一跳的职责,重点确认以下问题:

  • 公网入口是否允许客户端直接提交 X-Forwarded-For 或 X-Real-IP。
  • 源站是否配置了 set_real_ip_from 0.0.0.0/0,或信任范围明显大于实际代理网段。
  • 应用使用的是连接源地址,还是框架解析后的 req.ip、RemoteAddr。
  • CDN 后面的源站是否仍能被公网直接访问。
  • 管理接口是否只依赖 IP 白名单,没有额外身份认证。

有服务器权限时,可先检查 Nginx 是否启用了 realip 模块,并展开全部配置。nginx -T 的结果可能包含内部域名和证书路径,取证文件应按敏感配置管理。

nginx -V 2>&1 | grep -o -- '--with-http_realip_module'
nginx -T 2>&1 | grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|allow |deny |proxy_set_header|location .*(admin|metrics|actuator)'

# 同时检查是否存在重复配置或 include 文件覆盖
nginx -T 2>&1 | grep -nE '^# configuration file|set_real_ip_from|real_ip_header'

以下配置是常见风险点:

set_real_ip_from 0.0.0.0/0;
real_ip_header X-Real-IP;

location /admin/ {
    allow 127.0.0.1;
    allow 10.30.0.0/16;
    deny all;
    proxy_pass 
}

realip 模块可能先根据请求头重写 remote_addr,随后 allow/deny 再基于重写后的地址判断。此时来自公网的请求只要能控制 X-Real-IP,就可能被识别为本机或可信网段。

低影响漏洞验证

建议选择 /admin/health、/metrics 的空查询或其他只读路径,先记录基线响应,再分别测试常见代理头。下面使用文档保留地址 192.0.2.10 作为示例;-k 仅适用于测试环境的自签名证书,正式验证应校验证书。

HOST=admin.lab.example
IP=192.0.2.10
PATHNAME=/admin/health

# 基线请求
curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \
  -w 'baseline status=%{http_code} size=%{size_download}\n' \
  "

# 测试 X-Real-IP 是否被直接信任
curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \
  -H 'X-Real-IP: 127.0.0.1' \
  -w 'x-real-ip status=%{http_code} size=%{size_download}\n' \
  "

# 测试 X-Forwarded-For 信任链
curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -w 'xff status=%{http_code} size=%{size_download}\n' \
  " 403,而加入某个头后变成 200,且响应内容确实来自受限接口,可初步确认信任边界存在问题。不要只看状态码:部分 WAF、登录页和后端错误页也可能返回 200,应同时比较响应头、正文长度和稳定特征。

如果单地址未出现差异,还需要结合实际代理实现检查多跳解析规则。不同组件可能取最左侧、最右侧或最后一个非可信地址,不能仅凭一组请求判定安全。

for value in \
  '127.0.0.1, 198.51.100.25' \
  '198.51.100.25, 127.0.0.1' \
  '10.30.1.20, 198.51.100.25'
do
  curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \
    -H "X-Forwarded-For: $value" \
    -w "xff=$value status=%{http_code} size=%{size_download}\n" \
    "
done

这里只验证地址解析差异,不应继续访问敏感业务接口。若 WAF、CDN 与源站结果不一致,还应分别确认正常入口和源站直连路径,避免把 CDN 行为误判为源站漏洞。

技术分析

该问题通常由多个配置缺陷叠加形成:

  • 公网边缘代理使用 proxy_add_x_forwarded_for,把客户端自带的 X-Forwarded-For 原样保留并继续追加。
  • 后端应用错误地信任转发链最左侧地址,认为它一定是原始客户端。
  • Nginx 使用过宽的 set_real_ip_from,导致互联网客户端也被视为可信代理。
  • 源站安全组允许公网直连,使攻击者可以绕过负责清洗请求头的 CDN 或 WAF。
  • 管理接口把 IP 白名单当作唯一认证条件。

特别要区分“连接来源”和“业务判定地址”。Nginx 启用 realip 后,remote_addr 可能已经被替换,而 realip_remote_addr 通常保留建立 TCP 连接的原始地址。只记录 remote_addr 会导致日志看起来像请求真的来自 127.0.0.1 或办公网,给事件调查造成干扰。

日志留痕与排查

建议同时记录连接源地址、重写后的有效地址和原始转发头。示例日志格式如下:

log_format trust_chain
    '$time_iso8601 conn=$realip_remote_addr effective=$remote_addr '
    'xff="$http_x_forwarded_for" xri="$http_x_real_ip" '
    'host="$host" request="$request" status=$status '
    'upstream="$upstream_addr" request_id="$request_id"';

access_log /var/log/nginx/access_trust_chain.log trust_chain;

修改前先执行 nginx -t,确认配置正确后再平滑重载:

nginx -t && nginx -s reload

# 查找连接源不是本机,但有效地址被改写为本机的管理接口请求
grep 'request="GET /admin/' /var/log/nginx/access_trust_chain.log \
  | grep 'effective=127.0.0.1' \
  | grep -v 'conn=127.0.0.1'

# 检查外部请求提交的转发头及状态变化
grep -E 'xff="[^"]+"|xri="[^"]+"' \
  /var/log/nginx/access_trust_chain.log \
  | grep -E 'request="(GET|HEAD) /(admin|metrics|actuator)'

排查时应关联 WAF、负载均衡、Nginx 和应用日志,优先使用 request_id、时间戳、源端口及上游地址进行匹配。仅靠 IP 关联,在 NAT 或高并发代理环境中容易误判。

修复建议

第一步是收紧可信代理范围。set_real_ip_from 只能填写实际代理节点或专用网段,不能为了省事配置为全网:

# 示例:仅信任内部负载均衡网段
set_real_ip_from 10.20.1.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

real_ip_recursive 的行为依赖可信代理列表。启用前应确认代理顺序,并用真实流量回归测试;它不能弥补 set_real_ip_from 范围过宽的问题。

第二步是在公网第一跳覆盖客户端提交的转发头。如果该 Nginx 就是直接面向互联网的边缘节点,可明确重置:

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

如果前面还有 CDN,应按 CDN 文档使用其固定客户端地址头,并同步官方出口网段;同时通过安全组或防火墙限制源站只接受 CDN/WAF 节点访问。否则攻击者可以绕过边缘代理直接访问源站,并自行构造所有转发头。

第三步是收紧应用框架的可信代理设置。例如 Express 不应直接设置 trust proxy 为 true,而应限定实际代理:

app.set('trust proxy', ['loopback', '10.20.1.0/24']);

第四步是调整权限边界。IP 白名单适合做附加控制,不适合作为唯一身份认证。管理接口至少应叠加 VPN、mTLS、OIDC 或独立的反向代理认证,并将只读监控接口与可执行管理操作分离。

  • 管理路径使用独立域名和监听端口,避免与公网业务共用规则。
  • 源站禁止公网直连,只允许指定负载均衡或堡垒机网段。
  • 高风险接口同时校验用户身份、角色和请求来源。
  • 限速与封禁同时参考连接源地址和经过验证的客户端地址。
  • 保留原始转发头,但不要直接用未经验证的头作为授权依据。

修复验证

修复后应重复基线和伪造头测试,预期结果是外部请求无论提交何种 X-Real-IP、X-Forwarded-For,权限判定都保持一致;只有真实经过 VPN 或可信代理链的请求才能访问。

# 检查配置并重载
nginx -t && nginx -s reload

# 伪造头应继续被拒绝
curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \
  -H 'X-Real-IP: 127.0.0.1' \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -w 'post-fix status=%{http_code}\n' \
  " 是代理节点地址,effective 是真实客户端地址;直连源站请求则应在网络层被拒绝,而不是仅依赖应用返回 403。

总结

客户端 IP 信任链问题的核心不是某个请求头本身,而是代理边界定义错误。有效的渗透验证应同时覆盖入口头部处理、realip 配置、应用框架解析、源站直连路径和日志真实性。修复时应遵循“边缘覆盖外部输入、内部只信任明确代理、源站限制直连、管理接口叠加身份认证”的原则,并通过前后对照请求和多层日志完成闭环。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意
红队攻防路径编排示意图
侦察、入口、横向移动与权限路径梳理示意

这类问题验证时,重点不是“能不能伪造 X-Forwarded-For”,而是确认伪造值在哪一跳被采信,以及攻击者能否绕过最外层代理直接访问源站。

建议按下面顺序排查:

  1. 分别测试入口代理和源站地址。在授权环境中发送自定义请求头,对比应用识别出的客户端 IP:
    curl -k -i  \
      -H 'X-Forwarded-For: 127.0.0.1, 10.0.0.8' \
      -H 'X-Real-IP: 127.0.0.1'
    
    curl -i  \
      -H 'Host: example.com' \
      -H 'X-Forwarded-For: 127.0.0.1'
    如果经过正常入口时仍能控制应用看到的 IP,通常是边缘代理沿用了客户端提供的头;如果只有直连源站时可控,则主要问题是源站暴露或安全组配置不严。
  2. 同时记录传输层来源和解析后的来源。Nginx 启用 realip 模块后,$remote_addr 可能已被替换,单独记录它不利于追溯。可以临时增加:
    log_format ip_chain escape=json
      '{"peer":"$realip_remote_addr",'
      '"client":"$remote_addr",'
      '"xff":"$http_x_forwarded_for",'
      '"xri":"$http_x_real_ip",'
      '"host":"$host","request":"$request",'
      '"status":$status,"request_id":"$request_id"}';
    
    access_log /var/log/nginx/ip_chain.log ip_chain;
    这里的 peer 用于确认实际连接方,client 是 Nginx 计算后的地址,原始转发头用于复盘信任链。应用日志也应带上同一个 request ID,避免只看某一层日志得出误判。
  3. 核对每一跳的处理规则。最外层、直接面向互联网的代理应覆盖用户传入的转发头,而不是无条件追加:
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Real-IP       $remote_addr;
    proxy_set_header X-Request-ID    $request_id;
    后续内部代理才适合在已建立的可信链上追加:
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    如果边缘层直接使用 $proxy_add_x_forwarded_for,客户端预先放入的值会被保留下来,后端按“最左侧 IP”取值时就容易被绕过。
  4. 可信代理必须使用明确网段,不要信任任意来源:
    set_real_ip_from 10.20.0.0/16;
    set_real_ip_from 192.168.50.10/32;
    
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    不要配置 set_real_ip_from 0.0.0.0/0 或 ::/0。多级代理场景下,Nginx 会从右向左跳过可信代理,取第一个非可信地址,所以代理网段缺失或配置过宽都会改变最终结果。
  5. 应用层不要再次用另一套规则解析。例如 Express 不应简单设置:
    app.set('trust proxy', true);
    更稳妥的是限制为实际代理网段:
    const proxyaddr = require('proxy-addr');
    
    const trusted = proxyaddr.compile([
      '10.20.0.0/16',
      '192.168.50.10/32'
    ]);
    
    app.set('trust proxy', ip => trusted(ip));
    固定“信任 1 跳/2 跳”也要谨慎:如果不同请求存在长短不同的代理路径,攻击者可能从较短路径补齐伪造地址。

修复上还应配合网络层限制:源站端口只允许负载均衡器或反向代理网段访问,管理接口不要仅依赖客户端 IP 白名单。可以从源站检查当前监听和防火墙规则:

ss -lntp
nft list ruleset
iptables -S
ip6tables -S

如果使用 PROXY protocol,也要确保监听端口只能被可信代理访问;PROXY protocol 本身不提供身份认证,公网可达时同样可以伪造源地址。

回归验证至少覆盖三种情况:正常用户经过完整代理链、携带伪造转发头经过入口、绕过入口直连源站。最终应满足:外部自带头不会影响识别结果,源站无法从非代理网段访问,日志中能同时还原 socket peer、原始头和最终解析 IP。

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

最近浏览 0

  • 没有会员查看此页面。

SiteMap HACKHAT © 2026 All rights reserved.