代理转发链路中最容易被忽视的安全问题,不是代理本身被攻破,而是代理在“好心”传递信息的过程中,把不该暴露的东西暴露了出去。

内网IP泄露的典型场景是这样的:后端服务在处理请求时,把 X-Forwarded-For、X-Real-IP 或者 Location 头里的地址原样写进了响应。如果这个响应经过反向代理直接返回给公网客户端,客户端就能看到 192.168.x.x 或 10.x.x.x 这样的内网地址。攻击者拿到这些信息,等于拿到了一张内网拓扑的草图。

这篇文章从代理转发链路的视角,梳理内网IP泄露的几条主要路径,以及对应的header过滤策略。

一、泄露从哪里来:请求头和响应头是两条不同的路径

很多人把“header过滤”理解成一件事,但实际上,请求方向和响应方向的泄露机制完全不同。

请求方向的泄露,源于“该传的没传对,不该传的传了”。 反向代理在转发请求时,需要把客户端真实IP告诉后端,否则后端的日志、限流、审计全部失效。X-Real-IP 和 X-Forwarded-For 就是干这个用的。但如果代理配置不当,把代理自身的内部地址、或者链路中其他内部节点的地址也塞进了这些头,后端一旦把这些值回显或记录到对外可见的地方,内网地址就出去了。

响应方向的泄露,源于“后端吐了什么,代理就转了什么都”。 后端服务可能因为配置错误、调试代码残留、或者框架默认行为,在响应头中携带内部地址。比如 Location 头里写了 http://10.1.1.5/app,Content-Location 里带了 http://172.18.0.3/resource/123,或者自定义的 Server 头里嵌了内网IP。反向代理如果原样透传,这些信息就直达客户端。

两条路径的过滤策略不同:请求方向要控制“传什么给后端”,响应方向要控制“转什么给客户端”。

二、请求方向:只传该传的,不传多余的

2.1 X-Forwarded-For 的正确写法

Nginx 反向代理中,最常见的配置是:

nginx

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

$proxy_add_x_forwarded_for 的行为是:如果客户端请求里已经带了 XFF,就在后面追加 $remote_addr;如果没带,就新建一个。这里有一个容易被忽略的风险:客户端可以伪造 XFF。攻击者发请求时带上 X-Forwarded-For: 10.0.0.1,代理追加后变成 10.0.0.1, 真实客户端IP。后端如果取第一个值,就被欺骗了。

更安全的写法是覆盖而非追加,彻底丢弃客户端带来的 XFF:

nginx

proxy_set_header X-Forwarded-For $remote_addr;

这样后端拿到的永远是代理亲眼看到的客户端地址,没有伪造空间。代价是丢失了多级代理链路信息,但对于单层反向代理的场景,这是安全性与可追溯性之间最干净的取舍。

2.2 清理冗余的转发头

除了 XFF 和 X-Real-IP,一些代理或框架还会注入其他转发类头。这些头如果被后端信任并回显,同样可能携带内网信息:

nginx

proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Port "";

显式置空,避免它们从客户端或上游代理带入不可控的值。

2.3 后端侧的信任边界

代理把客户端IP传对了,后端还得“只在可信来源时才读这个头”。如果后端无条件信任 XFF,攻击者绕过代理直接访问后端(如果后端端口意外暴露),就能用伪造的 XFF 冒充内网IP,绕过基于IP的访问控制。

后端的配置原则是:只信任来自代理服务器地址的转发头。Nginx 层面可以用 set_real_ip_from 限定可信代理,应用层面(如 Gin 的 SetTrustedProxies、Spring 的 server.forward-headers-strategy)也需要做同样的限定。

三、响应方向:代理是最后一道过滤器

3.1 用 proxy_hide_header 清除敏感响应头

Nginx 的 proxy_hide_header 指令用于阻止后端响应中的指定头传递给客户端。这是响应方向最直接、最可靠的过滤手段:

nginx

proxy_hide_header X-Forwarded-For;
proxy_hide_header X-Real-IP;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

Server 头值得单独提。默认情况下 Nginx 会返回类似 Server: nginx/1.18.0 (Ubuntu) 的信息,版本号和操作系统都暴露了。虽然不直接包含IP,但结合其他信息可以辅助攻击者判断环境。用 server_tokens off; 关闭版本号,再用 proxy_hide_header Server 隐藏后端返回的 Server 头。

如果安装了 headers-more-nginx-module,可以用 more_clear_headers 批量清除:

nginx

more_clear_headers 'X-Originating-IP' 'X-Host' 'X-Forwarded-Host';

3.2 Location 头的重写:proxy_redirect

