跳转到帖子

咖啡馆实战:网站 Nginx 反向代理中的鉴权绕过、日志定位与边界修复

精选回复

发布于

背景

网站迁移到 Nginx 反向代理后,常见架构是公网 Nginx 负责 TLS、访问控制和静态资源,后端应用监听 127.0.0.1 或内网地址。一次授权测试中,管理接口 /admin/ 已配置统一鉴权,但测试发现访问 /api/admin/、大小写变体或经特定代理头转发后,仍可能绕过入口限制。

这类问题通常不是应用本身存在认证漏洞,而是 Nginx 的 location 匹配、URI 重写、可信代理配置与后端路由边界不一致。它尤其适合在网站建设和服务器运维阶段提前检查。

以下操作应仅用于自有网站或明确授权的测试环境。验证目标是确认权限边界,不应读取、修改真实业务数据。

问题定位

先明确请求经过的链路:CDN 或加速器、边缘 Nginx、站点 Nginx、应用网关、后端服务。不要只检查最终应用,因为鉴权可能位于链路中的任意一层。

  • 导出完整生效配置,而不是只看某个站点文件。
  • 确认所有监听端口,排除后端端口直接暴露。
  • 对比公网入口与本机回源请求的状态码、响应头和日志。
  • 检查 location 优先级、proxy_pass 是否携带 URI、rewrite 是否改变鉴权路径。
  • 确认 X-Forwarded-For、X-Real-IP 等头部只能由可信代理写入。

技术分析

下面是一类容易产生边界缺口的配置。管理员原本只希望保护 /admin/,但另一个通用 API 代理同样能够把请求送到包含管理路由的后端:

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

location /api/ {
    proxy_pass 
}

location = /_auth {
    internal;
    proxy_pass 
}

当客户端请求 /api/admin/users 时,Nginx 命中的是 /api/,并可能因 proxy_pass 末尾带斜杠而将 URI 替换为 /admin/users。后端看到的是管理路由,但入口层没有执行 auth_request。

另一个常见误区是使用来源 IP 代替身份认证。如果站点直接信任客户端提交的转发头,攻击者可能伪造“内网来源”。真实客户端地址应由受信任的 CDN 或反向代理重写,公网请求中的同名头必须覆盖,而不是透传。

操作步骤

第一步是确认配置与监听面。以下命令适合在服务器本机执行:

sudo nginx -T 2>&1 | less
sudo nginx -T 2>&1 | grep -nE 'server_name|listen|location|proxy_pass|auth_request|real_ip'
sudo ss -lntp
sudo systemctl status nginx --no-pager

# 检查后端端口是否被防火墙放行
sudo nft list ruleset
# 使用 firewalld 的系统可改用:
sudo firewall-cmd --list-all

第二步是在不触碰业务数据的前提下,对同一路由做最小化差异验证。优先请求只读、无副作用或专门准备的探针路径:

# 正常公网入口
curl -k -i 
curl -k -i 

# 本机模拟站点入口,避免直接访问后端业务接口
curl -i  -H 'Host: example.test'
curl -i  -H 'Host: example.test'

# 验证客户端伪造代理头是否会影响权限判断
curl -k -i  \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -H 'X-Real-IP: 127.0.0.1'

判断时不要只看是否返回 200。还应比较 Location、WWW-Authenticate、响应体长度、后端标识和请求 ID。若 /admin/health 返回 401,而 /api/admin/health 返回后端健康信息,基本可以确认入口路由存在鉴权覆盖缺口。

日志留痕与关联排查

建议临时增加包含上游信息的日志格式,以区分请求在 Nginx 中命中了什么路径、被转发到哪个上游,以及应用最终返回什么状态。不要记录 Cookie、Authorization 或完整查询参数中的敏感值。

log_format boundary '$time_iso8601 request_id=$request_id '
                    'remote=$remote_addr realip=$realip_remote_addr '
                    'host=$host request="$request" status=$status '
                    'uri=$uri upstream=$upstream_addr '
                    'upstream_status=$upstream_status rt=$request_time '
                    'urt=$upstream_response_time';

access_log /var/log/nginx/boundary_access.log boundary;

加载前先检查语法,再平滑重载:

sudo nginx -t && sudo systemctl reload nginx
sudo tail -F /var/log/nginx/boundary_access.log
sudo journalctl -u nginx --since '30 minutes ago' --no-pager

若应用支持请求 ID,应将 Nginx 生成的 ID传递给后端,方便跨层关联:

proxy_set_header X-Request-ID $request_id;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

修复配置

核心原则是:管理接口只能通过单一、明确的入口到达;前端代理与后端应用都必须执行权限校验。不要依赖 URI 黑名单,也不要只依赖来源 IP。

# 精确保护管理命名空间
location = /admin {
    return 308 /admin/;
}

location ^~ /admin/ {
    auth_request /_auth;
    proxy_pass 
    proxy_set_header X-Request-ID $request_id;
    proxy_set_header Host $host;
}

# API 代理保留 /api/ 前缀,避免意外映射到后端 /admin/
location /api/ {
    proxy_pass 
    proxy_set_header X-Request-ID $request_id;
    proxy_set_header Host $host;
}

