跳转到帖子

渗透攻防实战:反向代理路径归一化导致的后台权限边界绕过验证与修复

精选回复

发布于

背景在授权渗透测试中,常见的一类权限边界问题并不来自业务代码本身,而是来自 Nginx、网关与后端框架对 URL 路径的解析差异。例如,网关只允许管理员访问 /admin/,但后端会将重复斜杠、点段或编码后的分隔符归一化。攻击请求如果在两层之间得到不同解释,就可能绕过入口层限制,触达后台接口。本文以“外部 Nginx 反向代理 + 内部应用”的授权测试场景为例,说明如何确认解析差异、验证权限边界、关联日志并给出修复方案。测试应限定在书面授权的域名、账号和时间窗口内;验证以读取无敏感内容的测试接口为止,不进行数据修改。问题定位测试目标存在两个接口:普通用户可访问 /api/profile,管理员接口位于 /admin/api/status。入口代理使用路径前缀执行访问控制:location /admin/ {\n auth_request /_auth_admin;\n proxy_pass / {\n proxy_pass 的 location 匹配结果、proxy_pass 是否携带原始 URI,以及后端框架的路径归一化行为可能不一致。排查时不要直接批量扫描,先围绕一个无副作用的状态接口做小样本对照。技术分析需要区分三个值:客户端发送的原始请求目标、代理用于路由匹配的规范化 URI、后端最终看到的路径。以下情况值得重点核对:重复斜杠://admin/api/status、/admin//api/status。点段:/public/../admin/api/status。不同组件可能在鉴权前或鉴权后消解 ..。编码字符:%2f、%5c、%2e。需要确认在哪一层解码以及是否发生二次解码。路径参数:/public;v=1/../admin/api/status。部分 Java 容器或框架会移除分号参数后重新路由。代理重写:带 URI 与不带 URI 的 proxy_pass 语义不同,错误的尾斜杠会改变转发路径。判断标准不是“某个变体返回 200”这么简单。应同时比较响应体特征、后端访问日志、认证服务日志和请求 ID,确认请求确实到达了受保护处理器,而不是命中缓存、统一错误页或前端路由。操作步骤第一步,建立基线。分别以未登录、普通用户和管理员身份请求标准路径,记录状态码、响应长度、内容类型、重定向位置与请求 ID。普通用户访问管理员接口应稳定返回 401 或 403。第二步,在低频率条件下测试有限的路径变体。建议使用专门的测试账号,并通过唯一请求头关联日志。不要使用会新增、删除或更新数据的接口。TARGET=' path in \\\n '/admin/api/status' \\\n '//admin/api/status' \\\n '/admin//api/status' \\\n '/public/../admin/api/status' \\\n '/public/%2e%2e/admin/api/status'\ndo\n echo \"=== $path ===\"\n curl --path-as-is -sS -o /tmp/body.out -D /tmp/head.out \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"X-Pentest-ID: $RID\" \\\n -H 'Cache-Control: no-cache' \\\n \"$TARGET$path\"\n awk 'NR==1 || tolower($1)==\"content-type:\" || tolower($1)==\"location:\" || tolower($1)==\"x-request-id:\"' /tmp/head.out\n wc -c /tmp/body.out\ndone--path-as-is 很关键,否则 curl 可能在发送前自行消解点段,导致测试请求与预期不一致。命令只记录响应长度,不直接输出可能包含敏感数据的响应正文。第三步,确认代理层实际接收和转发的 URI。可以在测试环境临时增加独立日志格式,同时记录 $request_uri 和 $uri:log_format path_audit '$time_iso8601 rid=$http_x_pentest_id '
'status=$status request_uri=\"$request_uri\" '
'uri=\"$uri\" upstream=$upstream_addr '
'upstream_status=$upstream_status';
access_log /var/log/nginx/path_audit.log path_audit;其中 $request_uri 通常保留原始参数与编码形式,$uri 是 Nginx 当前处理的规范化 URI。两者出现差异并不必然代表漏洞,但如果访问控制基于一个值、上游路由基于另一个值,就需要继续验证。第四步,在后端记录框架解析后的路由模板和原始路径。日志中避免记录 Authorization、Cookie 以及完整查询参数。以常见 Java 服务为例,应关注容器访问日志中的 request target、应用过滤器看到的 request URI,以及最终匹配的 controller;Node.js 服务则应对比 req.originalUrl、req.url 和路由模板。第五步,形成证据链。一个可确认的问题至少应满足:普通用户访问规范路径被拒绝;某个变体得到成功响应;代理日志显示该请求未进入管理员鉴权分支;后端日志显示其最终命中管理员处理器;响应不是缓存结果,并且请求 ID 在各层可关联。日志排查示例# 代理层按测试标识定位,避免在大量日志中模糊检索
grep -F 'rid=pt-path-20250308-001' /var/log/nginx/path_audit.log

# systemd 管理的后端服务按时间窗与请求 ID 关联
journalctl -u app-backend \\
--since '2025-03-08 14:00:00' \\
--until '2025-03-08 14:10:00' \\
--no-pager | grep -F 'pt-path-20250308-001'

# 检查同一请求是否出现异常的 2xx 上游响应
awk '/rid=pt-path-20250308-001/ && /upstream_status=2[0-9][0-9]/' \\
/var/log/nginx/path_audit.log关键配置修复优先原则是:在统一完成一次严格规范化之后再鉴权和路由,并让所有层使用同一份规范化结果。不要依赖零散正则表达式封堵已知变体。将管理员权限校验下沉到应用或统一鉴权中间件,按后端实际匹配的路由执行授权。入口层限制可作为附加防线,但不应成为唯一控制。拒绝包含编码斜杠、反斜杠、NUL、非法 UTF-8、重复解码结果或越界点段的请求。拒绝优于“猜测式修正”。统一代理配置,审计 proxy_pass 尾斜杠和 rewrite 规则,避免同一服务存在多套路径拼接语义。关闭不必要的绝对 URI、路径参数和编码分隔符支持;同步检查 CDN、WAF、API 网关与应用容器。修复后将全部变体加入回归测试,确保未登录和普通用户始终得到 401/403,管理员请求仍正常。可在 Nginx 入口先拒绝明显异常形式,但该配置只是纵深防御,不能替代后端授权:# 示例需先在测试环境验证,避免影响合法业务路径
if ($request_uri ~* '(?:%2f|%5c|%00|\\\\|//)') {
return 400;
}

# 管理接口仍需在上游应用内执行角色校验
location ^~ /admin/ {
auth_request /_auth_admin;
proxy_pass /> }如果业务允许用户提交 URL 或文件路径,还要避免把这类入口规则误当成通用输入校验。URL 路由、文件系统路径和业务参数的规范化规则不同,应在各自信任边界内单独处理。风险评估与复测风险等级取决于被绕过接口的能力。只读健康状态通常影响较低;若可读取用户数据、导出配置、修改权限或触发后台任务,则应按未授权访问或权限提升评估。报告中应提供最小化请求、三层日志证据、受影响路由范围及前置账号权限,不应附带无关敏感数据。复测时建议建立一张矩阵,覆盖未登录、普通用户、管理员三类身份,以及标准路径、重复斜杠、点段、单次编码和混合编码五类输入。除检查状态码外,还要验证后台处理器未执行、审计日志完整、告警能够命中。对于写接口,可在隔离环境使用虚拟数据,并检查事务是否确实未提交。总结反向代理路径绕过的核心不是某个固定 payload,而是组件间解析不一致。授权渗透测试中,应从基线权限入手,用少量无副作用请求确认差异,再通过原始 URI、规范化 URI、后端路由和认证决策构建完整证据链。修复重点是统一规范化语义,并在最终业务路由上强制执行授权,而不是持续追加正则黑名单。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意
代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

这类问题建议先按“代理实际转发的 URI”和“后端鉴权看到的 URI”做差分验证,不要只测一个 ../。常见分歧点还包括重复斜杠、百分号编码、二次编码、反斜杠、分号参数,以及 Nginx proxy_pass 是否带尾斜杠。

可以先固定同一会话,批量比较状态码、响应长度和后端访问日志:

for p in \
  '/admin/users' \
  '/public/../admin/users' \
  '/public/%2e%2e/admin/users' \
  '/public/%252e%252e/admin/users' \
  '//admin//users' \
  '/public/..;/admin/users'
do
  curl --path-as-is -sk -o /dev/null \
    -w '%{http_code} %{size_download} %{url_effective}\n' \
    "
done

--path-as-is 很关键,否则 curl 自己会先消掉点段,测不到代理边界。排查时同时在代理层记录 $request_uri、$uri 和上游地址;前者接近客户端原始输入,后者通常已经归一化。若两者对应的权限规则不同,基本就能定位到边界错位。

log_format uri_diff '$remote_addr "$request" '
                    'request_uri="$request_uri" uri="$uri" '
                    'upstream="$upstream_addr" status=$status';
access_log /var/log/nginx/uri_diff.log uri_diff;

修复上,不建议依赖某个正则把所有畸形路径都拦完。更稳妥的是:后台入口使用独立、归一化后的 location 做鉴权,并保持 proxy_pass 的 URI 替换语义明确;后端再对管理接口执行一次真实权限校验。

merge_slashes on;

location ^~ /admin/ {
    auth_request /_auth;
    proxy_pass 
}

location / {
    proxy_pass 
}

还要重点检查类似 location /public/ { proxy_pass } 的配置:proxy_pass 带 URI 时会进行前缀替换,和不带尾斜杠的行为不同,容易让安全评审误判上游最终收到的路径。改完后建议把上述变体加入回归用例,并以“未授权请求在代理层和应用层都不能到达管理处理器”为验收标准,而不只是看返回 403。

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

最近浏览 0

  • 没有会员查看此页面。