Medium CVSS 7.1 webkit Bypass 🔧 Commit mapped

Overview

Medium
Severity
7.1
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionA malicious website may be able to process restricted web content outside the sandbox
ComponentWebKit UIProcess
Bug ClassBypass
Tracker315004
Fix commitf23ffb5a8450 (WebKit/WebKit) +24/-0
CWECWE-284
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L
CISA KEVNot listed
CreditedAaron Grattafiori - NVIDIA AI Red Team
Disclosed2026-06-29

Background

data: URL
A URL that inlines its bytes; can carry attacker-controlled content and an unshowable MIME type.
Download of unshowable responses
The navigation delegate’s stock behavior to download responses it cannot display.
isFromAPIClientRequest
Marks navigations initiated by the API client (e.g. -loadRequest:) vs by web content.

Root Cause Analysis

This fixes a sandbox/content-restriction bypass where a data: URL navigation could be silently turned into a file download of attacker-controlled bytes. When a navigation response has an unshowable MIME type, the UI process may hand it to the navigation delegate, whose stock behavior is to download unshowable responses. WebPageProxy::receivedNavigationResponsePolicyDecision did not special-case data: URLs, so a compromised Web Content process could navigate the top frame to a data: URL with an unshowable MIME type and rely on that stock ‘download unshowable responses’ path to write attacker-chosen bytes to disk without any user interaction — processing restricted content/writing files outside the intended flow.

The fix, in the UI process, converts action == PolicyAction::Download to PolicyAction::Ignore when request.url().protocolIsData() and the navigation was not driven by the API client (!navigation || !navigation->isFromAPIClientRequest()); legitimate data: downloads (<a download>, explicit download API, or client -loadRequest:) do not reach this path. A defense-in-depth mirror is added in WebCore’s DocumentLoader::continueAfterContentPolicy (disallowDataRequest()), and APINavigation gains isFromAPIClientRequest() to distinguish client-initiated loads.

The restored invariant is that a data: URL response is only downloadable when the load was initiated by the user or the API client. Established by the diff.

Key insight
A data: URL response should only be downloadable when initiated by the user or API client; relying on stock ‘download unshowable responses’ let web content write files silently.

Attack Path

  1. Compromise or drive the Web Content process Attacker-controlled web content (or a compromised WebContent process) initiates a top-frame navigation.
  2. Navigate to a data: URL with an unshowable MIME type The navigation targets data:<unshowable-mime>,<attacker bytes>, which cannot be displayed.
  3. Response policy becomes Download Pre-patch, the navigation delegate’s stock ‘download unshowable responses’ behavior turns the response into a download with no user gesture.
  4. Write attacker bytes to disk The data: URL’s attacker-controlled content is written to a file automatically, outside any user-initiated download flow.

Impact Assessment

A restricted-content/sandbox issue: a compromised WebContent could write attacker-controlled bytes to disk via a silent data: URL download without user interaction; medium. Not a memory primitive but an unauthorized file-write / content-processing bypass.

Changed Functions

FunctionChangeNotes
WebPageProxy::receivedNavigationResponsePolicyDecision
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Converts a Download policy to Ignore when the request is a data: URL and the navigation is not from the API client, blocking silent data:-URL downloads driven by web content.
DocumentLoader::continueAfterContentPolicy
Source/WebCore/loader/DocumentLoader.cpp
modified Defense-in-depth: refuses the download and stops the load when disallowDataRequest() holds, mirroring the UI-process check for ports/flows that don't cross that boundary.
API::Navigation::isFromAPIClientRequest
Source/WebKit/UIProcess/API/APINavigation.h
added Exposes whether the navigation request originated from the API client, used to permit legitimate client-initiated data: downloads.

Files Changed

  • Source/WebCore/loader/DocumentLoader.cpp
  • Source/WebKit/UIProcess/API/APINavigation.h
  • Source/WebKit/UIProcess/WebPageProxy.cpp

Audit Directions

  • Policy paths that auto-download
    Audit navigation-response policy where Download can be reached for data:/blob: URLs without a user/API-client gate.
  • Defense-in-depth mirrors
    Check that WebContent-side loaders (DocumentLoader) mirror UI-process restrictions for ports/flows not crossing that boundary.
diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp
index 9ee9f3231e57..d757709c2535 100644
--- a/Source/WebCore/loader/DocumentLoader.cpp
+++ b/Source/WebCore/loader/DocumentLoader.cpp
@@ -1142,6 +1142,16 @@ void DocumentLoader::continueAfterContentPolicy(PolicyAction policy)
             return;
         }
 
+        // Defense-in-depth: refuse to download a data: URL through a top-frame navigation that
+        // wasn't initiated by the user or the API client, mirroring the existing check in the
+        // PolicyAction::Use branch. The primary defense lives in the UI process; this guards
+        // ports / future flows that don't share that boundary.
+        if (disallowDataRequest()) {
+            protect(frameLoader())->policyChecker().cannotShowMIMEType(m_response);
+            stopLoadingForPolicyChange();
+            return;
+        }
+
         if (RefPtr mainResourceLoader = this->mainResourceLoader())
             InspectorInstrumentation::continueWithPolicyDownload(*frame, *mainResourceLoader->identifier(), *this, m_response);
 
diff --git a/Source/WebKit/UIProcess/API/APINavigation.h b/Source/WebKit/UIProcess/API/APINavigation.h
index 75076f101b82..69ca01776969 100644
--- a/Source/WebKit/UIProcess/API/APINavigation.h
+++ b/Source/WebKit/UIProcess/API/APINavigation.h
@@ -132,6 +132,7 @@ class Navigation : public ObjectImpl<Object::Type::Navigation> {
 
     bool wasUserInitiated() const { return m_lastNavigationAction && !!m_lastNavigationAction->userGestureTokenIdentifier; }
     bool NODELETE isRequestFromClientOrUserInput() const;
+    bool isFromAPIClientRequest() const { return m_requestIsFromClientInput; }
     void NODELETE markRequestAsFromClientInput();
     void markAsFromLoadData() { m_isFromLoadData = true; }
     bool isFromLoadData() const { return m_isFromLoadData; }
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index 572ea8d6537f..ceee9ead3799 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -5924,6 +5924,19 @@ void WebPageProxy::receivedNavigationResponsePolicyDecision(WebCore::PolicyActio
     if (!hasRunningProcess())
         return completionHandler(PolicyDecision { });
 
+    // Refuse to convert a navigation response into a download when the request is a data: URL,
+    // unless the navigation was driven by the API client (e.g. -loadRequest: with a data: URL).
+    // Otherwise a compromised Web Content process could navigate to a data: URL with an unshowable
+    // MIME type and rely on the navigation delegate's stock "download unshowable responses"
+    // behavior to write attacker-controlled bytes to disk without user interaction. Legitimate
+    // downloads of data: URLs go through the navigation action policy (e.g. <a href="data:..." download>)
+    // or the explicit download API, neither of which reaches this code path.
+    if (action == PolicyAction::Download && request.url().protocolIsData()
+        && (!navigation || !navigation->isFromAPIClientRequest())) {
+        WEBPAGEPROXY_RELEASE_LOG(Loading, "receivedNavigationResponsePolicyDecision: refusing to download data: URL not initiated by API client");
+        action = PolicyAction::Ignore;
+    }
+
     Ref pageLoadState = internals().pageLoadState;
     auto transaction = pageLoadState->transaction();
 
Loading diff…

Original Bug Report

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