Medium CVSS 8.1 webkit Bypass 🔧 Commit mapped

Overview

Medium
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may prevent Content Security Policy from being enforced
ComponentWebCore Page
Bug ClassBypass
Tracker308675
Fix commit9a19d07c4f53 (WebKit/WebKit) +117/-5
CWECWE-20, CWE-116 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CISA KEVNot listed
CreditedCantina
Disclosed2026-05-11

Background

CSP source path matching
CSP restricts sources by path; matching must follow CSP3’s per-segment algorithm.
Percent-encoding
%2e (.) and %2f (/) encodings that, if decoded at the wrong time, can change a path’s structure.
Path traversal
Using ../ (or its encoding) to escape a directory restriction.

Root Cause Analysis

This fixes a Content Security Policy path-matching bypass via percent-encoding, by rewriting ContentSecurityPolicySource::pathMatches() to follow the CSP3 match-paths algorithm.

Before the fix, pathMatches() percent-decoded the ENTIRE URL path once (PAL::decodeURLEscapeSequences(url.path())) and then did a whole-string comparison: startsWith(m_path) for a directory source (path ending in ‘/’) or an exact ==. In addition, ContentSecurityPolicySourceList::parsePath() pre-decoded the source-expression path with decodeURLEscapeSequences. The violated invariant is that CSP path matching must be performed segment-by-segment, decoding each path segment individually (per CSP3), so that encoded separators and traversal sequences cannot change the path’s structure before comparison. Because the old code decoded the whole path first, an attacker could encode a traversal such as %2e%2e (==’..’) or %2f (==’/’) inside the URL: the early full decode turned ‘/trusted/%2e%2e/evil/x.js’ into ‘/trusted/../evil/x.js’, which startsWith(’/trusted/’) and therefore passed the CSP check, while the network layer later normalized the path to ‘/evil/x.js’ when actually fetching — so a resource CSP was meant to block loaded anyway, i.e. the policy was not enforced (the advisory’s impact).

The fix splits both the source path (path A) and the URL path (path B) strictly on ‘/’, requires A to have no more segments than B, requires equal segment counts for an exact (non-trailing-slash) match, drops the trailing empty segment for a directory match, and compares each segment only after percent-decoding that individual segment. parsePath() now stores the raw, undecoded source path so decoding happens consistently per segment at match time. This makes encoded ‘/’-and-’..’ tricks compare as literal, non-matching segments, restoring correct CSP path enforcement. The regression test (path-traversal-bypass-with-percent-encoding) exercises exactly this. Fully established by the diff.

Key insight
CSP path matching must decode per segment (CSP3), not decode the whole path and startsWith; whole-path decoding let %2e%2e/%2f traversal pass the check while the fetch normalized to a blocked path.

Attack Path

  1. Target page ships a path-scoped CSP A page sets a Content Security Policy that allows a resource type only from a directory path, e.g. script-src https://host/trusted/.
  2. Introduce a percent-encoded traversal URL An attacker gets the page to reference a resource URL with encoded traversal inside the allowed prefix, e.g. https://host/trusted/%2e%2e/evil/x.js (or using %2f).
  3. Bypass the CSP path check Pre-patch pathMatches() decodes the whole path to /trusted/../evil/x.js and startsWith(’/trusted/’) returns true, so CSP treats the URL as allowed.
  4. Fetch resolves to the blocked path The network/URL layer normalizes /trusted/../evil/ to /evil/ when fetching, so the resource actually loads from a path CSP intended to block.
  5. CSP is not enforced The attacker-controlled script/resource loads despite the policy, defeating the path restriction the CSP was meant to impose.

Impact Assessment

A CSP enforcement bypass (policy not enforced) enabling loads from paths CSP meant to block; medium-to-high. Enables loading attacker resources despite CSP, no memory safety.

Changed Functions

FunctionChangeNotes
ContentSecurityPolicySource::pathMatches
Source/WebCore/page/csp/ContentSecurityPolicySource.cpp
modified Reimplemented to the CSP3 match-paths algorithm: handles empty and '/' paths, splits source and URL paths on '/', enforces segment-count rules for exact vs directory matches, drops the trailing empty segment for directory matches, and compares each segment only after per-segment percent-decoding — instead of decoding the whole path and doing startsWith/==, which allowed encoded traversal to bypass the check.
ContentSecurityPolicySourceList::parsePath
Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp
modified Stores the raw, undecoded source-expression path (String(...)) instead of pre-decoding it with decodeURLEscapeSequences, so percent-decoding is applied consistently per segment at match time.

Files Changed

  • LayoutTests/http/tests/security/contentSecurityPolicy/path-traversal-bypass-with-percent-encoding-expected.txt
  • LayoutTests/http/tests/security/contentSecurityPolicy/path-traversal-bypass-with-percent-encoding.html
  • Source/WebCore/page/csp/ContentSecurityPolicySource.cpp
  • Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp

Audit Directions

  • Whole-string decode before matching
    Find security comparisons that decodeURLEscapeSequences on a full path/URL before comparing; prefer per-component decoding.
  • Other CSP directive matchers
    Audit host/port/scheme matching for similar spec-divergence that encoding can exploit.
diff --git a/Tools/MobileMiniBrowser/MobileMiniBrowserFramework/SceneDelegate.m b/Tools/MobileMiniBrowser/MobileMiniBrowserFramework/SceneDelegate.m
index 0fa324aaec3a..492142235cbf 100644
--- a/Tools/MobileMiniBrowser/MobileMiniBrowserFramework/SceneDelegate.m
+++ b/Tools/MobileMiniBrowser/MobileMiniBrowserFramework/SceneDelegate.m
@@ -42,8 +42,11 @@ - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session op
     UIWindow *window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
     self.window = window;
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle bundleForClass:[SceneDelegate class]]];
     WebViewController *viewController = (WebViewController *)[storyboard instantiateInitialViewController];
+#pragma clang diagnostic pop
     window.rootViewController = viewController;
 
     WKWebsiteDataStore *dataStore = viewController.dataStore;
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.