Skip to content

Commit

Permalink
fix: prototype polution vector
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Aug 17, 2020
1 parent 8459582 commit df84691
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 20 deletions.
16 changes: 12 additions & 4 deletions compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -46,7 +54,7 @@ module.exports = {
)
},

getter: function(path, safe) {
getter: function (path, safe) {
var key = path + '_' + safe
return (
getCache.get(key) ||
Expand All @@ -55,5 +63,5 @@ module.exports = {
new Function('data', 'return ' + expr(path, safe, 'data'))
)
)
}
},
}
37 changes: 24 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++

Expand All @@ -41,23 +41,34 @@ 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
})
)
},

getter: function(path, safe) {
getter: function (path, safe) {
var parts = normalizePath(path)
return (
getCache.get(path) ||
Expand All @@ -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)
Expand All @@ -84,17 +95,17 @@ module.exports = {
}, '')
},

forEach: function(path, cb, thisArg) {
forEach: function (path, cb, thisArg) {
forEach(Array.isArray(path) ? path : split(path), cb, thisArg)
}
},
}

function normalizePath(path) {
return (
pathCache.get(path) ||
pathCache.set(
path,
split(path).map(function(part) {
split(path).map(function (part) {
return part.replace(CLEAN_QUOTES_REGEX, '$2')
})
)
Expand Down
25 changes: 22 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ 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: {
bar: ['baz', 'bux'],
fux: 5,
'00N40000002S5U0': 1,
N40000002S5U0: 2,
'FE43-D880-21AE': 3
}
'FE43-D880-21AE': 3,
},
}

// -- Getters --
Expand Down Expand Up @@ -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 ---')
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit df84691

Please sign in to comment.