跳转到帖子

渗透攻防实战:反向代理客户端 IP 信任误配的漏洞验证、权限边界与日志留痕

精选回复

发布于

背景

在一次授权 Web 渗透测试中,目标系统通过公网负载均衡、Nginx 反向代理和后端应用组成访问链路。运维接口 /ops/health/detail 未配置独立身份认证,仅根据客户端 IP 判断是否来自内网。正常公网请求返回 403,但修改 X-Forwarded-For 后可以访问接口。

这类问题的关键不是“能否伪造一个请求头”,而是代理链与应用对客户端地址的认定不一致:边界代理保留了用户可控的转发头,后端又直接使用该头部做权限判断,最终导致网络来源被当成身份凭据。

以下操作应限定在明确授权的目标、时间窗口和测试账号内。验证以状态码、固定测试字段和权限查询为主,不下载业务数据,不进行口令喷洒或横向扩散。

问题定位

先确认访问链路和预期控制点。常见链路如下:

Internet Client -> Load Balancer -> Nginx -> Application -> PostgreSQL

测试前需要向系统负责人确认三项信息:

  • 真正负责鉴权的是负载均衡、Nginx 还是应用自身。
  • 哪些代理网段有权写入 X-Forwarded-For 或 Forwarded。
  • 运维接口预期返回 401、403 还是只能在独立管理网络中访问。

先发送基线请求,并加入唯一评估标记,便于后续从访问日志、WAF 日志和应用日志中准确关联:

export TARGET='
export TRACE='PT-20250308-001'

curl -sk -o /dev/null \
  -D - \
  -H "X-Assessment-ID: ${TRACE}" \
  "${TARGET}/ops/health/detail"

若基线响应为 403,再分别验证后端是否信任用户提交的客户端地址。不要并发扫描,只测试已确认的接口:

curl -sk -o /dev/null -D - \
  -H "X-Assessment-ID: ${TRACE}" \
  -H 'X-Forwarded-For: 127.0.0.1' \
  "${TARGET}/ops/health/detail"

curl -sk -o /dev/null -D - \
  -H "X-Assessment-ID: ${TRACE}" \
  -H 'X-Forwarded-For: 10.10.20.15' \
  "${TARGET}/ops/health/detail"

curl -sk -o /dev/null -D - \
  -H "X-Assessment-ID: ${TRACE}" \
  -H 'Forwarded: for=127.0.0.1;proto=https' \
  "${TARGET}/ops/health/detail"

如果不带头部时返回 403,而加入伪造头部后返回 200,且响应内容确实来自受限接口,即可证明存在信任边界绕过。仅出现状态码变化还不够,需要排除 CDN 缓存、自定义错误页和重定向造成的误判。建议同时比对 Content-Type、响应长度、固定字段和请求 ID。

技术分析

问题通常由以下配置组合造成:

  • Nginx 使用 $proxy_add_x_forwarded_for,将外部请求原有的 X-Forwarded-For 继续传给后端。
  • 应用框架无条件启用代理信任,例如信任所有代理或直接取转发链最左侧地址。
  • 应用以 127.0.0.1、RFC1918 私网地址或固定办公网段作为管理权限依据。
  • 运维接口没有独立认证、角色校验和二次访问控制。

例如,客户端提交:

X-Forwarded-For: 127.0.0.1

Nginx 再将真实连接地址追加到末尾,后端最终收到:

X-Forwarded-For: 127.0.0.1, 198.51.100.24

若应用取第一个地址,就会把公网访问者识别为本机。反过来,盲目取最后一个地址也不可靠,因为多层负载均衡环境中最后一个地址可能只是边界代理。正确做法是建立明确的受信代理列表,并从靠近应用的一侧逐跳剥离受信代理,而不是固定取某个位置。

代理与应用配置检查

在服务器侧检查 Nginx 是否直接透传了用户可控头部:

nginx -T 2>&1 | grep -nE \
  'real_ip_header|set_real_ip_from|real_ip_recursive|proxy_set_header.*(X-Forwarded-For|Forwarded)'

需要重点关注以下高风险配置:

# 风险示例:未限制可信代理,同时保留客户端提交的头部
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 风险示例:将任意来源都视为可信代理
set_real_ip_from 0.0.0.0/0;

如果架构为“固定负载均衡到 Nginx,再到应用”,可由 Nginx 仅信任负载均衡网段,并覆盖传给应用的客户端地址。下面配置中的网段必须替换为实际负载均衡地址:

set_real_ip_from 10.20.0.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

location / {
    proxy_set_header Host $host;
    proxy_set_header X-Request-ID $request_id;

    # 使用经过可信代理校验后的地址覆盖外部输入
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;

    proxy_pass 
}

若 Nginx 本身直接暴露公网,不应把任意客户端提交的转发头视为可信信息。若前方还有 CDN,需要只允许 CDN 回源网段访问 Nginx,并按供应商定义的真实客户端地址头进行配置;不能同时接受多个含义重叠的来源头。

