From df846910915d59f711ce63c1f817815bceab5ff7 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Mon, 17 Aug 2020 13:33:41 -0400 Subject: [PATCH] fix: prototype polution vector --- compiler.js | 16 ++++++++++++---- index.js | 37 ++++++++++++++++++++++++------------- test.js | 25 ++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/compiler.js b/compiler.js index 1eed39a..70339b8 100644 --- a/compiler.js +++ b/compiler.js @@ -8,7 +8,7 @@ function makeSafe(path, param) { parts = split(path), isLast - forEach(parts, function(part, isBracket, isArray, idx, parts) { + forEach(parts, function (part, isBracket, isArray, idx, parts) { isLast = idx === parts.length - 1 part = isBracket || isArray ? '[' + part + ']' : '.' + part @@ -36,7 +36,15 @@ function expr(expression, safe, param) { module.exports = { expr, - setter: function(path) { + setter: function (path) { + if ( + path.indexOf('__proto__') !== -1 || + path.indexOf('constructor') !== -1 || + path.indexOf('prototype') !== -1 + ) { + return (obj) => obj + } + return ( setCache.get(path) || setCache.set( @@ -46,7 +54,7 @@ module.exports = { ) }, - getter: function(path, safe) { + getter: function (path, safe) { var key = path + '_' + safe return ( getCache.get(key) || @@ -55,5 +63,5 @@ module.exports = { new Function('data', 'return ' + expr(path, safe, 'data')) ) ) - } + }, } diff --git a/index.js b/index.js index e2f305d..d323ae9 100644 --- a/index.js +++ b/index.js @@ -7,14 +7,14 @@ function Cache(maxSize) { this._maxSize = maxSize this.clear() } -Cache.prototype.clear = function() { +Cache.prototype.clear = function () { this._size = 0 this._values = Object.create(null) } -Cache.prototype.get = function(key) { +Cache.prototype.get = function (key) { return this._values[key] } -Cache.prototype.set = function(key, value) { +Cache.prototype.set = function (key, value) { this._size >= this._maxSize && this.clear() if (!(key in this._values)) this._size++ @@ -41,15 +41,26 @@ module.exports = { normalizePath: normalizePath, - setter: function(path) { + setter: function (path) { var parts = normalizePath(path) return ( setCache.get(path) || - setCache.set(path, function setter(data, value) { - var index = 0, - len = parts.length + setCache.set(path, function setter(obj, value) { + var index = 0 + var len = parts.length + var data = obj + while (index < len - 1) { + let part = parts[index] + if ( + part === '__proto__' || + part === 'constructor' || + part === 'prototype' + ) { + return obj + } + data = data[parts[index++]] } data[parts[index]] = value @@ -57,7 +68,7 @@ module.exports = { ) }, - getter: function(path, safe) { + getter: function (path, safe) { var parts = normalizePath(path) return ( getCache.get(path) || @@ -73,8 +84,8 @@ module.exports = { ) }, - join: function(segments) { - return segments.reduce(function(path, part) { + join: function (segments) { + return segments.reduce(function (path, part) { return ( path + (isQuoted(part) || DIGIT_REGEX.test(part) @@ -84,9 +95,9 @@ module.exports = { }, '') }, - forEach: function(path, cb, thisArg) { + forEach: function (path, cb, thisArg) { forEach(Array.isArray(path) ? path : split(path), cb, thisArg) - } + }, } function normalizePath(path) { @@ -94,7 +105,7 @@ function normalizePath(path) { pathCache.get(path) || pathCache.set( path, - split(path).map(function(part) { + split(path).map(function (part) { return part.replace(CLEAN_QUOTES_REGEX, '$2') }) ) diff --git a/test.js b/test.js index 62494f0..195fba3 100644 --- a/test.js +++ b/test.js @@ -2,6 +2,8 @@ const a = require('assert') const expr = require('./index') const compiler = require('./compiler') +const root = (typeof global == 'object' && global) || this + function runSetterGetterTests({ setter, getter }) { let obj = { foo: { @@ -9,8 +11,8 @@ function runSetterGetterTests({ setter, getter }) { fux: 5, '00N40000002S5U0': 1, N40000002S5U0: 2, - 'FE43-D880-21AE': 3 - } + 'FE43-D880-21AE': 3, + }, } // -- Getters -- @@ -47,7 +49,24 @@ function runSetterGetterTests({ setter, getter }) { a.strictEqual(obj.foo.bar[1], 'bot') setter('[\'foo\']["bar"][1]')(obj, 'baz') + a.strictEqual(obj.foo.bar[1], 'baz') + // + ;['__proto__', 'constructor', 'prototype'].forEach((keyToTest) => { + setter(`${keyToTest}.a`)({}, 'newValue') + + a.notEqual(root['a'], 'newValue') + + const b = 'oldValue' + + a.equal(b, 'oldValue') + a.notEqual(root['b'], 'newValue') + + setter(`${keyToTest}.b`)({}, 'newValue') + a.equal(b, 'oldValue') + a.notEqual(root['b'], 'newValue') + a.equal(root['b'], undefined) + }) } console.log('--- Test Start ---') @@ -89,7 +108,7 @@ a.strictEqual(expr.join(parts), 'foo.baz["bar"][1]') let count = 0 -expr.forEach('foo.baz["bar"][1]', function(part, isBracket, isArray, idx) { +expr.forEach('foo.baz["bar"][1]', function (part, isBracket, isArray, idx) { count = idx switch (idx) {