proxy_redirect 是处理重定向头中内网地址的核心工具。后端返回 302 跳转时,如果 Location 里写的是内网地址(如 http://192.168.1.100/login),客户端拿到这个地址是访问不了的,同时内网IP也暴露了。

Nginx 的 proxy_redirect 可以将内网地址重写为对外域名:

nginx

proxy_redirect ~^(http://)(10\.\d+\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+)(:\d+)?(.*)$ https://your-domain.com$4;

这个正则匹配了 RFC 1918 定义的三个私有地址段,把匹配到的内网地址替换为对外的域名。Content-Location 等其他可能携带URL的响应头,同样需要类似处理,或者直接用 proxy_hide_header 移除(如果业务不需要)。

3.3 响应体中的内网地址

proxy_hide_header 只能处理头部,如果内网地址出现在响应体(HTML、JSON)里,需要 sub_filter 做内容替换:

nginx

sub_filter '192.168.1.100' 'your-domain.com';
sub_filter_once off;

sub_filter 有一定性能开销,且对压缩内容需要额外处理。仅在确认响应体可能泄露内网地址时使用。

四、代理自身的信息暴露

除了转发链路上的header,代理服务器本身也可能通过响应头泄露信息。

Via 头:正向代理通常会添加 Via 头标识代理身份。反向代理一般不添加,但如果配置了某些模块或上游代理添加了,需要确认是否必要。如果不希望暴露代理链路的存在,用 proxy_hide_header Via 移除。

X-Cache / X-Cache-Lookup:如果代理开启了缓存,这些头会暴露缓存命中状态和代理的存在。对安全敏感的场景,建议隐藏。

版本信息:除了 Server 头,X-AspNet-Version、X-Powered-By 等头也会暴露技术栈。统一用 proxy_hide_header 清理。

五、完整的 Nginx 配置示例

把上述策略整合到一个 location 块中:

nginx

server {
    listen 443 ssl;
    server_name app.example.com;

    server_tokens off;

    location / {
        proxy_pass http://backend_internal;

        # 请求方向:只传真实客户端IP,覆盖客户端伪造的XFF
        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-Forwarded-Host "";
        proxy_set_header X-Forwarded-Port "";

        # 响应方向:隐藏可能泄露内网信息的头
        proxy_hide_header X-Forwarded-For;
        proxy_hide_header X-Real-IP;
        proxy_hide_header X-Powered-By;
        proxy_hide_header Server;
        proxy_hide_header Via;
        proxy_hide_header X-Cache;
        proxy_hide_header X-Cache-Lookup;

        # 重写Location中的内网地址
        proxy_redirect ~^(http://)(10\.\d+\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+)(:\d+)?(.*)$ https://app.example.com$4;
    }
}

一个容易被忽略的细节:proxy_set_header 在 location 块中会覆盖父级(server 或 http 块)的同名配置,而不是合并。如果父级已经写了 proxy_set_header Host $host,在 location 里再写其他 header 时,Host 不会自动继承,需要重新写一遍。

六、验证:配置写完了不等于生效了

6.1 检查响应头

bash

curl -I https://app.example.com

重点看:Server 头是否还有版本信息,X-Forwarded-For / X-Real-IP 是否还在响应头里,Location 中是否还有内网地址。

6.2 尝试直接访问后端

如果后端端口意外暴露,从外部直接访问应该被拒绝或超时。如果还能直接打开页面,说明网络层隔离没做好,header 过滤再完善也挡不住绕过代理的访问。

6.3 后端日志对照

在后端临时打一条日志,输出 remote_addr、X-Real-IP、X-Forwarded-For 三个值。从外网发一个请求,确认:后端看到的 remote_addr 是代理的内网地址,X-Real-IP 是客户端真实公网IP。如果 X-Real-IP 也是内网地址,说明代理层的 $remote_addr 取值有问题(通常是代理前面还有一层负载均衡或CDN)。

七、合规提醒

本文讨论的是代理转发链路中header过滤的安全配置,属于Web架构安全加固的正常技术范畴。配置的目的是防止内部网络拓扑信息通过HTTP头部泄露给未授权的访问者,属于防御性安全措施。

在实际部署中,需要确保代理和后端服务本身不对外暴露不必要的端口,header过滤是纵深防御中的一层,不能替代网络隔离和访问控制。同时,header过滤策略应当与业务需求对齐:如果业务确实需要向后端传递多级代理链路信息(如CDN + 负载均衡 + 反向代理的多层架构),需要在传递完整性和最小暴露之间做权衡,而不是简单地全部删除。

八、总结

代理转发链路中的内网IP泄露,防护要点可以归结为三条控制线:

方向泄露来源过滤手段
请求方向XFF 携带伪造值或多余链路信息用 $remote_addr 覆盖而非追加,后端只信任代理来源
响应方向后端响应头回显内网地址proxy_hide_header 清除,proxy_redirect 重写 Location
代理自身Server、Via、X-Cache 暴露代理信息server_tokens off,按需隐藏缓存和代理标识

三条线中,响应方向的过滤是最后一道闸门。后端代码是否干净、框架是否有默认回显行为,都不是代理能控制的。代理能做的,是在把响应交给客户端之前,把不该出去的东西拦下来。

记住一个原则:代理应该是一个过滤器,而不是一根管道。 管道什么都过,过滤器只放该过的。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