From 16d6154e085bef01e99f01330e5a421a7f098afa Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Mon, 9 Sep 2024 08:14:24 -0700 Subject: [PATCH] fix: prevent __proto__ assignment via implicit string --- readme.md | 6 +++--- src/index.js | 2 +- src/merge.js | 2 +- test/suites/pollution.js | 19 ++++++++++++++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index e1e3ad3..804654d 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # dset [![CI](https://github.com/lukeed/dset/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/lukeed/dset/actions) [![codecov](https://badgen.net/codecov/c/github/lukeed/dset)](https://codecov.io/gh/lukeed/dset) -> A tiny (194B) utility for safely writing deep Object values~! +> A tiny (197B) utility for safely writing deep Object values~! For _accessing_ deep object properties, please see [`dlv`](https://github.com/developit/dlv). @@ -17,7 +17,7 @@ $ npm install --save dset There are two "versions" of `dset` available: #### `dset` -> **Size (gzip):** 194 bytes
+> **Size (gzip):** 197 bytes
> **Availability:** [CommonJS](https://unpkg.com/dset/dist/index.js), [ES Module](https://unpkg.com/dset/dist/index.mjs), [UMD](https://unpkg.com/dset/dist/index.min.js) ```js @@ -25,7 +25,7 @@ import { dset } from 'dset'; ``` #### `dset/merge` -> **Size (gzip):** 288 bytes
+> **Size (gzip):** 307 bytes
> **Availability:** [CommonJS](https://unpkg.com/dset/merge/index.js), [ES Module](https://unpkg.com/dset/merge/index.mjs), [UMD](https://unpkg.com/dset/merge/index.min.js) ```js diff --git a/src/index.js b/src/index.js index 75a01fb..85fd6a7 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ export function dset(obj, keys, val) { keys.split && (keys=keys.split('.')); var i=0, l=keys.length, t=obj, x, k; while (i < l) { - k = keys[i++]; + k = ''+keys[i++]; if (k === '__proto__' || k === 'constructor' || k === 'prototype') break; t = t[k] = (i === l) ? val : (typeof(x=t[k])===typeof(keys)) ? x : (keys[i]*0 !== 0 || !!~(''+keys[i]).indexOf('.')) ? {} : []; } diff --git a/src/merge.js b/src/merge.js index 49f467b..4b5f0dc 100644 --- a/src/merge.js +++ b/src/merge.js @@ -19,7 +19,7 @@ export function dset(obj, keys, val) { keys.split && (keys=keys.split('.')); var i=0, l=keys.length, t=obj, x, k; while (i < l) { - k = keys[i++]; + k = ''+keys[i++]; if (k === '__proto__' || k === 'constructor' || k === 'prototype') break; t = t[k] = (i === l) ? merge(t[k],val) : (typeof(x=t[k])===typeof keys) ? x : (keys[i]*0 !== 0 || !!~(''+keys[i]).indexOf('.')) ? {} : []; } diff --git a/test/suites/pollution.js b/test/suites/pollution.js index bf47756..8d338ed 100644 --- a/test/suites/pollution.js +++ b/test/suites/pollution.js @@ -38,6 +38,23 @@ export default function (dset) { assert.is(Object.create(null).hello, undefined); }); + pollution('should protect against ["__proto__"] assignment :: implicit string', () => { + let input = { abc: 123 }; + let before = input.__proto__; + + dset(input, [['__proto__'], 'polluted'], true); + + assert.equal(input.__proto__, before); + assert.equal(input, { abc: 123 }); + + assert.is({}.polluted, undefined); + assert.is(input.polluted, undefined); + assert.is((new Object).polluted, undefined); + assert.is(Object.create(null).polluted, undefined); + }); + + + pollution('should ignore "prototype" assignment', () => { let input = { a: 123 }; dset(input, 'a.prototype.hello', 'world'); @@ -85,7 +102,7 @@ export default function (dset) { }); }); - // Test for CVE-2022-25645 - CWE-1321 + // Test for CVE-2022-25645 - CWE-1321 pollution('should ignore JSON.parse crafted object with "__proto__" key', () => { let a = { b: { c: 1 } }; assert.is(a.polluted, undefined);