跳转到帖子

授权测试中伪造 X-Forwarded-For 后后台不再返回 403,如何定位并修复?

精选回复

发布于

背景:这个现象是否算权限绕过?

一个常见求助场景:后台通过 Nginx 限制办公出口 IP,正常从测试机访问返回 403;加上 X-Forwarded-For: 办公出口IP 后,却变成应用返回的 401。后台没有直接登录成功,这算不算漏洞?应该检查哪一层?

结论:如果测试机原本不在允许范围内,且唯一变化是请求头,那么已经证明来源 IP 限制可能被绕过;但不能据此宣称后台身份认证也被绕过。下面按授权测试的方式拆开验证,不做口令尝试,也不访问业务数据。

一、先明确链路和预期

假设实际链路如下,地址均为示例,执行时替换为授权环境中的值:

  • 测试客户端 → 边缘代理 10.20.0.10 → 后台 Nginx → 应用 127.0.0.1:8080。
  • 办公出口:198.51.100.23,允许进入后台认证页面。
  • 非办公出口:应在后台 Nginx 被拒绝,不能到达应用。
  • /admin/health 是已经确认无副作用的探测接口,未登录请求到达应用时固定返回 401。

这里的 401 只是区分“进入应用认证层”和“在入口被拒绝”的观测信号。若环境返回登录跳转或统一错误页,需要结合日志判断,不能机械地用状态码下结论。

二、问题定位:谁把请求头变成了客户端地址?

首先检查实际生效配置,而不是只看某个站点文件。Nginx 的 include、上级配置继承、默认虚拟主机都可能影响结果:

sudo nginx -T 2>&1 | grep -nE 'listen|server_name|include|real_ip_header|set_real_ip_from|real_ip_recursive|allow |deny |proxy_set_header'
nginx -V 2>&1 | grep -- --with-http_realip_module

完整的 nginx -T 输出可能包含内部地址、证书路径或配置中的凭据,不要直接贴到公开求助帖。定位时保留相关 server、location 和 http 级继承配置即可。

重点找下面这种组合:

# 危险示例:把所有连接来源都当成可信代理
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 198.51.100.23;
    deny all;
    proxy_pass 
}

启用 realip 模块后,$remote_addr 可能被请求头解析结果替换;访问控制模块随后使用这个地址做判断。若可信代理范围设置成全网,外部请求中可控的地址就可能变成白名单判断依据。

另一个容易漏掉的情况是:后台 Nginx 只信任了边缘代理,但边缘代理没有清洗外部传入的 X-Forwarded-For。信任代理地址正确,不等于代理转交的地址链可信。

三、先补观测,再做最小验证

在后台 Nginx 的 http 上下文临时增加独立日志格式,并在对应 server 中启用。不要记录 Cookie、Authorization 或完整请求体。

# http 上下文
log_format ip_boundary escape=json
    '{"time":"$time_iso8601",'
    '"rid":"$request_id",'
    '"client":"$remote_addr",'
    '"peer":"$realip_remote_addr",'
    '"xff":"$http_x_forwarded_for",'
    '"uri":"$uri",'
    '"status":$status,'
    '"upstream_status":"$upstream_status"}';

# 对应 server 上下文
access_log /var/log/nginx/ip_boundary.log ip_boundary;

$remote_addr 是 realip 处理后的地址,$realip_remote_addr 保留处理前的连接对端地址;在这条链路中,后者通常是边缘代理。$upstream_status 用于辅助判断请求是否进入应用,但发生重试或内部处理时仍需结合完整配置分析。

配置检查通过后再重载,然后从不在白名单内的授权测试出口发送两次请求:

sudo nginx -t && sudo systemctl reload nginx

# 基线:预期由入口拒绝
curl -sS -o /dev/null -w 'baseline=%{http_code}\n' \
  --connect-timeout 5 --max-time 10 \
  

# 只改变来源地址头,不带账号凭据
curl -sS -o /dev/null -w 'with_xff=%{http_code}\n' \
  --connect-timeout 5 --max-time 10 \
  -H 'X-Forwarded-For: 198.51.100.23' \
  

sudo tail -n 20 /var/log/nginx/ip_boundary.log

有说服力的证据组合是:基线请求为 403,未出现上游响应;第二次请求的 client 变成办公出口地址,上游返回 401。此时可以描述为“来源地址访问控制绕过,应用认证仍生效”。仅凭页面内容变化,证据还不够完整。

四、修复:逐跳建立地址信任边界

下面配置只适用于“边缘代理直接接收客户端连接”的上述拓扑。如果边缘前还有 CDN、负载均衡或其他代理,需要先明确它们各自的地址来源和清洗规则,不能直接照搬。

# 边缘代理:覆盖外部输入,而不是原样转交
# 前提:这里的 $remote_addr 就是真实客户端连接地址
location / {
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass 
}

# 后台 Nginx:只信任明确的上一跳
# 需要删除或收紧其他配置中继承的宽泛可信范围
set_real_ip_from 10.20.0.10/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

