CVE-2021-23509 (published 2021-11-03T18:15Z)

This affects the package json-ptr before 3.0.0. A type confusion vulnerability can lead to a bypass of CVE-2020-7766 when the user-provided keys used in the pointer parameter are arrays.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
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-843
cwe_name: Access of Resource Using Incompatible Type ('Type Confusion')
description: The product allocates or initializes a resource such as a pointer, object, or variable using one type, but it later accesses that resource using a type that is incompatible with the original type.
extended_description: 
            When the product accesses the resource using an incompatible type, this could trigger logical errors because the resource does not have expected properties. In languages without memory safety, such as C and C++, type confusion can lead to out-of-bounds memory access.
            While this weakness is frequently associated with unions when parsing data with many different embedded object types in C, it can be present in any application that can interpret the same variable or memory location in multiple ways.
            This weakness is not unique to C and C++. For example, errors in PHP applications can be triggered by providing array parameters when scalars are expected, or vice versa. Languages such as Perl, which perform automatic conversion of a variable of one type when it is accessed as if it were another type, can also contain these issues.
         
         

Fix information:
repo_url: https://github.com/flitbit/json-ptr
commit_details: repo_url: https://github.com/flitbit/json-ptr
author: Phillip Clark
author_date: 2021-10-26 09:30:22-06:00
msg: Fixed vulnerability reported by Snyk
repository: repo_url: https://github.com/flitbit/json-ptr
repo_name: flitbit/json-ptr
description: A complete implementation of JSON Pointer (RFC 6901) for nodejs and modern browsers.
repo_language: TypeScript
owner: flitbit
file_changes: filename: index.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,4 @@
+export * from './pointer.spec';
+export * from './ptr.spec';
+export * from './reference.spec';
+export * from './utils.spec';

programming_language: TypeScript
filename: utils.spec.ts
change_type: ModificationType.MODIFY
diff: @@ -156,6 +156,12 @@ describe('Utils', () => {
     });
   });
 
+  interface Prototyped {
+    __proto__?: { polluted: string };
+    constructor?: { polluted: string };
+    prototype?: { polluted: string };
+  }
+
   describe('setValueAtPath()', () => {
     it('throws when target undefined', () => {
       expect(() => setValueAtPath(undefined, 0, ['foo'])).to.throw(
@@ -181,6 +187,42 @@ describe('Utils', () => {
       expect(setValueAtPath(data, 'VV', ['one', 5], true)).to.be.undefined;
       expect(data.one[5]).to.eql('VV');
     });
+
+    it('will prevent __proto__ from being polluted', () => {
+      expect(() => {
+        setValueAtPath({}, 'yes', ['__proto__', 'polluted'], true);
+      }).to.throw('Attempted prototype pollution disallowed.');
+      const prototyped = {} as unknown as Prototyped;
+      expect(prototyped.__proto__?.polluted).to.not.eql('yes');
+    });
+    it('will prevent .constructor from being polluted', () => {
+      expect(() => {
+        setValueAtPath({}, 'yes', ['constructor', 'polluted'], true);
+      }).to.throw('Attempted prototype pollution disallowed.');
+      const prototyped = {} as unknown as Prototyped;
+      expect(prototyped.constructor?.polluted).to.not.eql('yes');
+    });
+    it('will prevent .prototype from being polluted', () => {
+      expect(() => {
+        setValueAtPath({}, 'yes', ['prototype', 'polluted'], true);
+      }).to.throw('Attempted prototype pollution disallowed.');
+      const prototyped = {} as unknown as Prototyped;
+      expect(prototyped.prototype?.polluted).to.not.eql('yes');
+    });
+    it('will prevent __proto__ from being polluted by javascript', () => {
+      expect(() => {
+        setValueAtPath(
+          {},
+          'yes',
+          // not allowed in TS depending on tsconfig, but hackable in JS:
+          [['__proto__'], 'polluted'] as unknown as string[],
+          true,
+        );
+        const prototyped = {} as unknown as Prototyped;
+        expect(prototyped.__proto__?.polluted).to.not.eql('yes');
+        expect(prototyped.__proto__).to.be.undefined;
+      }).to.throw('PathSegments must be a string or a number.');
+    });
   });
 
   describe('unsetValueAtPath()', () => {
@@ -206,5 +248,29 @@ describe('Utils', () => {
         expected,
       );
     });
+    it('will prevent __proto__ from being polluted', () => {
+      expect(() => {
+        unsetValueAtPath({}, ['__proto__', 'polluted']);
+      }).to.throw('Attempted prototype pollution disallowed.');
+    });
+    it('will prevent .constructor from being polluted', () => {
+      expect(() => {
+        unsetValueAtPath({}, ['constructor', 'polluted']);
+      }).to.throw('Attempted prototype pollution disallowed.');
+    });
+    it('will prevent .prototype from being polluted', () => {
+      expect(() => {
+        unsetValueAtPath({}, ['prototype', 'polluted']);
+      }).to.throw('Attempted prototype pollution disallowed.');
+    });
+    it('will prevent __proto__ from being polluted by javascript', () => {
+      expect(() => {
+        unsetValueAtPath(
+          {},
+          // not allowed in TS depending on tsconfig, but hackable in JS:
+          [['__proto__'], 'polluted'] as unknown as string[],
+        );
+      }).to.throw('PathSegments must be a string or a number.');
+    });
   });
 });

programming_language: TypeScript
filename: util.ts
change_type: ModificationType.MODIFY
diff: @@ -245,6 +245,9 @@ export function setValueAtPath(
   let p: number;
   while (++cursor < len) {
     step = path[cursor];
+    if (typeof step !== 'string' && typeof step !== 'number') {
+      throw new TypeError('PathSegments must be a string or a number.');
+    }
     if (
       step === '__proto__' ||
       step === 'constructor' ||
@@ -318,6 +321,16 @@ export function unsetValueAtPath(target: unknown, path: PathSegments): unknown {
   let p: number;
   while (++cursor < len) {
     step = path[cursor];
+    if (typeof step !== 'string' && typeof step !== 'number') {
+      throw new TypeError('PathSegments must be a string or a number.');
+    }
+    if (
+      step === '__proto__' ||
+      step === 'constructor' ||
+      step === 'prototype'
+    ) {
+      throw new Error('Attempted prototype pollution disallowed.');
+    }
     if (Array.isArray(it)) {
       p = toArrayIndexReference(it, step);
       if (p >= it.length) return undefined;

programming_language: TypeScript