URI-Template Route Bypass via Path Parameters
Affected commit: c33d01624d
Sink: source/common/router/config_impl.cc:1416
Summary
When ignore_path_parameters_in_path_matching is enabled, URI-template routes match the raw :path instead of the sanitized path. A semicolon path parameter prevents a protected URI-template route from matching, causing evaluation to fall through to an unprotected fallback route.
Reproduce
#!/bin/bash
set -e
git clone --depth 1 https://github.com/envoyproxy/envoy.git /tmp/envoy-uritemplate
cd /tmp/envoy-uritemplate
echo "HEAD: $(git rev-parse HEAD)"
# Show that UriTemplateMatcherRouteEntryImpl::matches uses path() (raw)
sed -n '1412,1421p' source/common/router/config_impl.cc
# Show that path() returns raw headers_.getPathValue() without sanitization
sed -n '81,82p' source/common/router/config_impl.h
# Show that sanitizedPath() strips after semicolon (used by other matchers)
sed -n '91,97p' source/common/router/config_impl.h
# Show another route type (e.g. ConnectRouteEntryImpl) uses sanitized path
grep -n 'sanitizePathBeforePathMatching\|sanitizedPath' source/common/router/config_impl.cc | head -5
echo ""
echo "=== Vulnerable code confirmed at HEAD ==="
echo "config_impl.cc:1417: UriTemplateMatcherRouteEntryImpl::matches"
echo " calls path_matcher_->match(route_match_context.path())."
echo "config_impl.h:81: path() returns headers_.getPathValue() (raw)."
echo "config_impl.h:93: sanitizedPath() strips after ';' when configured."
echo ""
echo "URI-template routes use path() not sanitizedPath(), so with"
echo "ignore_path_parameters_in_path_matching enabled, the router"
echo "sanitizes the path for route selection but URI-template matching"
echo "sees the raw path with the semicolon, causing a mismatch."
echo ""
echo "Attack: GET /api/v1/resource;bypass"
echo " URI-template '/api/v1/resource' does NOT match '/api/v1/resource;bypass'"
echo " Falls through to a less-protected fallback route."
rm -rf /tmp/envoy-uritemplate
Observed output (verified at c33d01624d):
HEAD: c33d01624d5b173b5d6e5c0c0474d480cab69b7b
RouteConstSharedPtr
UriTemplateMatcherRouteEntryImpl::matches(const RouteMatchContext& route_match_context,
const StreamInfo::StreamInfo& stream_info,
uint64_t random_value) const {
if (RouteEntryImplBase::matchRoute(route_match_context, stream_info, random_value) &&
path_matcher_->match(route_match_context.path())) {
return clusterEntry(route_match_context.headers(), stream_info, random_value);
}
return nullptr;
}
absl::string_view path() const { return headers_.getPathValue(); }
absl::string_view sanitizedPath() const {
if (!sanitized_path_computed_) {
sanitized_path_ = ignore_path_params_ ? stripPathParams(path()) : path();
sanitized_path_computed_ = true;
}
return sanitized_path_;
}
source/common/router/config_impl.cc:815:RouteEntryImplBase::sanitizePathBeforePathMatching(const absl::string_view path) const {
source/common/router/config_impl.cc:1509: ASSERT(path_matcher_->match(sanitizePathBeforePathMatching(path)));
=== Vulnerable code confirmed at HEAD ===
config_impl.cc:1417: UriTemplateMatcherRouteEntryImpl::matches
calls path_matcher_->match(route_match_context.path()).
config_impl.h:81: path() returns headers_.getPathValue() (raw).
config_impl.h:93: sanitizedPath() strips after ';' when configured.
URI-template routes use path() not sanitizedPath(), so with
ignore_path_parameters_in_path_matching enabled, the router
sanitizes the path for route selection but URI-template matching
sees the raw path with the semicolon, causing a mismatch.
Attack: GET /api/v1/resource;bypass
URI-template '/api/v1/resource' does NOT match '/api/v1/resource;bypass'
Falls through to a less-protected fallback route.
Credit
Zheng Yu @ Depthfirst