跳转到帖子

咖啡馆实战:网站接入 CDN/反向代理后,如何验证真实 IP、源站边界与日志留痕

精选回复

发布于

背景

网站接入 CDN、WAF 或自建反向代理后,常见架构是“客户端 → 加速节点 → Nginx → 应用”。这类架构最容易被忽略的不是某个具体漏洞,而是代理信任边界:源站错误信任客户端提交的 X-Forwarded-For,应用又依据客户端 IP 放行后台、调试接口或内部 API。

在一次授权测试中,某管理接口只允许 127.0.0.1 和办公网访问。正常外部请求返回 403,但向公开源站发送带有 X-Forwarded-For: 127.0.0.1 的请求后变成 200。问题并不在业务鉴权代码,而是 Nginx 将任意来源提供的请求头改写成了真实客户端地址。

以下操作仅适用于自有或明确授权的站点。示例使用 portal.test 和 RFC 5737 文档地址 203.0.113.10,不对应真实目标。

问题定位

先确认请求实际经过哪些节点,并分别记录边缘入口和源站的响应。不要只看页面内容,应同时比较状态码、响应头、证书和日志中的连接来源。

curl -skI 

# 已知并获准测试源站时,绕过 DNS 直接连接源站
curl -skI --resolve portal.test:443:203.0.113.10 \
  

# 验证源站是否错误采信客户端提交的转发头
curl -skI --resolve portal.test:443:203.0.113.10 \
  -H 'X-Forwarded-For: 127.0.0.1' \
   403,而第三个请求变为 200、302 或返回内容长度明显变化,应继续检查 realip 配置以及应用使用的 IP 变量。测试时不需要执行新增账号、上传文件等有状态操作,仅验证访问控制差异即可。

技术分析

Nginx 的 ngx_http_realip_module 会按照 real_ip_header 读取地址,并在连接来源命中 set_real_ip_from 时改写 remote_addr。危险配置通常如下:

# 错误示例:所有来源都被视为可信代理
set_real_ip_from 0.0.0.0/0;
set_real_ip_from ::/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

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

此时任意直连源站的客户端都可以构造 X-Forwarded-For。由于访问控制阶段看到的是被改写后的 remote_addr,allow/deny 规则会把外部请求误判为本机或办公网请求。

real_ip_recursive on 本身不是漏洞。它会沿地址链反向查找最后一个非可信地址,前提是 set_real_ip_from 只包含真正受控的代理地址。如果可信范围配置为全网,递归解析就失去了安全边界。

配置与链路检查

在服务器上先展开完整 Nginx 配置,避免只检查单个文件。include 引入的配置经常是问题来源。

sudo nginx -T 2>&1 | grep -nE \
  'set_real_ip_from|real_ip_header|real_ip_recursive|allow |deny |proxy_set_header'

# 检查 80/443 是否直接暴露在公网地址
sudo ss -lntp | grep -E ':(80|443)[[:space:]]'

# 查看近期连接的实际 TCP 对端
sudo journalctl -u nginx --since '30 min ago' --no-pager

随后确认应用到底读取哪个值。常见框架可能读取 REMOTE_ADDR、X-Forwarded-For、X-Real-IP,或者启用了类似 ProxyFix、ForwardedHeaders 的中间件。代理层和应用层只能有一套清晰的可信代理规则,不能无条件信任请求头。

检查项安全预期
源站 80/443仅允许 CDN、负载均衡器或运维网段连接
set_real_ip_from只配置官方公布且已核验的代理出口网段
X-Forwarded-For由边缘节点覆盖或规范追加,不直接采信外部值
后台鉴权账号、会话、MFA 等业务鉴权优先,IP 仅作附加条件
日志同时保留 TCP 对端、解析后客户端地址和完整转发链

安全配置示例

以下示例假设 192.0.2.0/24 和 2001:db8:100::/48 是经过核验的代理出口地址。实际部署必须替换为供应商当前公布的地址,并建立网段变更流程。

