跳转到帖子

咖啡馆实战:网站反向代理与加速器后的真实 IP、鉴权边界和日志留痕核查

精选回复

发布于

背景

网站接入 CDN、WAF、四层负载均衡或 Nginx 反向代理后,客户端地址通常通过 X-Forwarded-For、X-Real-IP 或厂商专用请求头向后传递。如果代理信任范围配置过宽,应用又把客户端 IP 用于后台鉴权、限流或内网接口判断,攻击者可能通过伪造请求头绕过权限边界。

下面以已授权的网站安全测试为场景,核查三件事:真实 IP 是否只由可信代理提供、源站是否能被绕过代理直接访问、日志能否同时保留代理节点和最终客户端地址。

测试前应明确目标域名、源站地址、测试窗口和允许验证的接口。本文示例只使用单次差异请求,不涉及扫描、口令尝试或破坏性操作。

典型场景

常见链路如下:

客户端 -> CDN/WAF -> Nginx -> Web 应用
                         |
                         +-> /admin、/internal、API

应用可能存在类似判断:来源为 127.0.0.1、内网网段或办公出口 IP 时允许访问管理接口。如果应用直接读取未经校验的 X-Forwarded-For,而 Nginx 又原样转发用户提交的请求头,就会把一个网络属性错误地当成身份凭据。

问题定位

先从服务器侧确认真实配置,不要只看单个虚拟主机文件。Nginx 的 include、容器挂载和自动化模板经常导致运行配置与仓库配置不一致。

# 导出 Nginx 当前生效配置并检查语法
sudo nginx -T > /tmp/nginx-effective.conf 2>&1
sudo nginx -t

# 查找真实 IP、转发头和访问控制相关配置
grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|proxy_set_header|allow |deny ' /tmp/nginx-effective.conf

# 确认监听地址,重点检查源站是否直接监听公网
sudo ss -lntp | grep -E ':(80|443|8080|8443)[[:space:]]'

# 查看服务启动参数和近期错误日志
systemctl status nginx --no-pager
sudo tail -n 100 /var/log/nginx/error.log

重点检查以下问题:

  • 是否存在 set_real_ip_from 0.0.0.0/0 或 ::/0。
  • 是否把整个内网网段设为可信代理,而该网段中还有普通业务主机。
  • 源站 80、443 端口是否允许任意公网地址连接。
  • 第一层反向代理是否覆盖外部传入的 X-Forwarded-For,而不是无条件追加。
  • 应用是否用客户端 IP 代替正式认证,或对 /admin、/internal 使用基于 IP 的单一放行。

授权验证步骤

先记录正常请求的状态码、响应长度和请求标识,再分别提交伪造头。不要只观察页面文字,302 跳转、缓存响应和统一错误页都可能造成误判。

# 正常基线
curl -kisS --connect-timeout 5  \
  -o /tmp/base.out -D /tmp/base.hdr

# 验证应用是否错误信任 X-Forwarded-For
curl -kisS --connect-timeout 5 \
  -H 'X-Forwarded-For: 127.0.0.1' \
   \
  -o /tmp/xff.out -D /tmp/xff.hdr

# 验证是否错误信任 X-Real-IP
curl -kisS --connect-timeout 5 \
  -H 'X-Real-IP: 127.0.0.1' \
   \
  -o /tmp/xri.out -D /tmp/xri.hdr

