Skip to content

路由解析一致性与反向代理头信任加固 ​

防御目标与威胁面 ​

Web 鉴权绕过往往源于“前段代理/WAF 所见的路径或方法,与后端框架实际路由执行的路径或方法不一致”。具体风险点包含:

  1. 不可信路由重写头:反向代理或后端框架盲信客户端传入的 X-Original-URL、X-Rewrite-URL、X-Forwarded-For、X-Forwarded-Host、X-HTTP-Method-Override、X-HTTP-Method 等头字段,导致基于路径或方法的 ACL 被轻易跳过。
  2. 路径规范化差异 (Parser Differential):前端按原始字符串匹配 /admin 拦截,后端 Spring/Tomcat/IIS/Nginx 却对 /admin..;/、/admin;.json、//admin、/%2e%2e/admin、/ADMIN 或尾部 %20/%00 做了不同程度的解码、截断或大小写折叠后放行。
  3. Method 矩阵遗漏:仅对 GET/POST 挂载鉴权拦截器,忽略 HEAD/OPTIONS/PUT/PATCH 或未开启统一默认拒绝策略(Default Deny)。
  4. 源码与调试资产暴露:前端 JS SourceMap(.js.map)、OpenAPI 文档与备份文件(.bak/~)未在发布阶段剥离或拦截,直接暴露隐藏路由与参数结构。

核心防御架构 ​

