跳转到帖子

授权内网渗透:验证 Nginx 转发头信任错误导致的 IP 白名单绕过

精选回复

发布于

背景:管理接口有白名单,不代表权限边界成立

在授权渗透中,经常遇到这样的部署:业务入口经过负载均衡或反向代理,后端 Nginx 根据客户端 IP 限制管理接口,仅允许办公网或运维网访问。配置看起来有 allow 和 deny,但如果真实客户端地址的来源不可信,攻击者仍可能通过请求头改变访问控制所使用的地址。

本文只验证一条具体链路:测试机能访问后端 Nginx,Nginx 错误信任任意来源的 X-Forwarded-For,导致原本不在白名单内的请求获得访问权限。验证使用无敏感数据的标记文件,不访问真实管理功能,不执行状态变更操作。

一、测试范围与成立条件

以下地址均为示例,实际操作应替换为书面授权范围内的地址:

  • 测试机:10.30.0.50,不属于管理白名单。
  • 后端 Nginx:10.30.0.20:8080。
  • 管理白名单:10.20.0.0/16。
  • 正常入口代理:10.30.0.10。
  • 验证资源:/internal/pentest-marker,仅返回固定文本。

漏洞成立通常需要同时满足三个条件:后端入口可达、请求方能够影响被信任的转发头、访问控制依赖被改写后的客户端 IP。如果只能经过会正确覆盖转发头的入口代理访问,下面的直接请求链路未必成立,需要按实际入口分别验证。

二、问题定位:先看地址在哪里被改写

在具备服务器配置审计权限时,先检查生效配置,而不是只看某一个配置文件:

nginx -V 2>&1 | grep -- --with-http_realip_module
nginx -T 2>&1 | grep -nE 'listen|server_name|set_real_ip_from|real_ip_header|real_ip_recursive|allow|deny|access_log'
ss -lntp | grep ':8080'

nginx -T 可能输出内部域名、上游地址等信息,应在本地分析,报告附件需要脱敏。重点检查三件事:谁被允许提供真实客户端地址、从哪个请求头取值、地址改写后又被哪些访问控制规则使用。

典型错误配置如下。此片段仅用于隔离实验环境复现,不应部署到生产:

# http 上下文;需要 ngx_http_realip_module
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

server {
    listen 8080;
    server_name _;

    location = /internal/pentest-marker {
        allow 10.20.0.0/16;
        deny all;
        root /srv/pentest;
        default_type text/plain;
    }
}

问题不在于启用了 X-Forwarded-For,而在于 set_real_ip_from 0.0.0.0/0 把所有连接来源都当成可信代理。若存在同类的全 IPv6 信任配置,也需要一起审计。

三、技术分析:白名单判断的是谁

Nginx realip 模块会根据可信代理配置,用请求头中的地址替换客户端地址。后续 IP 访问控制使用的便可能是替换后的 $remote_addr,而不是实际建立连接的对端。

  • $remote_addr:启用 realip 后,可能已经被改写的客户端地址。
  • $realip_remote_addr:realip 改写前的连接对端地址,适合辅助还原代理链路。
  • $http_x_forwarded_for:收到的原始转发头,只能作为取证线索,不能天然视为可信身份。

因此,攻击者不需要真的获得一个管理网地址。只要后端错误接受其提交的地址,白名单检查就可能放行。此处验证的是网络访问控制边界绕过,不等同于已经获得管理员身份;如果后端还有独立认证,仍需单独评估。

四、操作步骤:用无害资源建立对照

由系统维护人员在测试环境准备固定文件,确认 Nginx 工作进程具有读取权限:

sudo install -d -m 0755 /srv/pentest/internal
printf 'authorized-pentest-marker\n' | sudo tee /srv/pentest/internal/pentest-marker > /dev/null
sudo chmod 0644 /srv/pentest/internal/pentest-marker
sudo nginx -t && sudo nginx -s reload

这里使用静态文件,让请求实际经过访问控制阶段。不要用 return 200 代替该资源:return 在重写阶段就可能结束请求,导致验证无法证明 allow/deny 是否生效。

从白名单外的授权测试机发起三个对照请求。禁用客户端环境代理,避免代理设置影响测试路径:

# 1. 基线:预期 403
curl --noproxy '*' --connect-timeout 3 --max-time 5 -i \
  

# 2. 声称自己来自白名单:错误配置下可能返回 200 和固定标记
curl --noproxy '*' --connect-timeout 3 --max-time 5 -i \
  -H 'X-Forwarded-For: 10.20.0.9' \
  

# 3. 声称自己来自非白名单:预期仍为 403
curl --noproxy '*' --connect-timeout 3 --max-time 5 -i \
  -H 'X-Forwarded-For: 10.40.0.9' \
   403 → 200 → 403,且第二次响应正文与标记文件一致,就得到了一组有区分度的证据。但还需要结合服务器日志,排除命中不同虚拟主机、缓存响应或其他访问规则的可能。实际环境使用域名虚拟主机时,应保持正确的 Host;HTTPS 场景还要保持正确的 SNI。

五、日志留痕:同时记录改写前后地址

