跳转到帖子

授权内网测试中,如何定位 X-Forwarded-User 信任边界导致的认证绕过?

精选回复

发布于

背景:这个问题通常出现在哪里在授权内网渗透测试中,经常遇到这样的架构:用户先经过 Nginx、Ingress 或统一身份代理完成 SSO,代理再把认证结果写入 X-Forwarded-User、X-Remote-User 等请求头,后端应用据此识别身份。测试时如果发现直接添加该请求头就能进入受保护页面,不能立即认定为应用漏洞,需要先回答两个问题:客户端请求头是否被边界代理覆盖;后端端口是否只能由可信代理访问。本文只讨论已取得书面授权的测试环境。验证应使用自有低权限测试账号和无副作用接口,不读取他人数据,不修改业务状态。问题定位:先区分三类现象现象可能原因优先检查经过代理时伪造头无效代理正确覆盖客户端输入继续检查后端是否可被直连经过代理时伪造头有效代理透传了外部身份头Nginx 或 Ingress 的 header 配置仅直连后端时伪造头有效应用把网络边界当成认证边界安全组、防火墙、Service 暴露方式关键点是分别测试“经过入口代理”和“直达后端”两条路径,并确保除目标地址外,Host、路径、方法和测试账号保持一致。否则可能把虚拟主机差异、缓存或会话 Cookie 误判为认证绕过。技术分析:问题不在请求头本身,而在来源不可证明HTTP 请求头由客户端控制。只有当入口代理先完成强认证、删除客户端提交的同名头,再写入由代理生成的身份值,并且后端只接受该代理的连接时,这类头才能成为可信身份声明。任一条件缺失,信任链都会断裂。入口未覆盖:客户端提交的 X-Forwarded-User 被原样转发。后端可直连:攻击者绕过入口,直接向应用提交身份头。多级代理不一致:外层清理了头,内层又从错误变量恢复。应用存在回退逻辑:认证信息缺失时,回退到请求头、查询参数或默认管理员。日志字段混淆:日志只记录最终头值,无法判断它来自客户端还是认证代理。操作步骤:建立可复现、低影响的验证链路第一步,准备两个受控账号:普通账号 user-test 和具备一个可识别但无破坏性权限的验证账号 reviewer-test。选择 /api/me、/profile 或只读健康检查扩展接口作为验证目标。不要使用删除、转账、发布等有副作用的接口。第二步,记录正常访问基线。保留状态码、响应长度、用户标识、Set-Cookie、Location 和请求 ID。第三步,在不携带登录 Cookie 的情况下提交伪造身份头。第四步,如果授权范围允许且后端地址已由资产方确认,再从指定测试机直连后端复测。测试完成后应删除临时放行规则。# 1. 经过正式入口,不带会话,建立未认证基线
curl -skD /tmp/base.hdr \
-o /tmp/base.body \
-H 'Host: app.lab.example' \
'

# 2. 经过正式入口提交受控测试账号标识
curl -skD /tmp/proxy-test.hdr \
-o /tmp/proxy-test.body \
-H 'Host: app.lab.example' \
-H 'X-Forwarded-User: reviewer-test' \
'

# 3. 仅在明确授权且后端地址经确认时,绕过入口进行对照
curl -sD /tmp/direct-test.hdr \
-o /tmp/direct-test.body \
-H 'Host: app.lab.example' \
-H 'X-Forwarded-User: reviewer-test' \
'

# 4. 对比响应;重点看状态码、身份字段、跳转与 Cookie
diff -u /tmp/base.hdr /tmp/proxy-test.hdr
diff -u /tmp/base.body /tmp/proxy-test.body
sha256sum /tmp/base.body /tmp/proxy-test.body /tmp/direct-test.body判断成功不能只看 HTTP 200。很多应用会对未登录用户返回 200 和登录页。至少应同时满足:响应中的主体身份变成 reviewer-test;未使用该账号的有效 Cookie 或令牌;服务端日志显示请求确实命中目标处理器;重复请求结果稳定;不存在缓存命中造成的假象。日志排查:把客户端输入和认证结果分开记录排查时应关联入口、认证服务和应用三类日志。建议统一使用 request_id,并分别记录客户端提交的原始身份头、认证服务返回的身份、最终转发值和上游地址。不要把这几个字段都命名为 user。# Nginx 示例:保留客户端输入用于审计,但绝不能将其作为认证结果
log_format auth_boundary '$time_iso8601 request_id=$request_id '
'src=$remote_addr host=$host uri=$request_uri status=$status '
'client_claim=$http_x_forwarded_user '
'verified_user=$auth_user upstream=$upstream_addr';

access_log /var/log/nginx/auth_boundary.log auth_boundary;

# 按请求 ID 联查;示例值应替换为本次测试响应中的 ID
grep -F 'request_id=7f2c-test-001' /var/log/nginx/auth_boundary.log
grep -R -F '7f2c-test-001' /var/log/app/ /var/log/auth-service/如果 client_claim=reviewer-test,而 verified_user 为空,应用却仍将请求识别为 reviewer-test,问题大概率位于代理转发配置或应用回退逻辑。如果入口日志根本不存在对应 request_id,但应用日志存在,则应优先检查后端端口暴露和旁路访问。关键配置:正确覆盖身份头下面是使用 auth_request 的简化示例。核心不是隐藏头名称,而是始终以认证子请求的结果覆盖客户端输入。认证失败时必须直接拒绝,不能把空身份交给应用自行猜测。location / {
auth_request /_auth;
auth_request_set $auth_user $upstream_http_x_authenticated_user;

# 无论客户端是否提交同名头,都由可信认证结果覆盖
proxy_set_header X-Forwarded-User $auth_user;
proxy_set_header X-Request-ID $request_id;
proxy_set_header Host $host;
proxy_pass /> }

