跳转到帖子

咖啡馆实战:网站反向代理与加速器配置不当导致的鉴权绕过及日志失真排查

精选回复

发布于

背景

网站接入 CDN、WAF、Nginx 反向代理或自建加速器后,应用获取到的客户端地址通常不再是用户真实 IP,而是上一层代理节点的地址。为恢复真实 IP,很多站点会直接信任 X-Forwarded-For、X-Real-IP、CF-Connecting-IP 等请求头。

问题在于:如果源站可以被公网直接访问,或者 Nginx 将任意客户端提交的转发头原样传给后端,攻击者就可能伪造来源地址。这类配置缺陷经常影响后台 IP 白名单、接口限流、审计日志和封禁策略。下面以授权测试环境中的“CDN + Nginx + Web 应用”架构为例,说明如何验证信任边界。

典型场景

测试目标的后台接口只允许内网地址访问,应用代码根据 REMOTE_ADDR 或代理头进行判断:

def allow_admin(request):
    client_ip = request.headers.get("X-Forwarded-For", request.remote_addr)
    client_ip = client_ip.split(",")[0].strip()
    return client_ip.startswith("10.") or client_ip == "127.0.0.1"

这段逻辑存在两个问题:应用无条件信任用户可控的请求头;多级代理链中直接取最左侧地址,却没有确认该地址是否由可信代理追加。

常见的错误 Nginx 配置如下:

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

这里的 $http_x_forwarded_for 和 $http_x_real_ip 都来自客户端请求。代理没有重建可信头,而是将攻击者提供的值直接交给应用。

问题定位

授权测试时,先确认实际访问链路,不要直接尝试绕过。重点检查域名入口、源站暴露情况、代理层数量,以及应用最终使用哪个字段作为客户端地址。

  • 通过 DNS 记录和资产清单确认域名是否指向 CDN 或负载均衡。
  • 检查源站防火墙、安全组是否只允许代理节点地址段访问。
  • 查看 Nginx access_log 中的 remote_addr、http_x_forwarded_for 和 realip_remote_addr。
  • 检查应用框架是否启用了 ProxyFix、ForwardedHeaders 或 trust proxy。
  • 确认后台白名单、限流和封禁逻辑引用的是原始连接地址还是转发头。

建议临时增加独立日志格式,将代理解析前后的地址同时记录:

log_format proxy_audit '$time_iso8601 '
                       'remote=$remote_addr '
                       'realip=$realip_remote_addr '
                       'xff="$http_x_forwarded_for" '
                       'xri="$http_x_real_ip" '
                       'host=$host request="$request" status=$status';

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

授权验证步骤

第一步是在测试接口上发送普通请求,记录返回状态和服务端日志:

curl -k -i  -k -i  \
  -H 'X-Forwarded-For: 127.0.0.1'

curl -k -i  \
  -H 'X-Real-IP: 10.10.10.20'

curl -k -i  \
  -H 'Forwarded: for=127.0.0.1;proto=https;host=test.example.com'

如果普通请求返回 403,而添加某个头后返回 200,不能立即认定为完整鉴权绕过。还需要核对代理日志和应用日志,确认变化确实来自 IP 判断,而不是缓存、Host 路由或其他条件。

第三步,验证多级代理链处理是否正确:

curl -k -i  \
  -H 'X-Forwarded-For: 127.0.0.1, 198.51.100.25'

若应用固定取最左侧值,攻击者可将伪造地址放在链首。若应用固定取最右侧值,在存在多个可信代理时也可能错误识别 CDN 节点或边缘负载均衡地址。正确做法是从连接来源开始,逐级剔除已配置的可信代理,只接受可信设备追加的地址。

源站直连检查

即使域名入口配置正确,只要源站 IP 可以从公网直连,攻击者就可能绕过 CDN/WAF,并自行构造代理头。测试时可以在已知源站地址的前提下,通过 Host 和 resolve 参数验证入口限制:

curl -k -i --resolve test.example.com:443:203.0.113.10 \
   \
  -H 'X-Forwarded-For: 127.0.0.1'

这里应重点观察三个结果:TCP/TLS 是否可建立、虚拟主机是否正常响应、源站是否接受非代理节点来源。若源站对公网返回完整业务页面,说明加速器并未形成真正的入口边界。

安全配置示例

Nginx 应仅信任明确的代理地址段,并使用 realip 模块解析客户端地址。地址段应来自实际部署清单或服务商正式文档,不应使用 0.0.0.0/0。

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_pass 
}

$proxy_add_x_forwarded_for 会在现有链后追加当前客户端地址,但它并不能单独解决伪造问题。安全性依赖 set_real_ip_from 是否仅包含可信代理,以及源站是否拒绝其他来源。

如果后端不需要保留完整代理链,可以在边缘代理处直接覆盖外部提交的头:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Forwarded "";

对于 Express,应限制可信代理数量或地址段,避免直接使用 app.set('trust proxy', true):

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

Flask/Werkzeug 的 ProxyFix 同样需要按实际代理层数配置,不能因为部署了 CDN 就盲目相信所有转发字段:

from werkzeug.middleware.proxy_fix import ProxyFix