location = /_auth {
    internal;
    proxy_pass 
    proxy_pass_request_body off;
    proxy_set_header Content-Length '';
    proxy_set_header X-Original-URI $request_uri;
}

这里将通用 API 的 proxy_pass 改为不带末尾斜杠,使后端继续收到 /api/...。但这只是路由层修正,后端仍应根据登录身份和角色校验管理接口,避免以后新增代理规则时再次暴露。

如果前方存在 CDN,应只信任其官方回源地址段,并递归解析真实地址:

# 地址段仅为文档示例,生产环境应替换为实际 CDN 官方网段
set_real_ip_from 192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 防火墙同时限制 80/443 只接受 CDN 回源或运维出口,
# 后端应用端口仅绑定回环或内网地址。

回归验证

测试项预期结果
未登录访问 /admin/health401 或跳转统一登录页
未登录访问 /api/admin/health404、403,且不能到达管理处理器
伪造 X-Forwarded-For不会获得额外权限
直接访问后端端口公网不可达
合法管理员访问鉴权成功,日志含可关联请求 ID
普通账号访问管理接口后端返回 403,而非仅依赖 Nginx

风险与修复建议

  • 先封闭后端直连端口,再修复代理规则,避免入口修好但旁路仍然存在。
  • 在应用层实施角色权限校验,Nginx 鉴权只能作为第一道边界。
  • 将关键路由的未认证访问、连续 401/403、异常 URI 归一化结果纳入告警。
  • 配置变更应经过 nginx -t、灰度发布和回归用例,避免直接覆盖线上配置。
  • 保留变更前后的配置、测试时间、请求 ID和日志片段,便于复盘;敏感日志按最小权限保存。
  • 对 CDN 回源、真实 IP、TLS 终止位置建立文档,避免后续维护人员误信客户端头部。

总结

网站反向代理的权限问题,往往来自“入口看到的 URI”和“后端实际处理的 URI”不一致。排查时应把完整链路、有效配置、监听端口和跨层日志放在一起看。修复也不能停留在增加一条 Nginx 规则,而应同时收紧网络暴露面、统一代理语义,并在后端落实最终授权检查。

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

这类绕过建议先排除“后端鉴权失效”,重点核对 Nginx 的 location 命中结果。实战里常见问题是:受保护目录配置了 auth_request,但正则 location、静态文件 location 或内部代理入口重新匹配后,没有继承鉴权。

可以临时打开 rewrite 调试日志,并在访问日志里记录原始 URI、规范化后的 URI、最终代理目标和鉴权状态:

error_log /var/log/nginx/error.log notice;
rewrite_log on;

log_format auth_debug
    '$remote_addr "$request" status=$status '
    'request_uri="$request_uri" uri="$uri" '
    'args="$args" upstream="$upstream_addr" '
    'upstream_status="$upstream_status" '
    'auth_status="$auth_status" '
    'xff="$http_x_forwarded_for"';

access_log /var/log/nginx/auth_debug.log auth_debug;

$request_uri 保留客户端原始请求,$uri 是 Nginx 规范化并可能经过 rewrite 的值。两者出现明显差异时,应检查重复斜杠、编码斜杠、..、尾部斜杠以及 rewrite 是否把请求送进了未鉴权的 location。可用下面的请求做差异对照,但只应在授权环境验证:

for path in \
  '/admin' \
  '/admin/' \
  '/admin//users' \
  '/admin/../admin/users' \
  '/admin%2fusers'
do
  curl -sk -o /dev/null -D - " \
    -H 'X-Request-ID: auth-check-001'
done

grep 'auth-check-001' /var/log/nginx/auth_debug.log

配置上建议把鉴权放在能够覆盖整个业务前缀的前缀 location 中,并使用 ^~ 阻止后续正则 location 抢占;鉴权子请求和内部代理入口则必须设为 internal:

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 X-Original-Method $request_method;
}

location ^~ /admin/ {
    auth_request /_auth;
    auth_request_set $auth_status $upstream_status;

    proxy_set_header Host $host;
    proxy_set_header X-Original-URI $request_uri;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass 
}

location = /admin {
    return 308 /admin/;
}

还要特别检查 proxy_pass 是否带 URI。下面两种写法转发语义不同,混用很容易造成前后端对“受保护路径”的理解不一致:

# 保留 /admin/ 前缀
location /admin/ {
    proxy_pass 
}

# 用 / 替换已匹配的 /admin/ 前缀
location /admin/ {
    proxy_pass 
}

修复后不要只验证返回码,还要确认未登录请求没有到达业务 upstream。可以同时观察 Nginx 日志和后端访问日志,并执行 nginx -T 查看合并后的完整配置,避免遗漏 include 文件中的正则 location:

nginx -T 2>&1 | less
nginx -t && nginx -s reload

grep -E 'location|auth_request|proxy_pass|rewrite' \
  /tmp/nginx-full.conf

边界上不建议仅靠 Nginx 做唯一授权层。反向代理负责统一入口鉴权,后端仍应校验用户身份和资源权限;同时覆盖 GET、POST、HEAD、OPTIONS,以及带参数、编码路径和尾斜杠的回归用例,才能避免配置调整后再次出现旁路。

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

最近浏览 0

  • 没有会员查看此页面。

SiteMap HACKHAT © 2026 All rights reserved.