路由解析一致性与反向代理头信任加固
防御目标与威胁面
Web 鉴权绕过往往源于“前段代理/WAF 所见的路径或方法,与后端框架实际路由执行的路径或方法不一致”。具体风险点包含:
- 不可信路由重写头:反向代理或后端框架盲信客户端传入的
X-Original-URL、X-Rewrite-URL、X-Forwarded-For、X-Forwarded-Host、X-HTTP-Method-Override、X-HTTP-Method等头字段,导致基于路径或方法的 ACL 被轻易跳过。 - 路径规范化差异 (Parser Differential):前端按原始字符串匹配
/admin拦截,后端 Spring/Tomcat/IIS/Nginx 却对/admin..;/、/admin;.json、//admin、/%2e%2e/admin、/ADMIN或尾部%20/%00做了不同程度的解码、截断或大小写折叠后放行。 - Method 矩阵遗漏:仅对
GET/POST挂载鉴权拦截器,忽略HEAD/OPTIONS/PUT/PATCH或未开启统一默认拒绝策略(Default Deny)。 - 源码与调试资产暴露:前端 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 防范分隔符解析差异 |