跳转到帖子

授权测试中,为什么加一个 X-Forwarded-For 就能访问内网接口?

精选回复

发布于

背景:同一个接口,为什么测试结果不一致?

一个典型求助场景:业务通过 Nginx 发布接口,管理员给 /internal/ 配置了内网地址白名单。授权测试时,不带特殊请求头返回 403,加上 X-Forwarded-For: 10.20.30.40 却返回 200。

这类问题先别急着归因于应用鉴权失效。更常见的原因是:Nginx 错误信任了客户端提供的来源地址,随后又把这个地址用于访问控制。下面用仅监听本机的实验复现,并说明生产环境怎么定位、怎么修。

测试范围仅限自建环境或明确授权的系统。验证使用无敏感内容的静态文件,不需要访问真实管理接口或读取业务数据。

问题定位:先确定是谁返回了 403

  • 画出实际链路:测试端 → 负载均衡或反向代理 → Nginx → 应用。确认每一跳是否追加、覆盖或透传来源地址头。
  • 检查 Nginx 生效配置,而不是只看某个配置文件:使用 nginx -T,重点搜索 set_real_ip_from、real_ip_header、real_ip_recursive、allow 和 deny。
  • 对照访问日志与错误日志。标准访问控制模块拒绝时,错误日志通常能看到 access forbidden by rule。应用返回的 403 则需要结合上游状态和应用日志判断。
  • 确认是否存在更具体的 location、重写或代理规则,使两个请求实际上走了不同路径。

注意:nginx -T 输出可能包含内部域名、上游地址等信息。发到问答帖之前应脱敏,但要保留相关指令的作用域和先后关系。

技术分析:IP 白名单检查的地址可能已经被改写

Nginx 的 Real IP 模块可以根据受信代理提供的请求头替换客户端地址。配置中的几个字段分别决定:

  • set_real_ip_from:哪些连接来源有资格声明原始客户端地址。
  • real_ip_header:从哪个请求头读取地址。
  • real_ip_recursive:存在多跳代理时,如何沿地址链寻找非受信地址。

问题往往出在把 set_real_ip_from 写成了 0.0.0.0/0。这样任意 IPv4 连接来源都被当成可信代理。Real IP 模块改写地址后,allow/deny 就可能按照请求者自己填写的地址作出判断。

real_ip_recursive on 不是防伪开关。真正的安全边界是可信代理名单,以及可信代理是否正确处理外部传入的地址头。

操作步骤:在本机复现错误配置

实验需要安装带有 http_realip_module 的 Nginx。先检查构建参数;如果未包含该模块,下面配置会报未知指令。

nginx -V 2>&1 | grep -- --with-http_realip_module

mkdir -p /tmp/acl-lab/internal
printf 'authorized lab only\n' > /tmp/acl-lab/internal/check.txt

cat > /tmp/acl-lab/nginx.conf <<'EOF'
worker_processes 1;
pid /tmp/acl-lab/nginx.pid;
error_log /tmp/acl-lab/error.log notice;

events {
    worker_connections 128;
}

http {
    log_format acl '$time_iso8601 peer=$realip_remote_addr '
                   'effective=$remote_addr xff="$http_x_forwarded_for" '
                   'status=$status request="$request"';
    access_log /tmp/acl-lab/access.log acl;

    server {
        listen 127.0.0.1:8080;
        server_name localhost;
        root /tmp/acl-lab;

        # 故意错误的实验配置,禁止照搬到生产环境。
        set_real_ip_from 0.0.0.0/0;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;

        location /internal/ {
            allow 10.20.30.0/24;
            deny all;
            try_files $uri =404;
        }
    }
}
EOF

nginx -t -p /tmp/acl-lab/ -c /tmp/acl-lab/nginx.conf
nginx -p /tmp/acl-lab/ -c /tmp/acl-lab/nginx.conf

# 本机地址不在白名单,预期 403。
curl --noproxy '*' -i 

# 错误信任请求头后,预期 200,响应仅为实验文本。
curl --noproxy '*' -i \
  -H 'X-Forwarded-For: 10.20.30.40' \
  

cat /tmp/acl-lab/access.log

# 实验结束后停止独立实例。
nginx -s stop -p /tmp/acl-lab/ -c /tmp/acl-lab/nginx.conf

这里用静态文件产生响应,没有在 location 中写 return 200。原因是 return 在重写阶段就可能结束请求,用它验证 allow/deny 容易得出错误结论。

日志应该怎么看?

第二次请求中,日志通常会同时出现:

peer=127.0.0.1 effective=10.20.30.40 xff="10.20.30.40" status=200

