Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionPrivilege escalation in the Netmonitor component
ComponentCore
Bug ClassLogic Error
Tracker2010743
Fix commit9fd39d416d93 (firefox) +51/-39
CISA KEVNot listed
CreditedCody
Disclosed2026-02-24

Changed Functions

FunctionChangeNotes
getProperties
devtools/client/netmonitor/src/components/request-details/RequestPanel.js
modified
if
devtools/client/netmonitor/src/components/request-details/RequestPanel.js
modified
toggleRawRequestPayload
devtools/client/netmonitor/src/components/request-details/RequestPanel.js
modified
if
devtools/client/netmonitor/src/utils/request-utils.js
modified
for
devtools/client/netmonitor/src/utils/request-utils.js
modified

Files Changed

  • devtools/client/netmonitor/src/components/request-details/RequestPanel.js
  • devtools/client/netmonitor/src/utils/request-utils.js
  • devtools/client/netmonitor/test/browser_net_complex-params.js
  • devtools/client/netmonitor/test/html_params-test-page.html
diff --git a/devtools/client/netmonitor/src/components/request-details/RequestPanel.js b/devtools/client/netmonitor/src/components/request-details/RequestPanel.js
index 9ff470f1a0c..0f652e108d3 100644
--- a/devtools/client/netmonitor/src/components/request-details/RequestPanel.js
+++ b/devtools/client/netmonitor/src/components/request-details/RequestPanel.js
@@ -124,30 +124,34 @@ class RequestPanel extends Component {
   }
 
   /**
-   * Mapping array to dict for TreeView usage.
-   * Since TreeView only support Object(dict) format.
-   * This function also deal with duplicate key case
-   * (for multiple selection and query params with same keys)
+   * This maps an array to a dictionary for TreeView usage,
+   * sincs the treeView only supports the Object(dict) format.
    *
-   * This function is not sorting result properties since it can
-   * results in unexpected order of params. See bug 1469533
+   * This function also deals with the duplicate key scenario
+   * (i.e multiple selections and query params with same keys)
+   *
+   * Note: This is not sorting the result properties since it can
+   * result in an unexpected order of parameters. See bug 1469533
+   *
+   * @param {object[]} arrOfKeyValuePairs - An array of key-value pairs or form params.
+   * @param {string} arrOfKeyValuePairs[].name
+   * @param {string|Array} arrOfKeyValuePairs[].value
    *
-   * @param {object[]} arr - key-value pair array or form params
    * @returns {object} Rep compatible object
    */