app.wsgi_app = ProxyFix(
    app.wsgi_app,
    x_for=1,
    x_proto=1,
    x_host=1,
    x_port=0,
    x_prefix=0
)

日志留痕与排查

处理此类事件时,不要只检索包含 127.0.0.1 的 X-Forwarded-For。攻击者也可能使用内网地址、云元数据地址或与代理节点相似的地址。建议关联原始连接地址、转发链、请求路径、认证账号和状态码。

grep -E 'xff="(127\.0\.0\.1|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)' \
  /var/log/nginx/proxy_audit.log

awk '$0 ~ /status=200/ && $0 ~ /\/internal\// {print}' \
  /var/log/nginx/proxy_audit.log

还应检查同一 remote_addr 是否在短时间内提交大量不同的 X-Forwarded-For,以及同一账号是否突然从多个逻辑来源地址访问。日志至少保留原始 socket 地址和解析后的业务地址,不能只保留其中一个。

字段用途
remote_addr记录与 Nginx 建立连接的直接来源
realip_remote_addr保留 realip 模块改写前的地址
http_x_forwarded_for保留完整转发链,供事件回溯
request_id关联代理、应用和网关日志
upstream_addr确认请求实际转发到哪个后端实例

风险与修复建议

  • 不要使用客户端可控的 IP 头作为唯一鉴权依据。后台接口应同时要求身份认证和细粒度授权。
  • 在安全组或防火墙层限制源站入口,只允许 CDN、WAF、负载均衡及运维网络访问。
  • 代理层统一删除或覆盖外部提交的 Forwarded、X-Real-IP 和 X-Forwarded-For。
  • 可信代理必须使用明确地址段,并建立更新流程,避免地址范围变更后长期失效。
  • 限流、封禁和审计应使用同一套经过验证的客户端地址解析逻辑。
  • 修复后重新执行普通请求、伪造头、多级代理链和源站直连四组回归测试。
IP 白名单适合作为附加限制,不适合作为后台接口的唯一认证机制。代理头是否可信,不取决于字段名称,而取决于该字段是否由受控代理在受控链路中生成。

总结

网站接入加速器后,真正需要保护的是“客户端到代理、代理到源站、源站到应用”三段链路的信任边界。排查时应同时验证源站暴露、代理头重建、框架信任配置和日志完整性。修复重点不是简单屏蔽某个请求头,而是限制源站入口、明确可信代理、统一地址解析,并让敏感接口继续依赖可靠的身份认证与权限控制。

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

这类问题建议把“鉴权链路”和“日志取证链路”分开排查,重点确认源站是否错误信任了客户端可控的转发头。

先绕过加速器直连源站,对同一请求分别测试,并显式伪造常见头部:

curl -i 
curl -i --resolve example.com:443:源站IP 

curl -i  \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -H 'X-Real-IP: 127.0.0.1' \
  -H 'X-Original-URL: /admin' \
  -H 'X-Rewrite-URL: /admin'

如果只有携带某个头时能绕过,通常是应用把 X-Forwarded-For 当成可信来源,或者代理与后端对重写前后的 URI 理解不一致。边缘层应覆盖这些头,而不是把客户端值继续向后拼接:

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-Forwarded-Proto $scheme;

    proxy_set_header X-Original-URL "";
    proxy_set_header X-Rewrite-URL  "";

    proxy_pass 
}

若前面还有可信 CDN,不能简单信任任意来源的 X-Forwarded-For。应只信任 CDN 官方出口网段,并保证源站防火墙只允许这些网段访问:

# nginx
set_real_ip_from 203.0.113.0/24;   # 替换为实际 CDN 网段
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 防火墙思路:先放行管理入口/CDN,再拒绝公网直连源站
iptables -A INPUT -p tcp --dport 443 -s 203.0.113.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j DROP

日志失真往往是日志只记录了“解析后的客户端 IP”,却没保留 TCP 对端和原始头。排查阶段建议同时记录三者,并加请求 ID 串联 CDN、代理和应用日志:

log_format trace escape=json
  '{"time":"$time_iso8601",'
  '"request_id":"$request_id",'
  '"peer":"$remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"realip":"$http_x_real_ip",'
  '"host":"$host",'
  '"request":"$request",'
  '"uri":"$uri",'
  '"upstream":"$upstream_addr",'
  '"status":$status,'
  '"upstream_status":"$upstream_status"}';

access_log /var/log/nginx/access_trace.log trace;
proxy_set_header X-Request-ID $request_id;

还要检查加速器缓存规则:登录态、管理接口和带 Authorization/Cookie 的请求不应进入公共缓存,否则可能表现为“鉴权绕过”,实际是未授权用户命中了已缓存的授权响应。Nginx 可先做保守限制:

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

location ^~ /admin/ {
    proxy_no_cache 1;
    proxy_cache_bypass 1;
    add_header Cache-Control "private, no-store" always;
}

最后建议核对代理层与应用层实际用于鉴权的路径:同时记录 $request_uri、$uri 和应用收到的路由,重点测试 URL 编码、重复斜杠、分号参数及路径归一化差异。修复原则是鉴权放在最终路由解析之后,并且源站自身必须执行鉴权,不能只依赖加速器上的访问规则。

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

最近浏览 0

  • 没有会员查看此页面。