CVE-2024-9394
Overview
Background
- JSON Viewer
- a DevTools feature implemented in
converter-child.jsthat interceptsapplication/jsonresponses and renders them as an interactive HTML page served from the privilegedresource://devtoolsorigin. - Stream converter
- an
nsIStreamConverterimplementation (hereConverter) that Firefox invokes to transform a channel’s response body from one MIME type to another, deciding the output type viagetConvertedType. - `nsIMultiPartChannel`
- an interface implemented by channels carrying a
multipart/x-mixed-replaceresponse, where a single request yields multiple sequential document parts. - Site Isolation
- a desktop-only architecture that separates web content into per-site processes, limiting the reach of a compromised origin to same-site documents.
Root Cause Analysis
The vulnerable path is Converter.prototype.getConvertedType, which unconditionally returned "text/html" for any JSON response, causing the JSON Viewer to load privileged resource://devtools markup to display the data. The implicit invariant was that a single JSON response maps to a single JSON Viewer document, but a multipart/x-mixed-replace response delivered through an nsIMultiPartChannel violated this by supplying attacker-controlled subsequent parts within the same privileged origin, letting crafted content run as JavaScript under resource://devtools. Because that origin can issue cross-origin fetches for JSON, the injected script could exfiltrate cross-origin JSON content.
The fix makes getConvertedType accept the originating channel, detect when it instanceof Ci.nsIMultiPartChannel, and throw an NS_ERROR_FAILURE exception instead of converting, so multipart responses never reach the privileged renderer.
nsIMultiPartChannel channels in getConvertedType before any conversion to "text/html" occurs.Attack Path
- Serve a multipart JSON response
The attacker configures a server to return a
multipart/x-mixed-replaceresponse with a JSON content type, delivered to Firefox over annsIMultiPartChannel. - Trigger the JSON Viewer
Firefox routes the JSON MIME type to the
Converterstream converter, whosegetConvertedTypereturns"text/html"and loads the JSON Viewer underresource://devtools. - Inject a hostile part
A later part of the multipart stream carries crafted content that executes as JavaScript within the privileged
resource://devtoolsorigin rather than as inert displayed data. - Read cross-origin JSON The injected script uses the privileged origin’s fetch capability to retrieve JSON from other origins and exfiltrate it.
Impact Assessment
resource://devtools origin within the content/DevTools context, enabling theft of cross-origin JSON data. On desktop this is constrained to same-site documents by Site Isolation, but on Android full cross-origin access is possible. The only precondition is that the victim navigates to or fetches an attacker-controlled multipart JSON response.Changed Functions
| Function | Change | Notes |
|---|---|---|
asyncConvertDatadevtools/client/jsonview/converter-child.js |
modified | |
getConvertedTypedevtools/client/jsonview/converter-child.js |
modified | |
ifdevtools/client/jsonview/converter-child.js |
modified |
Files Changed
devtools/client/jsonview/converter-child.js
Audit Directions
- Privileged-origin renderersReview other
nsIStreamConverterand content-type handlers that load UI intoresource://orchrome://origins to ensure they reject or sanitize multipart and streaming channels. - Channel-type assumptionsLook for code that inspects only a response’s MIME type while ignoring the concrete channel interface (such as
nsIMultiPartChannel), since a single logical response may deliver multiple attacker-controlled parts. - Cross-origin fetch from privileged contextsAudit features running under privileged origins that can perform cross-origin requests, verifying that untrusted document content can never be injected into that origin.
Patch
diff --git a/devtools/client/jsonview/converter-child.js b/devtools/client/jsonview/converter-child.js
index e249de01802..3c8c9fcc5dc 100644
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -72,7 +72,13 @@ Converter.prototype = {
asyncConvertData(fromType, toType, listener) {
this.listener = listener;
},
- getConvertedType() {
+ getConvertedType(_fromType, channel) {
+ if (channel instanceof Ci.nsIMultiPartChannel) {
+ throw new Components.Exception(
+ "JSONViewer doesn't support multipart responses.",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
return "text/html";
},