应用层也不能仅依赖 IP。以 Express 为例,以下配置风险较高:

// 风险:信任任意代理来源
app.set('trust proxy', true);

应限制为实际代理网段,并对管理接口继续执行会话或令牌认证:

const proxyaddr = require('proxy-addr');
const trustProxy = proxyaddr.compile([
  'loopback',
  '10.20.0.0/24'
]);

app.set('trust proxy', (ip) => trustProxy(ip));

app.use('/ops', requireAuthenticatedUser);
app.use('/ops', requireRole('operations'));

权限边界验证

运维接口可能泄露数据库地址、消息队列名称、环境变量或内部服务 URL。验证时不应继续使用这些信息扩大战果,而应采用最小化方式确认影响范围。例如由系统负责人在受控终端使用应用服务账号执行 PostgreSQL 权限查询,只检查权限,不读取业务表内容:

SELECT current_user, session_user;
SELECT has_database_privilege(
  current_user,
  current_database(),
  'CREATE'
) AS can_create_in_database;

SELECT has_table_privilege(
  current_user,
  'public.orders',
  'DELETE'
) AS can_delete_orders;

SELECT has_table_privilege(
  current_user,
  'public.orders',
  'SELECT'
) AS can_read_orders;

如果应用账号同时具备建库、建表、修改角色或删除核心业务表的权限,即使入口漏洞只暴露诊断信息,也会显著放大后续风险。数据库账号应按业务功能拆分,迁移账号与运行账号分离,运行账号不应拥有 CREATEDB、CREATEROLE 或非必要的 DDL 权限。

日志留痕与排查

只记录最终客户端地址会丢失证据。建议在边界代理同时记录 TCP 对端地址、原始转发头、规范化后的客户端地址、请求 ID、上游地址和响应状态。测试阶段可使用如下 Nginx 日志格式:

log_format security_json escape=json
  '{'
  '"time":"$time_iso8601",'
  '"request_id":"$request_id",'
  '"peer_ip":"$realip_remote_addr",'
  '"normalized_ip":"$remote_addr",'
  '"xff_raw":"$http_x_forwarded_for",'
  '"method":"$request_method",'
  '"uri":"$request_uri",'
  '"status":$status,'
  '"upstream":"$upstream_addr",'
  '"upstream_status":"$upstream_status"'
  '}';

access_log /var/log/nginx/security_access.log security_json;

完成验证后,可以按评估标记和接口路径检索:

grep -F 'PT-20250308-001' /var/log/nginx/security_access.log
zgrep -hF '/ops/health/detail' /var/log/nginx/security_access.log*

journalctl -u application.service \
  --since '2025-03-08 10:00:00' \
  --until '2025-03-08 10:30:00' \
  | grep -E 'PT-20250308-001|health/detail'

排查历史利用痕迹时,可关注以下特征:

  • 公网对端地址对应的原始转发头中出现 127.0.0.1、私网地址或异常多级地址链。
  • 受限接口短时间内由 403 变为 200,但会话身份为空。
  • 同一请求 ID 在代理日志中显示公网来源,在应用日志中却被识别为本机或内网。
  • 诊断接口访问后紧接着出现配置导出、凭据校验或数据库连接异常。
  • 同一来源轮换提交 X-Forwarded-For、X-Real-IP、Forwarded 等多个头部。

修复与复测建议

  • 管理接口必须实施独立身份认证和角色授权,IP 白名单只能作为附加条件。
  • 边界层清除外部提交的客户端地址头,只由确定的代理节点重新写入。
  • 通过安全组或防火墙限制回源端口,仅允许负载均衡或 CDN 网段访问。
  • 应用框架的代理信任范围应配置为明确网段,避免使用“信任全部代理”。
  • 关闭生产环境的详细健康检查、调试页、环境变量输出和堆栈回显。
  • 轮换已经通过诊断接口暴露的数据库、缓存、消息队列和第三方服务凭据。
  • 将应用运行账号调整为最小权限,迁移操作使用独立的短期账号。
  • 日志中同时保留原始值和规范化值,并使用统一请求 ID 串联网关、应用与数据库审计记录。

修复后应至少完成四组复测:无转发头请求仍返回 401 或 403;伪造本机地址不能改变权限结果;经合法负载均衡访问时业务客户端地址记录正确;具备合法登录态但角色不足的账号仍不能访问运维接口。

总结

客户端 IP 信任误配表面上是一个请求头问题,实际涉及代理拓扑、身份认证、应用权限和日志证据链。渗透验证应停留在证明边界可绕过和确认最小影响范围,不需要读取真实业务数据。修复重点也不只是过滤某个头部,而是明确哪些节点有权声明客户端地址,并确保敏感接口始终使用可审计的身份和角色授权。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意
红队攻防路径编排示意图
侦察、入口、横向移动与权限路径梳理示意

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

最近浏览 0

  • 没有会员查看此页面。