wc -c /tmp/base.out /tmp/xff.out /tmp/xri.out
grep -iE 'HTTP/|location:|x-request-id:|server:|via:' /tmp/*.hdr

如果正常请求返回 401 或 403,而带伪造头的请求变为 200,需立即结合代理日志和应用日志确认请求实际经过的链路。只有响应体略有变化不能直接判定绕过,差异也可能来自缓存、动态令牌或风控页面。

已知源站地址且授权范围允许时,可验证是否能绕过 CDN 直连。使用 --resolve 可以同时保持正确的 Host 和 TLS SNI:

# 192.0.2.10 为文档示例地址,实际测试应使用授权资产
curl -kisS --resolve portal.example.test:443:192.0.2.10 \
   \
  -H 'X-Forwarded-For: 127.0.0.1'

如果经 CDN 访问时伪造头无效,但直连源站时生效,通常说明边缘节点做了请求头清洗,而源站仍错误地把任意连接方视为可信代理。这类问题应同时修复源站网络暴露和真实 IP 信任配置。

Nginx 真实 IP 处理细节

在启用 realip 模块前,remote_addr 表示与 Nginx 建立 TCP 连接的地址,通常是 CDN 或负载均衡节点。启用后,Nginx 会从指定请求头重写 remote_addr;realip_remote_addr 则保留原始对端地址。

可信范围必须精确到实际代理节点网段。下面以内部负载均衡网段为例:

# 仅信任确实会连接到本机的代理地址
set_real_ip_from 10.20.10.0/24;
set_real_ip_from 2001:db8:20:10::/64;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

location / {
    proxy_pass 

    # realip 处理后,remote_addr 应为已验证的客户端地址
    # 第一层自有代理建议覆盖外部传入值,不要盲目继承
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Request-ID $request_id;
}

是否使用 proxy_add_x_forwarded_for 取决于链路设计。它会把当前 remote_addr 追加到现有 X-Forwarded-For,因此在公网入口未先清洗请求头时,可能保留攻击者提交的内容。更稳妥的做法是由最外层可信入口覆盖该头,后续代理再按照固定规则追加,并让应用只信任明确的代理跳数或网段。

如果 CDN 提供专用真实 IP 头,应使用厂商明确承诺会覆盖且无法由客户端透传的头,同时定期同步其出口网段。不要仅凭请求中出现某个厂商头就认为流量经过该厂商。

应用侧权限边界

即使真实 IP 解析正确,也不建议把来源 IP 作为管理接口的唯一认证条件。IP 可用于附加限制,但正式边界应由 VPN、双向 TLS、OIDC、短期令牌或独立管理网关承担。

以 Express 为例,trust proxy 不应简单设置为 true,特别是在请求可能通过不同数量代理到达应用时。应明确可信代理网段:

app.set('trust proxy', [
  'loopback',
  '10.20.10.0/24',
  '2001:db8:20:10::/64'
]);

同时检查应用读取的是框架解析后的客户端地址,还是直接读取 req.headers 中的值。后者不具备可信链路语义。对于 /admin 和 /internal,建议先完成身份认证和权限校验,再把 IP 白名单作为额外条件;不要写成“命中内网地址即跳过认证”。

日志留痕

只记录 remote_addr 会在启用 realip 后丢失代理节点信息,只记录 X-Forwarded-For 又无法区分可信值和用户输入。建议同时记录原始连接方、解析后的客户端地址、原始转发头、请求 ID、上游地址和响应状态。

log_format proxy_trace escape=json
  '{"time":"$time_iso8601",'
  '"request_id":"$request_id",'
  '"peer_ip":"$realip_remote_addr",'
  '"client_ip":"$remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"host":"$host",'
  '"request":"$request",'
  '"status":$status,'
  '"bytes":$body_bytes_sent,'
  '"upstream":"$upstream_addr",'
  '"upstream_status":"$upstream_status",'
  '"duration":$request_time}';

access_log /var/log/nginx/proxy-trace.log proxy_trace;

应用日志应继续传递同一个 X-Request-ID,便于从 CDN、Nginx、应用和数据库审计日志中串联一次请求。不要记录 Cookie、Authorization、会话令牌或完整敏感查询参数。

源站网络收口

源站不应只依赖 Host 校验隐藏自己。应在云安全组、防火墙或前置 ACL 上,只允许 CDN、WAF、负载均衡和管理出口访问业务端口。执行前要确认健康检查、证书续期、监控探针和应急管理链路,避免误封。

# nftables 规则示意,地址需替换为实际可信代理网段
nft add table inet web_filter
nft 'add chain inet web_filter input { type filter hook input priority 0; policy accept; }'
nft add rule inet web_filter input tcp dport '{ 80, 443 }' ip saddr 203.0.113.0/24 accept
nft add rule inet web_filter input tcp dport '{ 80, 443 }' ip6 saddr 2001:db8:30::/48 accept
nft add rule inet web_filter input tcp dport '{ 80, 443 }' counter drop

# 变更后确认规则与计数器
nft list ruleset

生产环境应通过现有防火墙管理体系持久化规则,不要只执行临时命令。CDN 地址段变化时,需要有自动同步、变更审核和失败回滚机制。

修复后的复测

  • 从正常入口提交伪造 X-Forwarded-For 和 X-Real-IP,确认权限结果不发生变化。
  • 从非可信网络直连源站,确认连接被网络层拒绝,而不是仅返回一个可识别的错误页。
  • 确认 peer_ip 为实际代理地址,client_ip 为预期客户端地址,原始转发头仍可用于审计。
  • 检查 IPv4、IPv6、HTTP/2、WebSocket 和健康检查链路,避免只修复单一路径。
  • 确认 401、403、302 和缓存命中情况下均不会泄露后台内容。
  • 验证应用不再以客户端 IP 单独授予管理员或内网权限。

总结

反向代理和加速器场景中的核心不是“拿到真实 IP”,而是建立可验证的信任链:只有指定代理可以声明客户端地址,源站只接受可信代理连接,应用不把网络位置当成唯一身份,日志同时保留原始对端和解析结果。排查时将请求差异、运行配置、网络暴露和日志证据放在一起判断,才能避免把缓存差异当成漏洞,也能防止修完请求头后仍留下可直连源站这一条绕过路径。

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

这类场景最容易漏的不是“能否拿到真实 IP”,而是谁有资格声明真实 IP,以及绕过加速器后鉴权是否仍然成立。建议按下面三条链分别核查。

1. 真实 IP:只信任明确的代理链

不要直接使用客户端可控的 X-Forwarded-For。以 Nginx 为例,只把 CDN、负载均衡器的出口网段加入信任列表:

set_real_ip_from 10.20.0.0/16;        # 内部负载均衡网段
set_real_ip_from 203.0.113.0/24;      # CDN 官方出口网段,示例地址

real_ip_header X-Forwarded-For;
real_ip_recursive on;

real_ip_recursive on 会从代理链右侧向左找第一个非可信地址。CDN 网段需要定期从官方接口同步,不能长期手工写死。

可以伪造请求头验证配置是否失效:

curl -kI  \
  -H 'X-Forwarded-For: 1.1.1.1, 2.2.2.2'

检查日志中的客户端 IP 是否被直接改成 1.1.1.1。如果普通公网请求可以影响限流、封禁或审计 IP,说明信任边界配置过宽。

2. 源站绕过:鉴权不能只放在加速器侧

先确认源站 IP 是否能被直接访问:

curl -kI --resolve  \
  

curl -kI  \
  -H 'Host: 

重点比较经 CDN 和直连源站时,管理接口、API、登录态校验、限流结果是否一致。源站最好在网络层只允许 CDN/LB 出口访问;条件允许时再加 CDN 到源站的 mTLS。

如果只能使用“回源密钥请求头”,必须由 CDN覆盖同名客户端请求头,而不是原样转发;源站对缺失或错误值直接拒绝。不过这只能算补充措施,优先级低于防火墙白名单和 mTLS。

应用接收的身份头也要注意。类似 X-User、X-Role 不能直接信任客户端输入,应先清除,再由可信鉴权模块写入:

location /api/ {
    auth_request /_auth;

    auth_request_set $auth_user $upstream_http_x_authenticated_user;

    proxy_set_header X-User "";
    proxy_set_header X-Role "";
    proxy_set_header X-Authenticated-User $auth_user;

    proxy_pass 
}

同时建议列一个接口矩阵,逐项验证“边缘鉴权关闭、直连源站、伪造身份头、过期令牌”四种情况。真正敏感的权限判断仍应落在应用或统一鉴权服务,不能只依赖 CDN 规则。

3. 缓存边界:避免把用户态响应缓存成公共内容

带 Authorization、会话 Cookie 或 Set-Cookie 的响应,应默认绕过共享缓存:

proxy_cache_bypass $http_authorization $cookie_session;
proxy_no_cache     $http_authorization $cookie_session
                   $upstream_http_set_cookie;

账户页、订单页等敏感接口建议由应用明确返回:

Cache-Control: private, no-store

测试时可分别用两个账号请求同一 URL,观察 Age、X-Cache 等响应头,确认账号 B 不会命中账号 A 的响应。还要检查缓存键是否遗漏租户 ID、语言、设备类型等实际影响响应内容的维度。

4. 日志同时保留“连接来源”和“解释后的客户端 IP”

只记录 $remote_addr 不够。启用 realip 模块后,它可能已经被改写;建议同时保留原始连接地址、完整转发链和请求 ID:

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

access_log /var/log/nginx/access-audit.json audit;

proxy_set_header X-Request-ID $request_id;
add_header X-Request-ID $request_id always;

不要记录 Authorization、完整 Cookie、密码、验证码和敏感查询参数。日志侧至少应能关联 CDN 请求 ID、源站请求 ID、应用审计事件,并对“直连源站”“异常长 XFF 链”“同一身份跨大量 IP”“鉴权失败突增”设置告警。

最后可用一个简单标准判断是否核查到位:客户端不能伪造审计 IP,源站不能绕过边缘防护,身份信息不能由客户端声明,用户态响应不能进入公共缓存,且一次请求能从边缘一路追踪到应用。

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

最近浏览 0

  • 没有会员查看此页面。

SiteMap HACKHAT © 2026 All rights reserved.