Skip to content

Commit

Permalink
Fix: Avoids prototype pollution (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
sttk authored Mar 18, 2021
1 parent 4cac863 commit 2c738f5
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 27 deletions.
27 changes: 2 additions & 25 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
{
"env": {
"node": true
},
"extends": "gulp",
"rules": {
"array-bracket-spacing": [2, "never"],
"block-scoped-var": 2,
"brace-style": [2, "1tbs"],
"camelcase": 1,
"computed-property-spacing": [2, "never"],
"curly": 2,
"eol-last": 2,
"eqeqeq": [2, "smart"],
"max-depth": [1, 3],
"max-len": [1, 80],
"max-statements": [1, 40],
"new-cap": 1,
"no-extend-native": 2,
"no-mixed-spaces-and-tabs": 2,
"no-trailing-spaces": 2,
"no-unused-vars": 1,
"no-use-before-define": [2, "nofunc"],
"object-curly-spacing": [2, "always"],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"keyword-spacing": [2, { "before": true, "after": true }],
"space-unary-ops": 2
"max-statements": 0
}
}
8 changes: 8 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ function setDeep(obj, keyChain, valueCreator) {

function _setDeep(obj, keyElems, depth, valueCreator) {
var key = keyElems.shift();
if (isPossibilityOfPrototypePollution(key)) {
return;
}

if (!keyElems.length) {
var value = valueCreator(obj, key, depth);
if (value === undefined) {
Expand Down Expand Up @@ -224,3 +228,7 @@ function newUndefined() {
function isObject(v) {
return Object.prototype.toString.call(v) === '[object Object]';
}

function isPossibilityOfPrototypePollution(key) {
return (key === '__proto__' || key === 'constructor');
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"chai": "^3.5.0",
"coveralls": "^3.1.0",
"eslint": "^7.9.0",
"eslint-config-gulp": "^5.0.1",
"mocha": "^3.5.3",
"nyc": "^15.1.0",
"uglify-js": "^3.10.4"
Expand Down
102 changes: 102 additions & 0 deletions test/copy-props-proc.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,106 @@ describe('Processing', function() {

});

describe('Avoid a prototype pollution vulnerability', function() {

describe('The critical property key is in a src object', function() {

it('should ignore a property key: __proto__', function(done) {
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property key: constructor.prototype', function(done) {
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical property key is in a dest object and using reverse', function() {

it('should ignore a property key: __proto__', function(done) {
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property key: constructor.prototype', function(done) {
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical property value is in a fromto object', function() {

it('should ignore a property value: __proto__', function(done) {
var fromto = { a: '__proto__.poluuted', b: 'c' };
expect({}.polluted).to.be.undefined;
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property value: constructor.prototype', function(done) {
var fromto = { a: 'constructor.prototype.polluted', b: 'c' };
expect({}.polluted).to.be.undefined;
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical property key is in a fromto object and using reverse', function() {

it('should ignore a property key: __proto__', function(done) {
var fromto = { '__proto__.poluuted': 'a', c: 'b' };
expect({}.polluted).to.be.undefined;
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property key: constructor.prototype and using reverse', function(done) {
var fromto = { 'constructor.prototype.polluted': 'a', c: 'b' };
expect({}.polluted).to.be.undefined;
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical element is in a fromto array', function() {

it('should ignore an element: __proto__', function(done) {
var fromto = ['__proto__.polluted', 'b'];
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse('{"__proto__":{"polluted":"polluted"},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore an element: constructor.prototype', function(done) {
var fromto = ['constructor.prototype.polluted', 'b'];
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse('{"constructor":{"prototype":{"polluted":"polluted"}},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});
});
});
102 changes: 102 additions & 0 deletions test/web/copy-props-proc.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,106 @@ describe('Processing', function() {

});

describe('Avoid a prototype pollution vulnerability', function() {

describe('The critical property key is in a src object', function() {

it('should ignore a property key: __proto__', function(done) {
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property key: constructor.prototype', function(done) {
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical property key is in a dest object and using reverse', function() {

it('should ignore a property key: __proto__', function(done) {
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property key: constructor.prototype', function(done) {
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
expect({}.polluted).to.be.undefined;
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical property value is in a fromto object', function() {

it('should ignore a property value: __proto__', function(done) {
var fromto = { a: '__proto__.poluuted', b: 'c' };
expect({}.polluted).to.be.undefined;
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property value: constructor.prototype', function(done) {
var fromto = { a: 'constructor.prototype.polluted', b: 'c' };
expect({}.polluted).to.be.undefined;
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical property key is in a fromto object and using reverse', function() {

it('should ignore a property key: __proto__', function(done) {
var fromto = { '__proto__.poluuted': 'a', c: 'b' };
expect({}.polluted).to.be.undefined;
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore a property key: constructor.prototype and using reverse', function(done) {
var fromto = { 'constructor.prototype.polluted': 'a', c: 'b' };
expect({}.polluted).to.be.undefined;
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});

describe('The critical element is in a fromto array', function() {

it('should ignore an element: __proto__', function(done) {
var fromto = ['__proto__.polluted', 'b'];
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse('{"__proto__":{"polluted":"polluted"},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
expect({}.polluted).to.be.undefined;
done();
});

it('should ignore an element: constructor.prototype', function(done) {
var fromto = ['constructor.prototype.polluted', 'b'];
expect({}.polluted).to.be.undefined;
expect(copyProps(JSON.parse('{"constructor":{"prototype":{"polluted":"polluted"}},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
expect({}.polluted).to.be.undefined;
done();
});

});
});
});
8 changes: 8 additions & 0 deletions web/copy-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ function setDeep(obj, keyChain, valueCreator) {

function _setDeep(obj, keyElems, depth, valueCreator) {
var key = keyElems.shift();
if (isPossibilityOfPrototypePollution(key)) {
return;
}

if (!keyElems.length) {
var value = valueCreator(obj, key, depth);
if (value === undefined) {
Expand Down Expand Up @@ -226,6 +230,10 @@ function isObject(v) {
return Object.prototype.toString.call(v) === '[object Object]';
}

function isPossibilityOfPrototypePollution(key) {
return (key === '__proto__' || key === 'constructor');
}

},{"each-props":4,"is-plain-object":8}],2:[function(require,module,exports){
/*!
* array-each <https://github.com/jonschlinkert/array-each>
Expand Down
Loading

0 comments on commit 2c738f5

Please sign in to comment.