一条尾斜杠,一笔未授权转账:AWS API Gateway 鉴权绕过漏洞解析

2026-06-01 20 预计阅读时间:1 分钟
来源:infoq.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:11 分钟

一家金融科技公司的 API 网关上,Lambda authorizer 挡住了所有未授权请求——直到有人在 URL 末尾随手加了一个 /。这个看似无害的字符,让整条鉴权链路直接失效,未认证用户得以发起电汇操作。这不是科幻情节,而是真实发生的安全事件。更值得关注的是,同一类路径规范化不匹配的漏洞也出现在 gRPC-Go 中(CVE-2026-33186),说明这是一个跨平台、跨生态的系统性问题。

漏洞是怎么发生的

AWS API Gateway 的 HTTP API 模式支持"贪婪路由"(greedy route),例如 {proxy+} 会匹配 /transfer/transfer/wire 以及 /transfer/wire/——末尾有没有斜杠都命中同一条路由。

问题出在路由层和授权层对路径的规范化方式不一致:

  • 路由层在匹配时,会做路径规范化,/transfer/wire//transfer/wire 被视为同一条路由,都命中 {proxy+}
  • 授权层在判断"这条路由是否需要鉴权"时,用的是未经规范化的原始路径。如果你在 API Gateway 配置中只为 /transfer/wire 绑定了 Lambda authorizer,那么当请求路径是 /transfer/wire/ 时,授权层找不到匹配的授权绑定,直接跳过鉴权。

结果:请求通过了路由,却绕过了鉴权。

用一个简化的对比来理解:

请求路径 路由匹配结果 授权绑定查找结果 鉴权是否执行
/transfer/wire 命中 {proxy+} 找到绑定 ✅ 执行
/transfer/wire/ 命中 {proxy+} 未找到绑定 ❌ 跳过

核心矛盾就是四个字:各说各话。路由层说"这两条路径是同一个",授权层说"我没见过带斜杠的那个"。

这不是 AWS 的独有问题

gRPC-Go 在 CVE-2026-33186 中暴露了同类漏洞。gRPC 的 HTTP/2 路径匹配和内部服务方法解析之间也存在规范化不一致——攻击者通过构造特定格式的路径(如多余斜杠或路径段),可以让请求绕过某些中间件的校验逻辑直达后端服务方法。

这类漏洞的共同特征:

  1. 两个组件各自实现路径解析,没有共享规范化逻辑。
  2. 配置者只按一种格式写规则(通常是不带尾斜杠的"干净"路径),却不知道另一层会把变体路径也纳入匹配。
  3. 绕过方式极其简单——加一个字符,改一个斜杠,不需要任何高级技巧。

实战:如何检测和修复

用 curl 快速检测你的 API Gateway

以下脚本对一组敏感路径逐一测试,对比带尾斜杠和不带尾斜杠的响应差异。如果带斜杠的请求返回了业务数据而非 401/403,你的网关大概率存在此漏洞。

#!/usr/bash
# trailing-slash-check.sh
# 用法: ./trailing-slash-check.sh https://your-api-id.execute-api.region.amazonaws.com

API_BASE="${1:?请提供 API Gateway 的基础 URL}"
# 需要鉴权的敏感路径列表
PATHS=(
  "/transfer/wire"
  "/account/balance"
  "/admin/config"
)

for p in "${PATHS[@]}"; do
  echo "--- 测试路径: $p ---"
  # 不带尾斜杠
  STATUS_NO=$(curl -s -o /dev/null -w "%{http_code}" "${API_BASE}${p}")
  echo "无尾斜杠 ($p): HTTP $STATUS_NO"

  # 带尾斜杠
  STATUS_WITH=$(curl -s -o /dev/null -w "%{http_code}" "${API_BASE}${p}/")
  echo "有尾斜杠 ($p/): HTTP $STATUS_WITH"

  # 如果带斜杠返回 2xx 而不带斜杠返回 401/403,说明鉴权被绕过
  if [[ "$STATUS_WITH" =~ ^2 ]] && [[ "$STATUS_NO" =~ ^4 ]]; then
    echo "⚠️  鉴权绕过疑似存在!"
  else
    echo "✅  两种路径响应一致,暂无绕过迹象"
  fi
  echo ""
done

运行前把 PATHS 数组替换成你自己的敏感路径。脚本只看 HTTP 状态码,不检查响应体——如果你需要更精确的判断,可以加上 -D - 打印响应头,或用 jq 解析响应体中的错误字段。

修复方案:在授权层覆盖所有路径变体

最直接的修复是为每个需要鉴权的路径同时绑定带尾斜杠和不带尾斜杠的版本。以下 Terraform 配置展示了如何做到:

# 为 /transfer/wire 和 /transfer/wire/ 都绑定同一个 Lambda authorizer