-  getProperties(arr) {
-    return arr.reduce((map, obj) => {
-      const value = map[obj.name];
-      if (value || value === "") {
-        if (typeof value !== "object") {
-          map[obj.name] = [value];
+  getProperties(arrOfKeyValuePairs) {
+    return arrOfKeyValuePairs.reduce((dict, { name, value }) => {
+      if (name in dict) {
+        const dictValue = dict[name];
+        if (!Array.isArray(dictValue)) {
+          dict[name] = [dictValue];
         }
-        map[obj.name].push(obj.value);
+        dict[name].push(value);
       } else {
-        map[obj.name] = obj.value;
+        dict[name] = value;
       }
-      return map;
-    }, {});
+      return dict;
+    }, Object.create(null));
   }
 
   toggleRawRequestPayload() {
@@ -205,10 +209,9 @@ class RequestPanel extends Component {
 
     // Form Data section
     if (formDataSections && formDataSections.length) {
-      const sections = formDataSections.filter(str => /\S/.test(str)).join("&");
       component = PropertiesView;
       componentProps = {
-        object: this.getProperties(parseFormData(sections)),
+        object: this.getProperties(parseFormData(formDataSections)),
         filterText,
         targetSearchResult,
         defaultSelectFirstNode: false,
diff --git a/devtools/client/netmonitor/src/utils/request-utils.js b/devtools/client/netmonitor/src/utils/request-utils.js
index 31f4bfe037d..564fdda28ab 100644
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -83,7 +83,6 @@ async function getFormDataSections(
       }
     }
   }
-
   return formDataSections;
 }
 
@@ -428,28 +427,29 @@ function parseQueryString(query) {
 /**
  * Parse a string of formdata sections into its components
  *
- * @param {string} sections - sections of formdata joined by &
- * @return {Array} array of formdata params { name, value }
+ * @param {Array<string>} sections Array of sections of formdata
+ *                                 e.g ["", "a=x&b=y", "c=z"]
+ * @return {Array<object>}  Array of formdata params
+ *                          e.g [{ name: 'a', value: 'x' }, { name: 'b', value: 'y'}, { name: 'c', value: 'z'}]
  */
 function parseFormData(sections) {
-  if (!sections) {
+  if (!sections || !sections.length) {
     return [];
   }
-
-  return sections
-    .replace(/^&/, "")
-    .split("&")
-    .map(e => {
-      const firstEqualSignIndex = e.indexOf("=");
-      const paramName =
-        firstEqualSignIndex !== -1 ? e.slice(0, firstEqualSignIndex) : e;
-      const paramValue =
-        firstEqualSignIndex !== -1 ? e.slice(firstEqualSignIndex + 1) : "";
-      return {
-        name: paramName ? getUnicodeUrlPath(paramName) : "",
-        value: paramValue ? getUnicodeUrlPath(paramValue) : "",
-      };
+  const formDataParams = [];
+  const searchStr = sections
+    // Filter out empty sections
+    .filter(str => /\S/.test(str))
+    .join("&");
+
+  const params = new URLSearchParams(searchStr);
+  for (const [key, value] of params) {
+    formDataParams.push({
+      name: getUnicodeUrlPath(key),
+      value: getUnicodeUrlPath(value),
     });
+  }
+  return formDataParams;
 }
 
 /**
diff --git a/devtools/client/netmonitor/test/browser_net_complex-params.js b/devtools/client/netmonitor/test/browser_net_complex-params.js
index 7fcfcf88c26..a757211de2d 100644
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -20,7 +20,7 @@ add_task(async function () {
   store.dispatch(Actions.batchEnable(false));
 
   // Execute requests.
-  await performRequests(monitor, tab, 12);
+  await performRequests(monitor, tab, 13);
 
   const requestListItems = document.querySelectorAll(
     ".network-monitor .request-list-item"
@@ -49,7 +49,7 @@ add_task(async function () {
   await testRequestWithFormattedView(
     monitor,
     requestListItems[2],
-    "?foo",
+    "foo",
     "bar=123=xyz",
     "?foo=bar=123=xyz",
     1
@@ -92,6 +92,14 @@ add_task(async function () {
     '{ "foo": "bar" }',
     1
   );
+  await testRequestWithFormattedView(
+    monitor,
+    requestListItems[12],
+    "__proto__",
+    "evil_value",
+    "__proto__=evil_value",
+    1
+  );
 
   await teardown(monitor);
 });
diff --git a/devtools/client/netmonitor/test/html_params-test-page.html b/devtools/client/netmonitor/test/html_params-test-page.html
index 3d657a5e87e..a3a799b76a6 100644
--- a/devtools/client/netmonitor/test/html_params-test-page.html
+++ b/devtools/client/netmonitor/test/html_params-test-page.html
@@ -71,6 +71,7 @@
         await get("baz", "?species=in=(52,60)");
         await get("baz", "?a=&a=b");
         await get("baz", "?a=b&a=c&d=1");
+        await post("baz", "", urlencoded, "__proto__=evil_value");
       }
     </script>
   </body>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/devtools/client/netmonitor/test/browser_net_complex-params.js b/devtools/client/netmonitor/test/browser_net_complex-params.js
index 7fcfcf88c26..a757211de2d 100644
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -20,7 +20,7 @@ add_task(async function () {
   store.dispatch(Actions.batchEnable(false));
 
   // Execute requests.
-  await performRequests(monitor, tab, 12);
+  await performRequests(monitor, tab, 13);
 
   const requestListItems = document.querySelectorAll(
     ".network-monitor .request-list-item"
@@ -49,7 +49,7 @@ add_task(async function () {
   await testRequestWithFormattedView(
     monitor,
     requestListItems[2],
-    "?foo",
+    "foo",
     "bar=123=xyz",
     "?foo=bar=123=xyz",
     1
@@ -92,6 +92,14 @@ add_task(async function () {
     '{ "foo": "bar" }',
     1
   );
+  await testRequestWithFormattedView(
+    monitor,
+    requestListItems[12],
+    "__proto__",
+    "evil_value",
+    "__proto__=evil_value",
+    1
+  );
 
   await teardown(monitor);
 });
diff --git a/devtools/client/netmonitor/test/html_params-test-page.html b/devtools/client/netmonitor/test/html_params-test-page.html
index 3d657a5e87e..a3a799b76a6 100644
--- a/devtools/client/netmonitor/test/html_params-test-page.html
+++ b/devtools/client/netmonitor/test/html_params-test-page.html
@@ -71,6 +71,7 @@
         await get("baz", "?species=in=(52,60)");
         await get("baz", "?a=&a=b");
         await get("baz", "?a=b&a=c&d=1");
+        await post("baz", "", urlencoded, "__proto__=evil_value");
       }
     </script>
   </body>
Loading diff…