# 只信任明确的反向代理/CDN 出口
set_real_ip_from 192.0.2.0/24;
set_real_ip_from 2001:db8:100::/48;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 向上游传递解析后的地址,并保留主机信息
location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Request-ID $request_id;
    proxy_pass 
}

如果边缘代理已经覆盖 X-Forwarded-For,源站应按该产品的规则解析。如果边缘只是追加地址,需要明确地址链中每一跳的行为。多层代理场景中,不建议仅凭“取第一个 IP”或“取最后一个 IP”编写业务代码。

日志留痕

只记录 remote_addr 不足以回溯代理链。启用 realip 后,remote_addr 是解析后的客户端地址,而 realip_remote_addr 保存建立 TCP 连接的原始对端。建议同时记录二者、原始转发头、请求标识和上游响应。

log_format proxy_audit escape=json
  '{'
  '"time":"$time_iso8601",'
  '"request_id":"$request_id",'
  '"host":"$host",'
  '"peer_ip":"$realip_remote_addr",'
  '"client_ip":"$remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"method":"$request_method",'
  '"uri":"$request_uri",'
  '"status":$status,'
  '"bytes":$body_bytes_sent,'
  '"upstream":"$upstream_addr",'
  '"upstream_status":"$upstream_status"'
  '}';

access_log /var/log/nginx/proxy_audit.log proxy_audit;

变更后先检查语法,再平滑加载并观察错误日志。

sudo nginx -t && sudo systemctl reload nginx
sudo tail -F /var/log/nginx/proxy_audit.log /var/log/nginx/error.log

排查伪造时可重点关注三类记录:peer_ip 不属于可信代理网段但带有 X-Forwarded-For;client_ip 为回环、链路本地或内网地址;同一 peer_ip 在短时间内提交大量不同的地址链。

源站边界加固

修正 Nginx 配置仍不够。只要源站允许互联网直接访问,就存在绕过 CDN 防护、限速和缓存策略的可能。应在云安全组、主机防火墙或上游 ACL 中限制 80/443,仅允许代理出口地址。

  • 保留独立的运维入口,避免调整规则时把 SSH 或管理通道一并封禁。
  • 先添加新代理网段并验证流量,再删除旧网段,防止 CDN 节点切换导致业务中断。
  • IPv4 和 IPv6 必须同时限制,很多源站只封了 IPv4,却仍通过 AAAA 记录或公网 IPv6 暴露。
  • 不要依赖隐藏源站 IP。历史 DNS、证书透明度记录和邮件服务都可能使地址暴露。

回归验证

修复完成后,至少验证以下四组请求:

# 1. 正常通过域名和 CDN 访问,应保持业务可用
curl -sk -o /dev/null -w '%{http_code}\n' 

# 2. 非可信地址直连源站,预期超时、拒绝连接或被防火墙丢弃
curl -sk --connect-timeout 5 \
  --resolve portal.test:443:203.0.113.10 \
  -o /dev/null -w '%{http_code}\n' 

# 3. 外部伪造回环地址,后台仍应返回 401/403
curl -sk -H 'X-Forwarded-For: 127.0.0.1' \
  -o /dev/null -w '%{http_code}\n' 

# 4. 正常请求后核对 peer_ip、client_ip、xff 和 request_id
sudo tail -n 20 /var/log/nginx/proxy_audit.log

风险与修复建议

  • 立即删除 0.0.0.0/0、::/0 等过宽的 set_real_ip_from 配置。
  • 在网络层限制源站入口,不让非代理流量到达 Nginx。
  • 后台和内部 API 不应仅依赖来源 IP,应补充可靠的身份认证和授权检查。
  • 对回环、私网、链路本地地址出现在外部转发链中的情况建立告警。
  • 保存配置变更时间、测试请求 ID 和修复前后日志,便于复盘及确认影响范围。
  • 若历史日志只记录了改写后的地址,不能直接把它当作攻击源;应结合负载均衡日志、防火墙流日志和 CDN 请求 ID 交叉确认。

总结