resource "aws_apigatewayv2_api" "fintech_api" {
  name          = "fintech-http-api"
  protocol_type = "HTTP"
}

resource "aws_apigatewayv2_authorizer" "lambda_auth" {
  api_id           = aws_apigatewayv2_api.fintech_api.id
  authorizer_type  = "REQUEST"
  authorizer_uri   = aws_lambda_function.auth.invoke_arn
  identity_sources = "$request.header.Authorization"
  name             = "lambda-authorizer"
}

# 路由 1:不带尾斜杠
resource "aws_apigatewayv2_route" "transfer_wire" {
  api_id    = aws_apigatewayv2_api.fintech_api.id
  route_key = "POST /transfer/wire"
  target    = "integrations/${aws_apigatewayv2_integration.transfer_lambda.id}"
  authorizer_id = aws_apigatewayv2_authorizer.lambda_auth.id
  authorization_type = "CUSTOM"
}

# 路由 2:带尾斜杠(关键修复)
resource "aws_apigatewayv2_route" "transfer_wire_slash" {
  api_id    = aws_apigatewayv2_api.fintech_api.id
  route_key = "POST /transfer/wire/"
  target    = "integrations/${aws_apigatewayv2_integration.transfer_lambda.id}"
  authorizer_id = aws_apigatewayv2_authorizer.lambda_auth.id
  authorization_type = "CUSTOM"
}

如果你有大量路径,手动为每条路径加一个带斜杠的副本会很繁琐。更系统化的做法是:

  1. 在 Lambda authorizer 内部做路径规范化——收到请求后先去掉尾斜杠再判断权限,这样无论路由层传来什么格式,授权逻辑都基于统一路径。
  2. $default 路由兜底——为整个 API 设置一个默认路由并绑定 authorizer,确保任何未明确配置的路径变体也必须过鉴权。

下面是一个在 Lambda authorizer 中规范化路径的 Python 示例:

import re

def lambda_handler(event, context):
    # 从 API Gateway 传入的事件中提取原始路径
    raw_path = event.get("rawPath", "")

    # 规范化:去掉尾斜杠(保留根路径 "/")
    normalized = raw_path.rstrip("/") if raw_path != "/" else raw_path

    # 基于规范化路径做权限判断
    allowed_paths = {"/transfer/wire", "/account/balance"}
    if normalized not in allowed_paths:
        return {"isAuthorized": False, "context": {"reason": "path_not_allowed"}}

    # 继续验证 Authorization header 等...
    token = event["headers"].get("authorization", "")
    if not verify_token(token):
        return {"isAuthorized": False, "context": {"reason": "invalid_token"}}

    return {"isAuthorized": True, "context": {"user_id": extract_user(token)}}

def verify_token(token: str) -> bool:
    # 替换为你的实际验证逻辑
    return bool(token) and token.startswith("Bearer ")

核心改动就一行:raw_path.rstrip("/")。这确保了 /transfer/wire//transfer/wire 在授权判断中被视为同一条路径,与路由层的匹配逻辑对齐。

防范同类问题的清单

路径规范化不匹配是一个容易被忽视的漏洞类别。以下清单适用于任何涉及多层路径解析的系统:

  • 统一规范化策略:路由层、授权层、后端服务必须共享同一条路径规范化规则,或者至少保证"规范化后的路径集合"完全一致。最安全的做法是在最靠近入口的层(如 authorizer 或边缘中间件)做规范化,后续所有层只使用规范化后的路径。
  • 测试路径变体:在集成测试中,对每个敏感路径至少覆盖四种变体——原始路径、带尾斜杠、双斜杠(//transfer/wire)、路径编码(%2F)。自动化测试比人工检查更可靠。
  • 默认拒绝:任何未明确绑定授权的路由,应该默认返回 401,而不是默认放行。AWS API Gateway 的 $default 路径可以绑定 authorizer 来实现这一点。
  • 审计现有配置:检查你的 API Gateway 路由列表,找出所有绑定了 authorizer 的路径,逐一确认其尾斜杠变体是否也有授权绑定。可以用 AWS CLI 快速导出:
aws apigatewayv2 get-routes --api-id YOUR_API_ID \
  --query 'Items[].[RouteKey,AuthorizationType]' --output table

如果输出中只有 POST /transfer/wire 而没有 POST /transfer/wire/,你就需要补上。

  • 关注 CVE 通报:gRPC-Go 的 CVE-2026-33186 说明这类问题不只存在于 AWS。如果你使用 nginx、Envoy、Kong 等做路径路由和鉴权,同样需要验证各层对 ////./ 等路径变体的处理是否一致。

一个斜杠,一笔转账。安全工程中最危险的漏洞,往往不是复杂的加密缺陷,而是两个组件对同一个字符串的理解差了一个字符。


相关推荐