location /admin/ {
    allow 198.51.100.23;
    deny all;

    # 向应用传递已经解析、规范化的单个地址
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_pass 
}

不要把 $proxy_add_x_forwarded_for 当成自动清洗机制:它会保留原有地址链再追加地址。多级代理确实可能需要保留链路,但每一跳都必须有明确的可信范围和解析方式。

  • 后台监听端口通过网络 ACL 或安全组只允许边缘代理连接,避免客户端绕开边缘直连。
  • 应用仅监听回环地址,或只允许后台 Nginx 访问;否则攻击者可能绕开 Nginx 的访问控制。
  • 应用框架中的 trusted proxies 也应限制为实际上一跳,不要启用“信任所有代理”。
  • 清点 Forwarded、X-Real-IP 等其他地址头,确保应用不会改用未经规范化的字段。必要时在代理层清除不用的头。
  • 来源 IP 只能作为附加控制,不能替代后台账号认证、角色校验和高风险操作的再次确认。

五、修复后怎么验收?

  • 非白名单出口,不带地址头:仍为入口 403。
  • 非白名单出口,伪造办公出口地址:仍为入口 403,日志中的 client 不应变成伪造值。
  • 非白名单出口,传入逗号分隔的多地址链:仍不能影响最终白名单判断。
  • 办公出口,不带登录凭据:可以进入应用认证层,但不能取得已认证权限。
  • 在授权范围内检查后台直连路径:应被网络策略阻断,不能从其他入口访问同一应用。

验收记录应保留测试时间、实际出口、请求差异、对应日志和生效配置版本。诊断日志设置保留期限和访问权限,避免长期积累客户端地址信息。

总结:求助时给出哪些信息最有用?

在问答中心提这类问题,建议提供脱敏链路图、相关 realip 与 allow/deny 配置、两次请求的差异,以及同时间段的 client、peer、xff、status、upstream_status。把“TCP 对端是谁”“谁声明了客户端地址”“哪一层执行授权”分开,通常就能定位。不要把 IP 白名单绕过扩大描述成完整登录绕过,也不要因为应用还有登录页就忽略入口权限边界失效。

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

这个现象优先怀疑是某一层把客户端可控的 X-Forwarded-For 当成了 IP 白名单依据。不过先别只看状态码:403 变成 200,也可能只是返回登录页或统一错误页,需要确认是否真的拿到了受限接口的数据。

建议按下面顺序定位:

  1. 固定请求,只改一个头。保持 Cookie、路径、请求方法一致,先不要自动跟随重定向,保存响应头和响应体:

    URL='
    curl -sS -D baseline.headers -o baseline.body "$URL"
    curl -sS -D modified.headers -o modified.body \
      -H 'X-Forwarded-For: 已验证会改变结果的地址' "$URL"
    diff -u baseline.headers modified.headers
    sha256sum baseline.body modified.body

    实际测试时带上相同的认证信息,注意别把凭证写进工单或共享日志。

  2. 找出是谁返回的 403。在入口代理临时记录连接对端、原始请求头、最终识别地址、上游状态和请求 ID,再关联 WAF、网关及应用日志。Nginx 开启 realip 模块时,可参考下面的日志格式,配置放在 http 块:

    log_format iptrace escape=json
      '{"rid":"$request_id","peer":"$realip_remote_addr",'
      '"effective":"$remote_addr","xff":"$http_x_forwarded_for",'
      '"status":"$status","upstream_status":"$upstream_status",'
      '"upstream":"$upstream_addr"}';
    
    access_log /var/log/nginx/iptrace.log iptrace;

    重点看:伪造后 effective 是否变化;403 时是否存在上游状态;应用的鉴权日志使用了哪个 IP。不要仅凭响应里的 Server 字段判断拦截层。

  3. 沿着命中的判断追代码或配置。查 Nginx 的 set_real_ip_from、real_ip_header、real_ip_recursive,以及应用的可信代理设置。尤其检查是否信任了 0.0.0.0/0,或者代码直接取 X-Forwarded-For.split(',')[0] 做后台放行。

修复的核心是明确代理信任边界,不是简单禁止这个头。例如,若 Nginx 就是直接面向客户端的最外层入口,且没有用 realip 把任意客户端头改写成 $remote_addr,转发给应用时可以覆盖而不是追加:

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

如果前面还有 CDN/LB,这段不能直接照搬:应只信任实际代理的出口地址范围,并按该代理的头部约定恢复客户端 IP。应用也只应信任来自指定代理的转发信息;后端端口通过防火墙或安全组限制为仅代理可达。同时检查是否还接受 Forwarded 等其他客户端地址来源,避免修了一个头、另一个仍可影响鉴权。

最后回归三类请求:不带头、伪造单个地址、伪造多级地址链,确认授权结果一致;再验证真实代理链下客户端地址仍能正确识别。后台权限本身应由登录身份和角色决定,IP 白名单只作为额外限制。

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

最近浏览 0

  • 没有会员查看此页面。