$realip_remote_addr 保留 Real IP 改写前的连接地址,$remote_addr 则是改写后供后续处理使用的地址。两者结合,才能看出请求实际上由本机发起,却以白名单地址通过了检查。

单独看到 X-Forwarded-For 中有内网地址,并不能证明存在漏洞。应同时具备三项证据:连接来源不应被放行、有效客户端地址被不可信输入改写、访问结果因此发生变化。

风险与修复建议

如果服务直接接收客户端连接,没有可信反向代理,应移除不必要的 Real IP 配置,按连接地址执行网络访问控制。

如果确实存在代理,假设链路为“客户端 → 边缘代理 10.20.0.10 → 后端 Nginx”,且不存在更前置的 CDN,可以采用以下配置思路:

# 边缘代理:在面向客户端的入口覆盖外部传入的同名头。
# 放在相应 proxy_pass 所在的 location 中,并检查配置继承关系。
proxy_set_header X-Forwarded-For $remote_addr;

# 后端 Nginx:仅信任真实代理的来源地址。
set_real_ip_from 10.20.0.10/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 后端受限路径:允许的是客户端网段,不是代理网段。
location /internal/ {
    allow 10.60.0.0/24;
    deny all;
    # 保留实际业务的静态文件或上游处理配置。
}

这段配置只适用于上述拓扑。有 CDN、多级负载均衡或 SNAT 时,需要逐跳确认地址来源,不能直接套用。尤其不要把整段办公网、容器网段都无条件列为可信代理。

  • 通过防火墙或安全组限制后端入口,只允许实际代理访问对应业务端口,避免绕过边缘策略。
  • 管理接口保留独立身份认证和权限检查。IP 白名单是附加限制,不是管理员身份的证明。
  • 生产日志记录连接地址、有效客户端地址、请求标识和必要的上游状态;来源头属于不可信输入,日志应限制访问和保留周期。
  • 修复后至少验证:非白名单客户端正常访问被拒绝、伪造来源头仍被拒绝、合法白名单客户端经代理正常访问、直接访问后端被阻断。

总结:求助时提供哪些信息最有效?

这类问题的关键不是“请求里有没有内网 IP”,而是“谁有资格声明这个 IP,以及哪个组件拿它作了授权判断”。在问答中心提问时,附上脱敏链路图、相关生效配置、两组对照请求和对应日志,通常就能把问题收敛到具体一跳。只有响应码截图,没有连接来源和配置上下文,很容易把代理信任错误误判成应用鉴权漏洞。

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

这种现象通常不是“加了 XFF 就进入内网”,而是接口把可伪造的客户端 IP 当成了访问控制依据。先区分一下:你访问的是公网入口后面的受限接口,还是服务端真的根据请求去访问另一个内网地址?前者多半是代理信任配置问题,不能仅凭这个现象判定为 SSRF。

建议用同一个接口做对照,固定登录态、请求方法和请求体,只改变 XFF。下面地址替换为授权目标:

URL='

curl -i "$URL"
curl -i "$URL" -H 'X-Forwarded-For: 203.0.113.10'
curl -i "$URL" -H 'X-Forwarded-For: 127.0.0.1'
curl -i "$URL" -H 'X-Forwarded-For: 10.0.0.1'

不要只看状态码,还要比较响应内容、业务结果和服务端日志。若只有回环或私网地址能通过,优先找“内网 IP 放行”的判断;若任意 XFF 都能通过,则更像是错误的分支判断,或者不同请求被转发到了不同后端。

有配置权限的话,重点查两处:

  • 入口有没有覆盖客户端传来的 XFF。如果 Nginx 是唯一公网入口且直接接受客户端连接,可以用下面的方式向后端传递实际连接来源;如果前面还有 CDN/LB,则需要先按明确的代理地址范围恢复真实 IP,不能直接照搬。
location / {
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_pass 
}
  • 后端信任了哪些代理。检查 Nginx 的 set_real_ip_from、real_ip_header、real_ip_recursive,以及应用框架的代理信任配置。尤其留意信任所有来源、直接读取 XFF 第一个值,或用字符串前缀匹配私网地址的代码。后端端口若能被客户端直连,入口覆盖请求头也挡不住绕过。

定位时把 TCP 对端 IP、原始 XFF、框架解析后的客户端 IP,以及最终命中的放行规则记到同一条请求日志里,基本就能确认是哪一层信错了。修复上,可信代理链要收紧;敏感接口仍应做身份和权限校验,不能仅凭“看起来是内网 IP”就放行。

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

最近浏览 0

  • 没有会员查看此页面。