[5] CSP path matching bypassed via percent-encoded slashes
Severity: Medium | Component: WebCore CSP path matching | 9a19d07
Rated Medium because the diff fixes a deterministic CSP path-restriction bypass exploitable when combined with a separate URL-injection primitive on the allowed origin; impact is policy bypass, not memory corruption.
WebKit's pathMatches() diverged from the CSP3 spec by percent-decoding the entire URL path as a flat string, then doing prefix/equality checks. This made it vulnerable to %2F..%2F path-traversal bypasses. The fix adopts the spec's algorithm (§6.7.2.12): split both paths on literal /, percent-decode each segment, and compare corresponding pairs.
Source/WebCore/page/csp/ContentSecurityPolicySource.cpp
bool ContentSecurityPolicySource::pathMatches(const URL& url) const
{
+ // https://www.w3.org/TR/CSP3/#match-paths
if (m_path.isEmpty())
return true;
- auto path = PAL::decodeURLEscapeSequences(url.path());
+ auto urlPath = url.path();
+
+ if (m_path == "/"_s && urlPath.isEmpty())
+ return true;
+
+ bool exactMatch = !m_path.endsWith('/');
+ auto pathListA = m_path.splitAllowingEmptyEntries('/');
+ auto pathListB = urlPath.toString().splitAllowingEmptyEntries('/');
+
+ if (pathListA.size() > pathListB.size())
+ return false;
+ if (exactMatch && pathListA.size() != pathListB.size())
+ return false;
+ if (!exactMatch) {
+ ASSERT(pathListA.last().isEmpty());
+ pathListA.removeLast();
+ }
- if (m_path.endsWith('/'))
- return path.startsWith(m_path);
+ for (unsigned i = 0; i < pathListA.size(); ++i) {
+ if (PAL::decodeURLEscapeSequences(pathListA[i]) != PAL::decodeURLEscapeSequences(pathListB[i]))
+ return false;
+ }
- return path == m_path;
+ return true;
}
Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp
- return PAL::decodeURLEscapeSequences(begin.first(buffer.position() - begin.data()));
+ return String(begin.first(buffer.position() - begin.data()));
Patch Details
The old implementation percent-decoded the entire URL path as one flat string and then did a startsWith/equality check against m_path. The new implementation splits both m_path and url.path() on the literal / via splitAllowingEmptyEntries('/'), applies steps 1–7 of the spec (empty-path fast path, / vs empty, directory-vs-exact match, segment count comparison, trailing empty-segment removal), and only then percent-decodes each segment in isolation before comparing. Complementarily, parsePath() no longer percent-decodes the directive's path at parse time — m_path now retains raw %2F sequences so the segment split is meaningful.
Decode-before-tokenise ordering in a path matcher: percent-decoding the full URL path before splitting on '/' lets encoded slashes (%2F) cross structural segment boundaries that the access-control policy is defined over.
Background
A CSP directive carries a list of source expressions; each has a scheme, host, port and optional path component. URL fetching consults ContentSecurityPolicySource::matches(), which checks scheme/host/port/path. CSP3 §6.7.2.12 defines path matching as a segment-wise operation — both paths are split on literal /, each pair of segments is percent-decoded, and the decoded segments are compared. A directive ending in / is a directory match; otherwise it is an exact match. %2F is the percent-encoding of /, and the URL standard preserves %2F literally in the path component rather than decoding it to a structural separator, so /a%2Fb/c has two segments (a%2Fb and c), not three. PAL::decodeURLEscapeSequences has no notion of segment boundaries and turns %2F into a literal /.
Analysis
pathMatches() performed the comparison on a fully percent-decoded URL path. PAL::decodeURLEscapeSequences(url.path()) turns %2F into a literal /, so an attacker URL like http://host/security/contentSecurityPolicy%2F..%2F..%2Fresources/script.js decodes to /security/contentSecurityPolicy/../../resources/script.js. The decoded string starts with /security/contentSecurityPolicy/, which startsWith(m_path) accepts — even though the URL's true path segment is the single literal blob contentSecurityPolicy%2F..%2F..%2Fresources that does NOT live under /security/contentSecurityPolicy/resources/ per the CSP3 segment-wise definition.
Aa Aaaaaaaa Aaa Aaaaaaaa a Aaa Aaaaaa Aaaaaaa Aa Aaa Aaaa a Aaa Aaaaaaaa Aaa Aa Aaaaaaaaaaaaaa Aaaaaaaaa Aaaa Aaaa Aaaa Aaaaa a Aaaaaaaa Aaaaaaaaa Aaaaaaaaaa Aa Aaaa Aaaaaaaa Aa Aaa Aaaaaaa Aaaaaaa Aa a Aaaaaaaaaaa Aaaaaaaaaa Aaaa a Aaa Aaaaa Aaa Aaaa Aa Aaa Aaaaa Aaaaaaaaaa Aaaaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaaa Aaaaa Aaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aa a Aaaaaaaaa Aaaaaaaaa Aaaaa Aaa Aaaaa Aaaa Aaaaa Aaaa Aa Aaaaa Aaaaaaa Aaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaaaa Aaa Aaaaaaa Aaaaaa Aaaaaa Aaaaaaaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaa Aa Aaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaa Aaaaa Aaaaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaaa Aaa Aaa Aaaaaaaaaaaaaaaa Aaaaa Aaaaaaaaa Aaaa Aaaaaaa a Aaaaaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaaaa Aaaaa Aa Aaaa Aaaaa Aaaa Aaaaaaaa Aaaa Aaaaa Aaaaaaaaa Aaaaaa Aaa Aaaa Aa Aaaaaaaa Aaa Aaaaa Aaaaaaaaa a Aaa Aaa Aa Aaaa Aaaaaaaaa Aaaaaaaaa Aaaaa Aaaaaaaaaa Aa Aaa Aaaaa Aaa Aaaa Aaaaa Aaa Aaaaaaaaaa Aa Aaaaaa Aaa Aaaaaaaaa Aaaaaaaaaaa Aaa Aaaa Aaaaaaa Aaaa Aaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaa Aaaa Aaaaaaaa Aa Aaa Aaaaaa Aaaa Aa Aaaaaaaa Aaa Aaa Aaaaaa Aaa Aaaaaaaa Aaaaa Aaaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaa Aaa Aaaa Aaaaaaaaa Aa Aaaaaaaaa Aaa Aaa Aaaaaaa Aaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aa Aaa Aaaaaaaa Aaa Aaaaaaaa Aaa Aaaaaaa Aaa Aaa Aaaaaaa a Aaa Aaaa Aaaa Aaaa Aa Aaaaaaaaa Aaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaaaaa Aaa Aaaaaaa Aaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaaaa Aaaaaaaaaaaaaaa
🔒The decode-then-compare ordering at the heart of this bug is one of the oldest anti-patterns in web security — the analysis traces exactly which URLs slipped through and why CSP3's spec deliberately reverses the order.
Subscribe to read more
Audit directions
a Aaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaa Aaaaaaaaaaaaaa Aaaaaaaaa Aaaaa Aaa Aaaaaa Aa Aaaaaaa Aaaa Aaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaaa Aaaaaaa Aa a Aaaa Aaa Aaaaaaaaa Aaaaaa Aaaaaaaaaa Aa Aaaaaaaaa Aa Aaaaaaa Aa Aaaaaaaaaa Aaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaa Aaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaaa Aa a Aaaa Aaaaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaaa Aa Aaaa Aaaaa Aa Aaaaaaaaaaaaaaaa Aaaaaa Aaaaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaa Aa Aaaaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaaaaaaa Aaaaa Aaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaa Aaaaaaaaaaa Aaaaaaaaa
a Aaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaa Aaaa Aaaaaaaa Aaaaa Aaaaaaaaa Aaa Aaaaaaaaaaaaaa Aaaa Aaaaaaaaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaa Aaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aa Aaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaa Aaa Aaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaa Aaa Aaaaaaaa
a Aaaaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaaa Aaaaa Aaaa Aa Aaaaa Aaaaaa Aa Aaaaa Aaaa Aaa Aaaaaa Aaa Aaaaaa Aaaa Aaaa Aaa Aaaa Aaa Aaaaa Aaaaa Aaaaa Aaa Aaa Aaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaa Aaaaa Aaaaaaaaaaaaa Aaaa Aaaaa Aaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaa Aaaaaa
a Aaaaaaaaaaaaaaaa Aaaaa Aaa Aaaaa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaa Aaaaa Aaaaaaaa Aaaa a Aaaaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaa Aaa Aaaaaaa Aaa Aaa Aaaaa Aaaaaa Aaaaaaaa Aa Aaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaa Aaaa Aaa Aaa Aaaa Aaaaaaaaaaaa Aaaaaaaaaaaaa
🔒Four reusable audit directions for percent-encoded canonicalisation bypasses across WebKit policy layers, with specific starting points beyond CSP path matching.
Subscribe to read more