Medium CVSS 6.1 firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
6.1
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionIn multipart/x-mixed-replace responses, <code>Content-Disposition: attachment</code> in the response header was not respected and did not force a download, which could allow XSS attacks.
ComponentNetworking
Bug ClassLogic Error
Tracker1914521
Fix commitb24f93853b53 (firefox) +17/-5
CWECWE-79 (Cross-site scripting)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CISA KEVNot listed
CreditedMasato Kinugawa
Disclosed2024-10-29

Background

`multipart/x-mixed-replace`
A streaming response type in which the server sends a sequence of body parts, each replacing the previously rendered one, commonly used for server-push and live-updating resources.
`nsMultiMixedConv`
Firefox’s stream converter that parses a multipart/x-mixed-replace stream and dispatches each part as its own channel (nsPartChannel) to upper layers.
`Content-Disposition: attachment`
An HTTP response header directive instructing the browser to download the resource rather than render it inline, a common defense against rendering attacker-controlled content in a document context.
`SetContentDisposition`
The nsPartChannel method that records the disposition value later consulted to decide whether a part is displayed inline or forced to download.

Root Cause Analysis

When nsMultiMixedConv processed a multipart/x-mixed-replace response, the top-level response header Content-Disposition was read during OnStartRequest for other headers (such as Content-Security-Policy) but the disposition itself was never captured for the outer document. In SendStart, each part channel had its disposition set via mPartChannel->SetContentDisposition(mContentDisposition), where mContentDisposition reflected only per-part heading data and defaulted to empty, so an attachment disposition present on the root response was silently dropped. The violated invariant is that a server’s Content-Disposition: attachment on the response must force a download and prevent inline rendering; because the value never propagated to the part channels, attacker-supplied parts were rendered inline as active documents instead of being downloaded.

The fix records the root response’s disposition into a new mRootContentDisposition member during OnStartRequest, then in SendStart prefers that value (if (!mRootContentDisposition.IsEmpty())) over the per-part value when setting the part channel’s disposition. This restores the invariant so a top-level attachment directive is respected across every part of the multipart stream.

Key insight
The single core mistake was failing to propagate the outer response’s Content-Disposition header to the individual part channels, letting it be overwritten by an empty per-part value; the fix captures the root disposition in mRootContentDisposition and applies it in preference so attachment is honored.

Attack Path

  1. Serve a multipart response An attacker controls or influences a server endpoint that returns a multipart/x-mixed-replace response with Content-Disposition: attachment set on the top-level response header.
  2. Header is captured but ignored nsMultiMixedConv::OnStartRequest reads other root headers but never stores the disposition, so the intended download directive is lost before parts are dispatched.
  3. Part channels get empty disposition In nsMultiMixedConv::SendStart, SetContentDisposition is called with the empty mContentDisposition, so no part is flagged for download.
  4. Inline rendering as a document Each attacker-controlled part is treated as a displayable top-level document rather than an attachment, allowing HTML/script content to execute in the origin’s context.
  5. XSS execution The rendered active content runs as script in the loading context, achieving cross-site scripting that the attachment disposition was meant to prevent.

Impact Assessment

An attacker gains cross-site scripting execution in the document/content context of the affected origin by delivering active content through a multipart/x-mixed-replace response that should have been forced to download. The precondition is that the server response carries Content-Disposition: attachment at the top level while using the multipart content type, and that a victim loads that response in the browser. The impact is rated moderate because it requires a multipart response configuration and defeats a download-based mitigation rather than granting unconditional code execution.

Files Changed

  • netwerk/streamconv/converters/nsMultiMixedConv.cpp
  • netwerk/streamconv/converters/nsMultiMixedConv.h

Audit Directions

  • Header propagation across converters
    Review stream converters and channel-splitting code (like nsMultiMixedConv) for security-relevant response headers (Content-Disposition, Content-Security-Policy, X-Content-Type-Options) that are read for the root but not carried onto derived per-part or sub-resource channels.
  • Empty-value overwrites
    Look for places where a security directive is unconditionally set from a per-item field that may be empty, silently clobbering a stronger value inherited from a parent context.
  • Multipart and streaming paths
    Audit other multipart, chunked, or push-style response handlers to confirm that download-forcing and rendering-suppression directives are honored on every emitted part, not just the first or the outer response.
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.cpp b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
index e1ccfda303c..5c4ea72a86e 100644
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -474,6 +474,12 @@ nsMultiMixedConv::OnStartRequest(nsIRequest* request) {
     if (NS_SUCCEEDED(rv)) {
       mRootContentSecurityPolicy = csp;
     }
+    nsCString contentDisposition;
+    rv = httpChannel->GetResponseHeader("content-disposition"_ns,
+                                        contentDisposition);
+    if (NS_SUCCEEDED(rv)) {
+      mRootContentDisposition = contentDisposition;
+    }
   } else {
     // try asking the channel directly
     rv = mChannel->GetContentType(contentType);
@@ -850,7 +856,11 @@ nsresult nsMultiMixedConv::SendStart() {
   rv = mPartChannel->SetContentLength(mContentLength);
   if (NS_FAILED(rv)) return rv;
 
-  mPartChannel->SetContentDisposition(mContentDisposition);
+  if (!mRootContentDisposition.IsEmpty()) {
+    mPartChannel->SetContentDisposition(mRootContentDisposition);
+  } else {
+    mPartChannel->SetContentDisposition(mContentDisposition);
+  }
 
   // Each part of a multipart/replace response can be used
   // for the top level document.  We must inform upper layers
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.h b/netwerk/streamconv/converters/nsMultiMixedConv.h
index 4f773a69eb6..694150d69c2 100644
--- a/netwerk/streamconv/converters/nsMultiMixedConv.h
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.h
@@ -151,15 +151,17 @@ class nsMultiMixedConv : public nsIStreamConverter {
   nsCOMPtr<nsIStreamListener> mFinalListener;  // this guy gets the converted
                                                // data via his OnDataAvailable()
 
-  nsCOMPtr<nsIChannel>
-      mChannel;  // The channel as we get in in OnStartRequest call
-  RefPtr<nsPartChannel> mPartChannel;  // the channel for the given part we're
-                                       // processing. one channel per part.
+  // The channel as we get it in OnStartRequest call
+  nsCOMPtr<nsIChannel> mChannel;
+  // the channel for the given part we're
+  // processing. one channel per part.
+  RefPtr<nsPartChannel> mPartChannel;
   nsCOMPtr<nsISupports> mContext;
   nsCString mContentType;
   nsCString mContentDisposition;
   nsCString mContentSecurityPolicy;
   nsCString mRootContentSecurityPolicy;
+  nsCString mRootContentDisposition;
   uint64_t mContentLength{UINT64_MAX};
   uint64_t mTotalSent{0};
 
Loading diff…