网站使用 CDN 或反向代理后,真实 IP 不是一个普通请求头,而是一条需要明确认证边界的数据链。验证时应同时检查源站暴露、可信代理范围、应用取值、访问控制和日志字段。比较稳妥的落地方式是:网络层只允许代理访问源站,Nginx 只信任明确代理网段,业务后台继续执行独立鉴权,并在日志中同时保留原始 TCP 对端和解析后的客户端地址。

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

这类接入建议分三层验收:客户端 IP 是否可信、源站是否只能被代理访问、日志是否能还原完整链路。只看页面能打开不够。

1. 真实 IP 只接受可信代理写入

Nginx 不要无条件信任 X-Forwarded-For,否则客户端自己加一个请求头就能伪造 IP。应当只信任 CDN 官方公布的出口网段,并确认这些网段有自动更新机制:

set_real_ip_from 203.0.113.0/24;   # 示例,替换为 CDN 官方网段
set_real_ip_from 2001:db8:1234::/48;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

如果厂商提供专用头,例如 CF-Connecting-IP,优先按厂商文档使用,并确认 CDN 会覆盖而不是透传客户端提交的同名头。后面还有一层自建反代时,每一跳的信任范围都要明确,应用框架里的 trusted proxies 也要同步配置。

可以直接做伪造测试:

curl -sS -H 'X-Forwarded-For: 1.2.3.4' 
curl -sS -H 'X-Real-IP: 5.6.7.8' 

最终应用和日志中的客户端地址不应直接变成手工填写的值。若使用 PROXY protocol,还要同时限制监听端口来源,否则直接连接该端口也可能伪造协议头。

2. 源站边界要从外部网络验证

源站的 80/443 应只允许 CDN IPv4、IPv6 网段,SSH/管理端口只允许 VPN 或堡垒机。常见遗漏是只封了 IPv4,AAAA 对应的 IPv6 源站仍可直连。

在一台不属于 CDN 的外部主机上绕过 DNS 测试:

curl -vk --connect-timeout 5 \
  --resolve example.com:443:源站IP \
  

正确结果应是超时、拒绝连接,或者在源站入口被明确拒绝,而不是返回正常业务页面。源站侧同时抓包确认实际对端:

sudo tcpdump -ni any 'tcp dst port 443'
sudo ss -tn state established '( sport = :443 )'

还要检查历史 DNS、旧域名、邮件头、公开监控记录和证书透明度记录是否暴露过源站。若业务允许,可以在网络白名单之外再加一层 CDN 到源站的 mTLS;单独依赖“秘密请求头”不够,因为源站 IP 一旦暴露,该头可能被重放或泄漏。

3. 日志同时保留客户端地址和代理对端地址

排障时只记录重写后的 $remote_addr 会丢失证据。Nginx 可同时记录真实 IP、直接连接者、原始转发链及请求 ID:

log_format trace escape=json
  '{"time":"$time_iso8601",'
  '"client_ip":"$remote_addr",'
  '"peer_ip":"$realip_remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"host":"$host",'
  '"method":"$request_method",'
  '"uri":"$uri",'
  '"status":$status,'
  '"request_time":$request_time,'
  '"request_id":"$request_id",'
  '"cdn_request_id":"$http_cf_ray"}';

access_log /var/log/nginx/access_trace.log trace;

cdn_request_id 应替换为实际厂商的链路 ID。URI 中如果可能带 token,建议改记不含参数的 $uri,不要直接记录完整 $request_uri。日志最好实时投递到独立日志节点,统一 NTP 时间,并限制本机业务账号删除或修改历史日志。

最后建议形成固定验收表:正常经 CDN 访问、伪造转发头、直接访问 IPv4 源站、直接访问 IPv6 源站、错误 Host/SNI、绕过 CDN 的大请求各测一次。这样比单看 IP 查询页面更容易发现信任链和边界配置问题。

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

最近浏览 0

  • 没有会员查看此页面。

SiteMap HACKHAT © 2026 All rights reserved.