建议临时增加专用日志。下面的 log_format 放在 http 上下文,access_log 放在被测 server 中:

log_format ip_boundary escape=json
    '{"time":"$time_iso8601",'
    '"peer":"$realip_remote_addr",'
    '"client":"$remote_addr",'
    '"xff":"$http_x_forwarded_for",'
    '"host":"$host",'
    '"uri":"$uri",'
    '"status":$status}';

# 放入被测 server
access_log /var/log/nginx/ip-boundary.log ip_boundary;

复现成功时,第二次请求通常可观察到:peer 为测试机地址 10.30.0.50,client 却变成 10.20.0.9,响应状态为 200。这比仅截取一张成功响应更能说明问题根因。

如果请求经过代理,peer 应当是最后一跳代理地址。应把入口访问日志、后端日志和测试时间对应起来,不能把最后一跳代理误认成原始请求方。

六、修复建议:收窄信任来源,而不是过滤某个头值

  • 只信任实际受控代理的地址,不要信任整个互联网,也不要因为方便而信任全部内网。
  • 通过安全组、防火墙或网络策略限制后端监听端口,只允许入口代理与必要的运维来源访问。
  • 明确转发头的生成规则:入口负责建立可信地址,内部节点只接受来自受控上游的声明。
  • 管理功能保留独立身份认证与授权,IP 白名单不应是唯一安全边界。

在本文的一跳代理拓扑中,后端可以改为:

# 后端 Nginx:替换原来的宽泛信任配置,不要仅追加
set_real_ip_from 10.30.0.10/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 入口代理的相关 location:
# 适用于该入口直接接收客户端连接、且其 remote_addr 来源可信的情况
proxy_set_header X-Forwarded-For $remote_addr;

不能简单把入口规则机械套用到 CDN、多级负载均衡或服务网格环境。多跳场景需要逐跳确认可信代理地址、头部追加或覆盖策略,以及递归解析结果。real_ip_recursive on 本身不是修复措施,信任范围错误时依然会出问题。

七、复测与结论

修复后,从白名单外测试机重复上述三个请求,应全部被拒绝,或因后端网络隔离而无法直连。同时,经合法代理进入、来自真实允许来源的访问仍应正常。日志应证明:不可信直连请求提交的转发头不再改变用于访问控制的客户端地址。

最终报告需要区分“已验证事实”和“潜在影响”:已验证的是指定入口上的 IP 白名单绕过;真实管理接口是否受影响,要根据其路由、认证及授权配置单独确认,不应通过读取敏感信息来扩大证明范围。完成后删除临时标记文件与测试配置,按约定保留脱敏证据。

这类问题的核心不是请求头是否容易伪造,而是系统是否让不可信请求头参与了权限决策。把连接来源、代理信任和应用授权分开检查,才能准确定位边界,也方便修复后逐项验收。

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

建议先确认白名单到底由 Nginx 的 allow/deny 执行,还是由后端读取转发头判断。仅发送 X-Forwarded-For 不会自动影响 Nginx 白名单;需要关注 RealIP 配置是否把不可信请求头转换成了 $remote_addr。

在授权测试入口上,用同一路径做对照,示例地址替换为实际测试值:

# 基线:测试源 IP 不在白名单内
curl -sk -D - -o /dev/null 

# 仅修改转发头,填入已知的测试白名单 IP
curl -sk -D - -o /dev/null \
  -H 'X-Forwarded-For: 192.0.2.10' \
  

# 仅在配置或代码确实使用 X-Real-IP 时测试
curl -sk -D - -o /dev/null \
  -H 'X-Real-IP: 192.0.2.10' \
  

不要只凭 403 变成 200 下结论,还要核对响应是否确实来自受保护功能,排除登录页、错误页和缓存响应。建议临时增加诊断日志,在同一次请求里记录连接源、改写后的地址和收到的头:

# 放在 http 上下文;需要已编译 RealIP 模块
log_format ipcheck '$request_id peer=$realip_remote_addr '
                   'effective=$remote_addr xff="$http_x_forwarded_for" '
                   'xri="$http_x_real_ip" status=$status';
access_log /var/log/nginx/ipcheck.log ipcheck;

再通过 nginx -T 检查实际加载配置,重点看 set_real_ip_from 是否包含 0.0.0.0/0、过大的内网网段,以及 real_ip_header、real_ip_recursive 在目标 location 的生效情况。若修改头后 effective 变成白名单 IP,且该 location 使用 allow/deny,证据链就比较清楚;若地址没变但访问放行,应继续查后端是否直接信任了转发头。

修复核心不是简单关闭某个头,而是收紧信任边界:

# 示例:仅信任实际受控的前置代理
set_real_ip_from 10.20.30.10/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

同时确保该代理正确处理客户端传入的转发头,源站入口仅允许预期代理访问。real_ip_recursive on 本身不是安全开关;可信网段配得过宽,仍可能接受伪造地址。修复后应同时回归“经代理访问”和“直连源站”两条路径。

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

最近浏览 0

  • 没有会员查看此页面。

SiteMap HACKHAT © 2026 All rights reserved.