Envoy 路径规范化安全修复:剥离 dot/dotdot 段参数(CVE-2026-73551)
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
本文围绕 Envoy 针对 CVE-2026-73551 的安全修复展开,核心内容是:在启用normalize_path路径规范化时,Envoy 会从 dot 段(/.;)与 dotdot 段(/..;)中剥离 URL 路径参数,使 RFC 3986 路径规范化能够正确折叠这些段,从而修复路径规范化绕过漏洞。读者读完本文后,将掌握该漏洞的成因、修复实现原理、stripParametersFromDotSegments的源码级行为、normalize_path配置与 runtime guard 的回退机制,以及如何通过测试用例验证修复效果。
漏洞背景:路径参数遮蔽 dotdot 段
在 HTTP 请求路径中,..(dotdot)段在 RFC 3986 语义下表示"回退到上一级目录",/a/../b规范化为/b。然而,URL 路径参数(以分号;开头的段后缀,如/..;foo=bar)会让 dotdot 段不再被路径规范化逻辑识别——因为..;foo=bar不再是严格的..,规范器会把它当作普通段保留下来。
这带来了一个严重的安全后果:攻击者构造/..;foo=bar/之类的路径时,规范器无法折叠 dotdot 段,导致实际处理路径与规范路径不一致,可能绕过基于路径的路由匹配、访问控制(RBAC)或鉴权规则,形成路径规范化绕过(Path Normalization Bypass)。这正是 CVE-2026-73551 所修复的问题。
该漏洞公告见 changelogs/current/bug_fixes/url_normalization__collapse_dotdot_segments_with_parameters.rst,对应 Envoy 安全公告 GHSA-2w8w-rfw7-8gg4(CVE-2026-73551)。
修复思路:先剥离参数,再折叠段
修复策略很直接:在路径规范化之前,先把 dot/dotdot 段中携带的路径参数剥离掉,让/..;foo=bar还原为/..、/.;foo=bar还原为/./,这样后续的 RFC 3986 规范化就能正确识别并折叠它们。
修复的触发条件
根据变更日志的明确说明,这一行为改变仅在启用normalize_path配置选项时才生效:
normalize_path未启用时,路径不进行规范化,剥离逻辑也不会触发,行为与修复前一致;normalize_path启用时,stripParametersFromDotSegments作为规范化前的一个预处理步骤被执行。
关键实现:stripParametersFromDotSegments
该修复的核心实现在 source/common/http/path_utility.cc 的PathUtil::stripParametersFromDotSegments:
void PathUtil::stripParametersFromDotSegments(RequestHeaderMap& headers) { ASSERT(headers.Path()); const auto original_path = headers.getPathValue(); const absl::string_view::size_type query_or_fragment = original_path.find_first_of("?#"); absl::string_view path = original_path.substr(0, query_or_fragment); if (path.find(".;") == absl::string_view::npos) { return; } const absl::string_view query_and_fragment = query_or_fragment == absl::string_view::npos ? absl::string_view{} : original_path.substr(query_or_fragment); // Remove parameters from dot and dotdot segments so the subsequent path canonicalization // can correctly collapse them. std::vector segments = absl::StrSplit(path, '/'); for (auto& segment : segments) { if (absl::StartsWith(segment, "..;")) { segment = ".."; } else if (absl::StartsWith(segment, ".;")) { segment = "."; } } headers.setPath(absl::StrCat(absl::StrJoin(segments, "/"), query_and_fragment)); }从源码结构可以看出该实现的几个关键行为:
- 只处理路径部分:先通过
find_first_of("?#")切出 query 与 fragment,仅对路径段做剥离,查询字符串与片段原样保留(例如/xyz?param=/..;foo中 query 里的..;foo不会被误伤); - 快速短路:若路径中不存在
.;(同时覆盖.;与..;两种形态),直接返回,零开销; - 精确匹配前缀:仅当段以
..;或.;开头时才剥离参数,..foo、...;foo、.foo等不以分号紧跟的段不受影响(测试见 test/common/http/path_utility_test.cc 第 149-179 行的StripDotSegmentWithParameters用例); - 多参数段一并剥离:
..;param1;param2也会整体还原为..; - 与规范化流程衔接:
canonicalPath在stripParametersFromDotSegments之后调用url::CanonicalizePath(来自 Google URL 库),此时/xyz/../abc可被正确折叠为/abc。
调用链:canonicalPath 中的门控
在同一个文件中的PathUtil::canonicalPath(source/common/http/path_utility.cc 第 33-61 行)展示了修复如何被接入:
/* static */ bool PathUtil::canonicalPath(RequestHeaderMap& headers) { ASSERT(headers.Path()); if (Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.strip_dotdot_segments_with_parameters")) { stripParametersFromDotSegments(headers); } const auto original_path = headers.getPathValue(); ... auto normalized_path_opt = canonicalizePath(...); ... }关键点:stripParametersFromDotSegments的调用被 runtime guard 包裹。也就是说,即使normalize_path已启用,也只有在 runtime guard 处于默认开启状态时才会执行剥离。而根据变更日志,guard 关闭时(envoy.reloadable_features.strip_dotdot_segments_with_parameters设为false)可临时回退到修复前的行为。
normalize_path本身在 api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto(字段normalize_path = 30,第 947-961 行注释)中定义:启用后,HTTP filters 与路由处理请求前,路径会按 RFC 3986 规范化(影响上游:path头),格式不合法的路径会返回 400。注意该字段当前默认false(未来默认将变为true),未显式指定时可由 runtime 变量http_connection_manager.normalize_path覆盖。
运行时回退机制:runtime guard
为便于运维方在升级后观察影响、处理异常,Envoy 提供了临时回退开关。变更日志明确说明:
该行为变更可通过将 runtime guard
envoy.reloadable_features.strip_dotdot_segments_with_parameters设置为false临时回退。
该 guard 在 source/common/runtime/runtime_features.cc 中注册:
RUNTIME_GUARD(envoy_reloadable_features_strip_dotdot_segments_with_parameters);配置方式(Bootstrap 的runtime_layer):
layered_runtime: layers: - name: static_layer static_layer: envoy: reloadable_features: strip_dotdot_segments_with_parameters: "false"回退适用于以下场景:
- 生产环境升级到包含此修复的版本后,发现依赖旧行为的请求(如路径参数本身携带业务语义、且依赖 dotdot 折叠行为)出现异常;
- 需要先在灰度环境验证修复对现有流量的影响,再全量放开。
需要强调的是,关闭 guard 意味着路径规范化绕过漏洞重新暴露,因此仅应作为短期应急手段,问题解决后应尽快恢复默认(guard 缺省即为true,剥离逻辑默认生效)。
测试验证:修复行为的证据
修复的正确性由单元测试充分覆盖,测试文件为 test/common/http/path_utility_test.cc。
剥离逻辑测试(StripDotSegmentWithParameters)
PathUtil::stripParametersFromDotSegments的剥离行为测试(第 149-179 行)覆盖了 dot 与 dotdot 两类段:
// dotdot 段 EXPECT_EQ("/xyz/../abc", stripDot("/xyz/..;foo=bar/abc")); EXPECT_EQ("/../abc", stripDot("/..;foo=bar/abc")); EXPECT_EQ("/xyz/..", stripDot("/xyz/..;foo=bar")); EXPECT_EQ("/a/../b", stripDot("/a/..;param1;param2/b")); // dot 段 EXPECT_EQ("/xyz/./abc", stripDot("/xyz/.;foo=bar/abc")); EXPECT_EQ("/./abc", stripDot("/.;foo=bar/abc")); EXPECT_EQ("/xyz/.", stripDot("/xyz/.;foo=bar")); // 非分号后缀不受影响 EXPECT_EQ("/xyz/..foo", stripDot("/xyz/..foo")); EXPECT_EQ("/xyz/...;foo",stripDot("/xyz/...;foo")); // query/fragment 中的分号段不被误伤 EXPECT_EQ("/xyz?param=/..;foo", stripDot("/xyz?param=/..;foo")); EXPECT_EQ("/xyz#frag=/..;foo", stripDot("/xyz#frag=/..;foo"));关闭 guard 后的回归测试(NormalizeValidPathsDisabled)
对应 guard 关闭时的行为,测试(第 87-103 行)通过TestScopedRuntime将envoy.reloadable_features.strip_dotdot_segments_with_parameters合并为"false",验证修复前行为被完整保留:
scoped_runtime.mergeValues( {{"envoy.reloadable_features.strip_dotdot_segments_with_parameters", "false"}}); const std::vector> non_normal_pairs{ {"/xyz/..;foo=bar/abc", "/xyz/..;foo=bar/abc"}, {"/..;foo=bar/abc", "/..;foo=bar/abc"}, {"/xyz/..;foo=bar", "/xyz/..;foo=bar"}};即 guard 关闭时,..;foo=bar段原样保留、不做折叠,与修复前行为一致,证明回退开关有效。相关集成层面也有同样验证(见 test/common/http/conn_manager_utility_test.cc 第 2725 行附近的用例)。
完整效果示例
结合以上实现与测试,启用修复(normalize_path: true+ guard 默认开启)后:
| 请求路径 | 规范化结果 |
|---|---|
/a/../b | /b |
/a/..;foo=bar/b | /b |
/a/.;foo/b | /a/b |
/a/..;param1;param2/b | /b |
/a/..foo/b | /a/..foo/b(非 dotdot 段,不剥离) |
/a/..;foo/b?x=/..;y | /b?x=/..;y(query 不受影响) |
升级与运维建议
- 确认版本:升级到包含该修复的 Envoy 版本,并核对发布说明中本变更的归属;
- 评估 normalize_path 状态:该修复仅在
normalize_path启用时生效。若你尚未启用normalize_path,建议评估启用(默认未来将变为true),以获得路径规范化带来的路由一致性收益; - 观察期回退:若升级后出现依赖旧行为的异常流量,可通过 runtime guard
envoy.reloadable_features.strip_dotdot_segments_with_parameters = false临时回退,同时排查并修正依赖路径参数的客户端,随后恢复默认并再次验证; - 安全回归:回退期间应视为漏洞处于暴露状态,密切监控相关路径的异常访问模式,尽快恢复正常配置。
小结
CVE-2026-73551 的修复通过"先剥离 dot/dotdot 段中的路径参数、再执行 RFC 3986 规范化"的两步策略,在normalize_path启用的前提下堵住了路径规范化绕过漏洞。修复由 runtime guardenvoy.reloadable_features.strip_dotdot_segments_with_parameters控制,既保证了默认安全,也保留了运维方临时回退的弹性。其核心实现位于 source/common/http/path_utility.cc,并由 test/common/http/path_utility_test.cc 中的正反两组测试用例完整锁定行为。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考