location = /_auth {
internal;
proxy_pass /> proxy_pass_request_body off;
proxy_set_header Content-Length '';
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Cookie $http_cookie;
}还应在网络层限制 app_backend 仅接受代理网段。Kubernetes 环境要检查 Service 类型、Ingress、NetworkPolicy 和节点端口;传统内网要检查安全组、主机防火墙、负载均衡后端监听地址。只修代理、不封直连端口,仍然保留旁路。常见误判与进一步检查缓存误判:响应来自共享缓存。验证时检查 Age、Via、X-Cache,并对测试路径使用唯一查询标记,但不要对业务写接口这样做。Cookie 残留:curl 使用了 cookie-jar,或浏览器自动携带会话。应使用全新会话并显式检查请求。Host 不一致:直连 IP 时后端进入默认虚拟主机。必须保留原 Host,并确认 TLS SNI 是否一致。大小写误判:HTTP 头名称大小写不敏感,不要把仅改变大小写视为绕过。多值头差异:不同代理对重复头可能取首值、末值或拼接。只在授权测试窗口验证,并结合代理实际解析日志,不要批量发送畸形请求。风险分级与修复建议如果外部或普通办公网用户可经过正式入口伪造管理员身份,通常应按高风险认证绕过处理。如果仅能从受严格隔离的代理网段直连触发,仍属于权限边界缺陷,但评级应结合网络可达性、可获取身份标识的难度以及可执行操作确定。入口层:删除并覆盖所有客户端可控的身份头,认证失败默认拒绝。网络层:后端仅允许可信代理访问;优先使用双向 TLS 或服务身份,而不是只依赖源 IP。应用层:不提供从任意请求头恢复登录态的回退逻辑;对高权限操作再次校验服务端会话或签名令牌。令牌层:如必须跨多级代理传递身份,使用短时、限定受众和签名的内部令牌,并校验 iss、aud、exp、nbf。审计层:区分原始客户端声明与已验证身份,统一 request_id,避免在日志中记录完整会话令牌。回归测试:同时覆盖入口伪造、后端直连、缺失认证结果、重复身份头和代理重启后的配置一致性。总结这类问题的验证重点不是证明“某个头可以改”,而是还原完整信任链:谁完成认证、谁写入身份、后端如何确认来源、旁路是否存在、日志能否证明每一步。报告中应给出两条路径的对照请求、关联请求 ID、网络可达性证据和最小化修复方案。这样既能避免把正常代理行为误报为漏洞,也能准确定位应该修代理、网络边界还是应用认证逻辑。

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

这类问题不要先盯着应用里的“当前用户”,而是先把请求经过的每一跳画出来:客户端 → 入口网关/WAF → 反向代理 → 认证代理 → 应用。关键是确认哪一跳有权生成 X-Forwarded-User,以及其他入口是否能绕过这一跳直达后端。

建议先做三组差分请求,并同时对照各层访问日志:

# 不带身份头
curl -isk 

# 从外部主动注入身份头
curl -isk  \
  -H 'X-Forwarded-User: test-admin'

# 测试重复头及不同规范化形式
curl -isk  \
  -H 'X-Forwarded-User: low-user' \
  -H 'X-Forwarded-User: test-admin'
curl -isk  \
  -H 'X_Forwarded_User: test-admin'

如果第二组请求改变了身份或响应权限,继续区分是入口未清洗,还是存在旁路入口。可在授权范围内分别访问负载均衡地址、Ingress 地址和后端服务地址;后端应只允许认证代理所在网段访问,不能把“内网来源”本身当作已认证。排查时记录响应状态、用户标识、上游节点和 request ID,不要只看页面差异。

安全的代理行为应是:先无条件删除客户端传入值,再用认证结果覆盖,而不是“没有时才补”。例如 Nginx:

location / {
    auth_request /auth;

    # 从认证子请求读取可信身份
    auth_request_set $verified_user $upstream_http_x_authenticated_user;

    # set_header 会覆盖传给上游的同名字段
    proxy_set_header X-Forwarded-User $verified_user;
    proxy_set_header X-Original-X-Forwarded-User "";
    proxy_pass 
}

还要检查应用服务器如何处理重复头。不同组件可能采用首值、末值或逗号拼接,代理与应用解析不一致时容易产生绕过。应用侧最好拒绝多值,并验证请求确实来自指定认证代理;更稳妥的是使用代理与后端之间的 mTLS,或传递带签名、短时效且包含 audience 的身份令牌,而不是仅凭裸用户名头:

def trusted_user(request):
    values = request.headers.getlist("X-Forwarded-User")
    if len(values) != 1 or "," in values[0]:
        abort(400)
    if not request.environ.get("mtls_verified_proxy"):
        abort(401)
    return values[0]

日志上建议在入口记录“收到但已丢弃的身份头是否存在”,在认证代理记录认证主体,在应用记录最终主体和代理身份。不要直接记录完整会话令牌。最终修复标准应是:任意客户端注入、重复头、大小写/下划线变体及旁路访问,都不能改变应用识别出的用户;未经过认证代理的请求应在网络层或应用层直接失败。

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

最近浏览 0

  • 没有会员查看此页面。