Skip to content

Commit 2c738f5

Browse files
authored
Fix: Avoids prototype pollution (#7)
1 parent 4cac863 commit 2c738f5

8 files changed

+225
-27
lines changed

.eslintrc

+2-25
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,6 @@
11
{
2-
"env": {
3-
"node": true
4-
},
2+
"extends": "gulp",
53
"rules": {
6-
"array-bracket-spacing": [2, "never"],
7-
"block-scoped-var": 2,
8-
"brace-style": [2, "1tbs"],
9-
"camelcase": 1,
10-
"computed-property-spacing": [2, "never"],
11-
"curly": 2,
12-
"eol-last": 2,
13-
"eqeqeq": [2, "smart"],
14-
"max-depth": [1, 3],
15-
"max-len": [1, 80],
16-
"max-statements": [1, 40],
17-
"new-cap": 1,
18-
"no-extend-native": 2,
19-
"no-mixed-spaces-and-tabs": 2,
20-
"no-trailing-spaces": 2,
21-
"no-unused-vars": 1,
22-
"no-use-before-define": [2, "nofunc"],
23-
"object-curly-spacing": [2, "always"],
24-
"quotes": [2, "single", "avoid-escape"],
25-
"semi": [2, "always"],
26-
"keyword-spacing": [2, { "before": true, "after": true }],
27-
"space-unary-ops": 2
4+
"max-statements": 0
285
}
296
}

index.js

+8
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ function setDeep(obj, keyChain, valueCreator) {
184184

185185
function _setDeep(obj, keyElems, depth, valueCreator) {
186186
var key = keyElems.shift();
187+
if (isPossibilityOfPrototypePollution(key)) {
188+
return;
189+
}
190+
187191
if (!keyElems.length) {
188192
var value = valueCreator(obj, key, depth);
189193
if (value === undefined) {
@@ -224,3 +228,7 @@ function newUndefined() {
224228
function isObject(v) {
225229
return Object.prototype.toString.call(v) === '[object Object]';
226230
}
231+
232+
function isPossibilityOfPrototypePollution(key) {
233+
return (key === '__proto__' || key === 'constructor');
234+
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"chai": "^3.5.0",
4444
"coveralls": "^3.1.0",
4545
"eslint": "^7.9.0",
46+
"eslint-config-gulp": "^5.0.1",
4647
"mocha": "^3.5.3",
4748
"nyc": "^15.1.0",
4849
"uglify-js": "^3.10.4"

test/copy-props-proc.js

+102
Original file line numberDiff line numberDiff line change
@@ -701,4 +701,106 @@ describe('Processing', function() {
701701

702702
});
703703

704+
describe('Avoid a prototype pollution vulnerability', function() {
705+
706+
describe('The critical property key is in a src object', function() {
707+
708+
it('should ignore a property key: __proto__', function(done) {
709+
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
710+
expect({}.polluted).to.be.undefined;
711+
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
712+
expect({}.polluted).to.be.undefined;
713+
done();
714+
});
715+
716+
it('should ignore a property key: constructor.prototype', function(done) {
717+
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
718+
expect({}.polluted).to.be.undefined;
719+
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
720+
expect({}.polluted).to.be.undefined;
721+
done();
722+
});
723+
724+
});
725+
726+
describe('The critical property key is in a dest object and using reverse', function() {
727+
728+
it('should ignore a property key: __proto__', function(done) {
729+
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
730+
expect({}.polluted).to.be.undefined;
731+
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
732+
expect({}.polluted).to.be.undefined;
733+
done();
734+
});
735+
736+
it('should ignore a property key: constructor.prototype', function(done) {
737+
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
738+
expect({}.polluted).to.be.undefined;
739+
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
740+
expect({}.polluted).to.be.undefined;
741+
done();
742+
});
743+
744+
});
745+
746+
describe('The critical property value is in a fromto object', function() {
747+
748+
it('should ignore a property value: __proto__', function(done) {
749+
var fromto = { a: '__proto__.poluuted', b: 'c' };
750+
expect({}.polluted).to.be.undefined;
751+
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
752+
expect({}.polluted).to.be.undefined;
753+
done();
754+
});
755+
756+
it('should ignore a property value: constructor.prototype', function(done) {
757+
var fromto = { a: 'constructor.prototype.polluted', b: 'c' };
758+
expect({}.polluted).to.be.undefined;
759+
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
760+
expect({}.polluted).to.be.undefined;
761+
done();
762+
});
763+
764+
});
765+
766+
describe('The critical property key is in a fromto object and using reverse', function() {
767+
768+
it('should ignore a property key: __proto__', function(done) {
769+
var fromto = { '__proto__.poluuted': 'a', c: 'b' };
770+
expect({}.polluted).to.be.undefined;
771+
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
772+
expect({}.polluted).to.be.undefined;
773+
done();
774+
});
775+
776+
it('should ignore a property key: constructor.prototype and using reverse', function(done) {
777+
var fromto = { 'constructor.prototype.polluted': 'a', c: 'b' };
778+
expect({}.polluted).to.be.undefined;
779+
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
780+
expect({}.polluted).to.be.undefined;
781+
done();
782+
});
783+
784+
});
785+
786+
describe('The critical element is in a fromto array', function() {
787+
788+
it('should ignore an element: __proto__', function(done) {
789+
var fromto = ['__proto__.polluted', 'b'];
790+
expect({}.polluted).to.be.undefined;
791+
expect(copyProps(JSON.parse('{"__proto__":{"polluted":"polluted"},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
792+
expect({}.polluted).to.be.undefined;
793+
done();
794+
});
795+
796+
it('should ignore an element: constructor.prototype', function(done) {
797+
var fromto = ['constructor.prototype.polluted', 'b'];
798+
expect({}.polluted).to.be.undefined;
799+
expect(copyProps(JSON.parse('{"constructor":{"prototype":{"polluted":"polluted"}},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
800+
expect({}.polluted).to.be.undefined;
801+
done();
802+
});
803+
804+
});
805+
});
704806
});

test/web/copy-props-proc.js

+102
Original file line numberDiff line numberDiff line change
@@ -701,4 +701,106 @@ describe('Processing', function() {
701701

702702
});
703703

704+
describe('Avoid a prototype pollution vulnerability', function() {
705+
706+
describe('The critical property key is in a src object', function() {
707+
708+
it('should ignore a property key: __proto__', function(done) {
709+
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
710+
expect({}.polluted).to.be.undefined;
711+
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
712+
expect({}.polluted).to.be.undefined;
713+
done();
714+
});
715+
716+
it('should ignore a property key: constructor.prototype', function(done) {
717+
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
718+
expect({}.polluted).to.be.undefined;
719+
expect(copyProps(JSON.parse(maliciousSrcJson), {})).to.deep.equal({ a: 1 });
720+
expect({}.polluted).to.be.undefined;
721+
done();
722+
});
723+
724+
});
725+
726+
describe('The critical property key is in a dest object and using reverse', function() {
727+
728+
it('should ignore a property key: __proto__', function(done) {
729+
var maliciousSrcJson = '{"__proto__":{"polluted":"polluted"},"a":1}';
730+
expect({}.polluted).to.be.undefined;
731+
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
732+
expect({}.polluted).to.be.undefined;
733+
done();
734+
});
735+
736+
it('should ignore a property key: constructor.prototype', function(done) {
737+
var maliciousSrcJson = '{"constructor":{"prototype":{"polluted":"polluted"}},"a":1}';
738+
expect({}.polluted).to.be.undefined;
739+
expect(copyProps({}, JSON.parse(maliciousSrcJson), true)).to.deep.equal({ a: 1 });
740+
expect({}.polluted).to.be.undefined;
741+
done();
742+
});
743+
744+
});
745+
746+
describe('The critical property value is in a fromto object', function() {
747+
748+
it('should ignore a property value: __proto__', function(done) {
749+
var fromto = { a: '__proto__.poluuted', b: 'c' };
750+
expect({}.polluted).to.be.undefined;
751+
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
752+
expect({}.polluted).to.be.undefined;
753+
done();
754+
});
755+
756+
it('should ignore a property value: constructor.prototype', function(done) {
757+
var fromto = { a: 'constructor.prototype.polluted', b: 'c' };
758+
expect({}.polluted).to.be.undefined;
759+
expect(copyProps({ a: 'polluted', b: 1 }, {}, fromto)).to.deep.equal({ c: 1 });
760+
expect({}.polluted).to.be.undefined;
761+
done();
762+
});
763+
764+
});
765+
766+
describe('The critical property key is in a fromto object and using reverse', function() {
767+
768+
it('should ignore a property key: __proto__', function(done) {
769+
var fromto = { '__proto__.poluuted': 'a', c: 'b' };
770+
expect({}.polluted).to.be.undefined;
771+
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
772+
expect({}.polluted).to.be.undefined;
773+
done();
774+
});
775+
776+
it('should ignore a property key: constructor.prototype and using reverse', function(done) {
777+
var fromto = { 'constructor.prototype.polluted': 'a', c: 'b' };
778+
expect({}.polluted).to.be.undefined;
779+
expect(copyProps({}, { a: 'polluted', b: 1 }, fromto, true)).to.deep.equal({ c: 1 });
780+
expect({}.polluted).to.be.undefined;
781+
done();
782+
});
783+
784+
});
785+
786+
describe('The critical element is in a fromto array', function() {
787+
788+
it('should ignore an element: __proto__', function(done) {
789+
var fromto = ['__proto__.polluted', 'b'];
790+
expect({}.polluted).to.be.undefined;
791+
expect(copyProps(JSON.parse('{"__proto__":{"polluted":"polluted"},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
792+
expect({}.polluted).to.be.undefined;
793+
done();
794+
});
795+
796+
it('should ignore an element: constructor.prototype', function(done) {
797+
var fromto = ['constructor.prototype.polluted', 'b'];
798+
expect({}.polluted).to.be.undefined;
799+
expect(copyProps(JSON.parse('{"constructor":{"prototype":{"polluted":"polluted"}},"b":1}'), {}, fromto)).to.deep.equal({ b: 1 });
800+
expect({}.polluted).to.be.undefined;
801+
done();
802+
});
803+
804+
});
805+
});
704806
});

web/copy-props.js

+8
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ function setDeep(obj, keyChain, valueCreator) {
185185

186186
function _setDeep(obj, keyElems, depth, valueCreator) {
187187
var key = keyElems.shift();
188+
if (isPossibilityOfPrototypePollution(key)) {
189+
return;
190+
}
191+
188192
if (!keyElems.length) {
189193
var value = valueCreator(obj, key, depth);
190194
if (value === undefined) {
@@ -226,6 +230,10 @@ function isObject(v) {
226230
return Object.prototype.toString.call(v) === '[object Object]';
227231
}
228232

233+
function isPossibilityOfPrototypePollution(key) {
234+
return (key === '__proto__' || key === 'constructor');
235+
}
236+
229237
},{"each-props":4,"is-plain-object":8}],2:[function(require,module,exports){
230238
/*!
231239
* array-each <https://github.com/jonschlinkert/array-each>

0 commit comments

Comments
 (0)