风险类别根治控制措施实施要点
重写头与方法覆盖边缘层无条件剥离客户端注入头,后端禁用 Method Override清除 X-Original-URL、X-Rewrite-URL、X-HTTP-Method*,或仅允许由可信内网网关注入
路径规范化差分先单次规范化再鉴权,拒绝歧义路径字符拒绝对含 ;、\0、\、多重 %2e/%2f、非规范连续斜杠的请求进行隐式降级匹配
鉴权位置错配鉴权下沉到业务控制器统一中间件,禁止仅依赖 URL 前缀黑名单采用“默认拒绝 + 显式路由权限标注”,确保 HEAD/PUT/DELETE/PATCH 同权校验
隐藏路由与 Schema 泄露生产构建禁用 SourceMap 外发,下线未鉴权文档与备份清理 *.map、关闭 Spring Boot /actuator/* 非健康端点

加固配置与离线核查示例 ​

Nginx 边缘网关清洗危险重写头并阻断路径混淆字符的配置示例:

nginx
# 1. 剥离客户端伪造的路由/方法重写头,禁止透传到上游应用
proxy_set_header X-Original-URL "";
proxy_set_header X-Rewrite-URL "";
proxy_set_header X-HTTP-Method-Override "";
proxy_set_header X-HTTP-Method "";
proxy_set_header X-Method-Override "";

# 2. 直接拒绝包含矩阵分号、控制字符或未规范化穿越片段的原始 URI
if ($request_uri ~* "(;|\x00|\\|%00|%2e%2e|%252e)") {
    return 400;
}

# 3. 拦截前端 SourceMap 与备份后缀
location ~* \.(map|bak|old|swp|orig)$ {
    return 404;
}

以下 Python 规范化中间件演示“单轮严格解码 + 拒绝歧义路径 + 方法覆盖拦截 + 默认拒绝路由鉴权”的自检实现:

python
import posixpath
import re
from urllib.parse import unquote
from typing import Dict, Tuple

BLOCKED_HEADERS = {
    "x-original-url",
    "x-rewrite-url",
    "x-http-method-override",
    "x-http-method",
    "x-method-override",
}
AMBIGUOUS_RAW_RE = re.compile(r"(;|\\|\x00|%00|%25|%2e%2e|%2f%2f|//)", re.I)


def canonicalize_and_authorize(
    raw_path: str,
    method: str,
    headers: Dict[str, str],
    user_role: str,
) -> Tuple[int, str]:
    lower_headers = {k.lower(): v for k, v in headers.items()}

    # 1. 拒绝携带路由或方法重写头的外部请求
    for h in BLOCKED_HEADERS:
        if h in lower_headers:
            return 400, f"rejected_override_header:{h}"

    # 2. 在原始层拦截含分号、空字节、双重编码或连续斜杠的歧义请求
    if AMBIGUOUS_RAW_RE.search(raw_path):
        return 400, "rejected_ambiguous_path"

    # 3. 单次 URL 解码后执行严格 POSIX 规范化与小写核验
    decoded = unquote(raw_path.split("?", 1)[0])
    if "\x00" in decoded or ";" in decoded or "\\" in decoded:
        return 400, "rejected_decoded_control"

    norm = posixpath.normpath(decoded)
    if not norm.startswith("/"):
        norm = "/" + norm
    canonical = norm.lower()

    # 4. 统一策略:先确定最终规范路径与方法,再按角色执行 Default-Deny 鉴权
    allowed_methods = {"GET", "POST", "PUT", "DELETE", "PATCH"}
    if method.upper() not in allowed_methods:
        return 405, "method_not_allowed"

    if canonical.startswith("/admin") or canonical.startswith("/api/internal"):
        if user_role != "admin":
            return 403, f"forbidden:{canonical}"
        return 200, f"ok:{canonical}"

    return 200, f"public:{canonical}"


if __name__ == "__main__":
    assert canonicalize_and_authorize("/admin", "GET", {"X-Original-URL": "/admin"}, "guest") == (400, "rejected_override_header:x-original-url")
    assert canonicalize_and_authorize("/admin;.json", "GET", {}, "guest") == (400, "rejected_ambiguous_path")
    assert canonicalize_and_authorize("/public/../admin", "GET", {}, "guest") == (403, "forbidden:/admin")
    assert canonicalize_and_authorize("/ADMIN", "GET", {}, "guest") == (403, "forbidden:/admin")
    assert canonicalize_and_authorize("/api/internal/users", "DELETE", {}, "user") == (403, "forbidden:/api/internal/users")
    assert canonicalize_and_authorize("/admin", "GET", {}, "admin") == (200, "ok:/admin")
    print("Route normalization defense self-check passed.")

防御工作流 ​

text
1. 代理清头:在反代/API 网关统一清除外部请求中的路由重写与方法覆盖头
2. 统一规范:确保 WAF、网关与后端应用使用一致的路径解码顺序与大小写策略,拒绝含分号/空字节/双编码的路径
3. 鉴权下沉:在应用路由最终分发点之后、控制器执行之前挂载 Default-Deny 鉴权拦截器
4. 方法对齐:为管理与敏感接口显式限定允许的 HTTP Method,禁止 HEAD/PATCH/OPTIONS 绕过前置钩子
5. 产物清理:从生产静态托管目录移除 .js.map、非公开 OpenAPI 文档与备份文件

验证与判定标准 ​

  • 路径变异全拦截:对受保护路由发送 /admin..;/、/admin;.json、//admin、/%2e%2e/admin、/ADMIN 及带 X-Original-URL: /admin 请求时,匿名或普通用户均只获得 400 或 403。
  • 方法矩阵一致性:对同一敏感端点遍历 GET/POST/PUT/PATCH/DELETE/HEAD,未授权状态均无法触发业务执行或状态变更。
  • SourceMap 不可下载:所有前端静态包对应 .js.map 在生产 CDN/源站返回 404。

MCP 工具映射 ​

防御步骤MCP 工具说明
检索路由与代理差异防御kb_router搜索 路由加固、代理差异、路径规范化
对照路由绕过矩阵kb_read_file读取 recon-routing.md 核查全部 Header 与路径变异列表
关联缓存欺骗防御kb_catalog联动 web-cache-deception-defense 防范分隔符解析差异

Apache-2.0 · 仅供授权环境下的学习与防御性研究使用 · 免责声明