From 2b247fd575448a19604a7f81460b04fcb033d315 Mon Sep 17 00:00:00 2001 From: Mike Rodrigues Date: Tue, 5 Jan 2021 12:44:54 -0500 Subject: [PATCH] Fix #18 Checks for possible prototype pollution attempts on nested key values by moving the key path check to the recursive function. --- dist/path.js | 2 +- lib/path.js | 10 +++++----- test/tests.js | 13 +++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/dist/path.js b/dist/path.js index 2d3d233..d0136fc 100644 --- a/dist/path.js +++ b/dist/path.js @@ -3,4 +3,4 @@ * doc-path * Copyright (c) 2015-present, Michael Rodrigues. */ -"use strict";function evaluatePath(t,r){if(!t)return null;let{dotIndex:e,key:a,remaining:i}=state(r);return e>=0&&!t[r]?Array.isArray(t[a])?t[a].map(t=>evaluatePath(t,i)):evaluatePath(t[a],i):Array.isArray(t)?t.map(t=>evaluatePath(t,r)):t[r]}function setPath(t,r,e){if(!t)throw new Error("No object was provided.");if(!r)throw new Error("No keyPath was provided.");return r.startsWith("__proto__")||r.startsWith("constructor")||r.startsWith("prototype")?t:_sp(t,r,e)}function _sp(t,r,e){let{dotIndex:a,key:i,remaining:s}=state(r);if(a>=0){if(!t[i]&&Array.isArray(t))return t.forEach(t=>_sp(t,r,e));t[i]||(t[i]={}),_sp(t[i],s,e)}else{if(Array.isArray(t))return t.forEach(t=>_sp(t,s,e));t[r]=e}return t}function state(t){let r=t.indexOf(".");return{dotIndex:r,key:t.slice(0,r>=0?r:void 0),remaining:t.slice(r+1)}}module.exports={evaluatePath:evaluatePath,setPath:setPath}; \ No newline at end of file +"use strict";function evaluatePath(t,r){if(!t)return null;let{dotIndex:e,key:a,remaining:i}=state(r);return e>=0&&!t[r]?Array.isArray(t[a])?t[a].map(t=>evaluatePath(t,i)):evaluatePath(t[a],i):Array.isArray(t)?t.map(t=>evaluatePath(t,r)):t[r]}function setPath(t,r,e){if(!t)throw new Error("No object was provided.");if(!r)throw new Error("No keyPath was provided.");return _sp(t,r,e)}function _sp(t,r,e){let{dotIndex:a,key:i,remaining:n}=state(r);if(r.startsWith("__proto__")||r.startsWith("constructor")||r.startsWith("prototype"))return t;if(a>=0){if(!t[i]&&Array.isArray(t))return t.forEach(t=>_sp(t,r,e));t[i]||(t[i]={}),_sp(t[i],n,e)}else{if(Array.isArray(t))return t.forEach(t=>_sp(t,n,e));t[r]=e}return t}function state(t){let r=t.indexOf(".");return{dotIndex:r,key:t.slice(0,r>=0?r:void 0),remaining:t.slice(r+1)}}module.exports={evaluatePath:evaluatePath,setPath:setPath}; \ No newline at end of file diff --git a/lib/path.js b/lib/path.js index 22ea1d6..97743ac 100644 --- a/lib/path.js +++ b/lib/path.js @@ -48,11 +48,6 @@ function setPath(obj, kp, v) { throw new Error('No keyPath was provided.'); } - // If this is clearly a prototype pollution attempt, then refuse to modify the path - if (kp.startsWith('__proto__') || kp.startsWith('constructor') || kp.startsWith('prototype')) { - return obj; - } - return _sp(obj, kp, v); } @@ -67,6 +62,11 @@ function setPath(obj, kp, v) { function _sp(obj, kp, v) { let {dotIndex, key, remaining} = state(kp); + // If this is clearly a prototype pollution attempt, then refuse to modify the path + if (kp.startsWith('__proto__') || kp.startsWith('constructor') || kp.startsWith('prototype')) { + return obj; + } + if (dotIndex >= 0) { // If there is a '.' in the key path, recur on the subdoc and ... if (!obj[key] && Array.isArray(obj)) { diff --git a/test/tests.js b/test/tests.js index a50152d..0db9297 100644 --- a/test/tests.js +++ b/test/tests.js @@ -230,10 +230,12 @@ describe('doc-path Module', function() { it('should protect against prototype pollution via __proto__', (done) => { doc = {}; + assert.equal(doc.polluted, undefined); path.setPath(doc, '__proto__.polluted', 'prototype-polluted'); assert.equal(doc.__proto__.polluted, undefined); assert.equal(doc.polluted, undefined); assert.equal({}.polluted, undefined); + assert.equal(Object.polluted, undefined); done(); }); @@ -266,5 +268,16 @@ describe('doc-path Module', function() { assert.equal({}.test, undefined); done(); }); + + it('should protect against prototype pollution against a nested document', (done) => { + doc = {}; + assert.equal(doc.polluted, undefined); + path.setPath(doc, 'a.__proto__.polluted', 'polluted!'); + assert.equal(typeof doc.a, 'object'); + assert.equal(doc.polluted, undefined); + assert.equal({}.polluted, undefined); + assert.equal(Object.polluted, undefined); + done(); + }); }); });