CVE-2023-26136 (published 2023-07-01T05:15Z)

Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in rejectPublicSuffixes=false mode. This issue arises from the manner in which the objects are initialized.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-1321
cwe_name: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
extended_description: 
				By adding or modifying attributes of an object prototype, it is possible to create attributes that exist on every object, or replace critical attributes with malicious ones. This can be problematic if the product depends on existence or non-existence of certain attributes, or uses pre-defined attributes of object prototype (such as hasOwnProperty, toString or valueOf).
				This weakness is usually exploited by using a special attribute of objects called proto,  constructor or prototype. Such attributes give access to the object prototype. This weakness is often found in code that assigns object attributes based on user input, or merges or clones objects recursively.
			
			

Fix information:
repo_url: https://github.com/salesforce/tough-cookie
commit_details: repo_url: https://github.com/salesforce/tough-cookie
author: Colin Casey
author_date: 2023-06-05 12:13:22-03:00
msg: Prevent prototype pollution in cookie memstore (#283)

All occurrences of new object creation in `memstore.js` have been changed from `{}` (i.e.; `Object.create(Object.prototype)` to `Object.create(null)` so that we are using object instances that do not have a prototype property that can be polluted.

@fixes #282
repository: repo_url: https://github.com/salesforce/tough-cookie
repo_name: salesforce/tough-cookie
description: RFC6265 Cookies and CookieJar for Node.js
repo_language: TypeScript
owner: salesforce
file_changes: filename: memstore.js
change_type: ModificationType.MODIFY
diff: @@ -39,7 +39,7 @@ class MemoryCookieStore extends Store {
   constructor() {
     super();
     this.synchronous = true;
-    this.idx = {};
+    this.idx = Object.create(null);
     const customInspectSymbol = getCustomInspectSymbol();
     if (customInspectSymbol) {
       this[customInspectSymbol] = this.inspect;
@@ -111,10 +111,10 @@ class MemoryCookieStore extends Store {
 
   putCookie(cookie, cb) {
     if (!this.idx[cookie.domain]) {
-      this.idx[cookie.domain] = {};
+      this.idx[cookie.domain] = Object.create(null);
     }
     if (!this.idx[cookie.domain][cookie.path]) {
-      this.idx[cookie.domain][cookie.path] = {};
+      this.idx[cookie.domain][cookie.path] = Object.create(null);
     }
     this.idx[cookie.domain][cookie.path][cookie.key] = cookie;
     cb(null);
@@ -146,7 +146,7 @@ class MemoryCookieStore extends Store {
     return cb(null);
   }
   removeAllCookies(cb) {
-    this.idx = {};
+    this.idx = Object.create(null);
     return cb(null);
   }
   getAllCookies(cb) {
@@ -196,9 +196,9 @@ exports.MemoryCookieStore = MemoryCookieStore;
 function inspectFallback(val) {
   const domains = Object.keys(val);
   if (domains.length === 0) {
-    return "{}";
+    return "[Object: null prototype] {}";
   }
-  let result = "{\n";
+  let result = "[Object: null prototype] {\n";
   Object.keys(val).forEach((domain, i) => {
     result += formatDomain(domain, val[domain]);
     if (i < domains.length - 1) {
@@ -212,7 +212,7 @@ function inspectFallback(val) {
 
 function formatDomain(domainName, domainValue) {
   const indent = "  ";
-  let result = `${indent}'${domainName}': {\n`;
+  let result = `${indent}'${domainName}': [Object: null prototype] {\n`;
   Object.keys(domainValue).forEach((path, i, paths) => {
     result += formatPath(path, domainValue[path]);
     if (i < paths.length - 1) {
@@ -226,7 +226,7 @@ function formatDomain(domainName, domainValue) {
 
 function formatPath(pathName, pathValue) {
   const indent = "    ";
-  let result = `${indent}'${pathName}': {\n`;
+  let result = `${indent}'${pathName}': [Object: null prototype] {\n`;
   Object.keys(pathValue).forEach((cookieName, i, cookieNames) => {
     const cookie = pathValue[cookieName];
     result += `      ${cookieName}: ${cookie.inspect()}`;

programming_language: TypeScript
filename: cookie_jar_test.js
change_type: ModificationType.MODIFY
diff: @@ -809,4 +809,29 @@ vows
       }
     }
   })
+  .addBatch({
+    "Issue #282 - Prototype pollution": {
+      "when setting a cookie with the domain __proto__": {
+        topic: function() {
+          const jar = new tough.CookieJar(undefined, {
+            rejectPublicSuffixes: false
+          });
+          // try to pollute the prototype
+          jar.setCookieSync(
+            "Slonser=polluted; Domain=__proto__; Path=/notauth",
+            "https://__proto__/admin"
+          );
+          jar.setCookieSync(
+            "Auth=Lol; Domain=google.com; Path=/notauth",
+            "https://google.com/"
+          );
+          this.callback();
+        },
+        "results in a cookie that is not affected by the attempted prototype pollution": function() {
+          const pollutedObject = {};
+          assert(pollutedObject["/notauth"] === undefined);
+        }
+      }
+    }
+  })
   .export(module);

programming_language: JavaScript