CVE-2020-7736: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The package bmoor before 0.8.12 are vulnerable to Prototype Pollution via the set function.
CWE description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
CVSS v3 base score: 9.8
Published: 2020-10-02T10:15Z
Repository: https://github.com/b-heilman/bmoor
Language: JavaScript
Files changed: package.json, src/core.js, src/core.spec.js, src/object.spec.js
Commit date: 2020-09-17 00:24:17 -0400
Commit message: fix: prototype pollution

Diff: diff --git a/package.json b/package.json
index f2eeb7f..cc2be65 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
   "name": "bmoor",
-  "version": "0.8.11",
+  "version": "0.8.12",
   "author": "Brian Heilman <das.ist.junk@gmail.com>",
   "description": "A basic foundation for other libraries, establishing useful patterbs, and letting them be more.",
   "license": "MIT",
   "repository": {
     "type": "git",
diff --git a/src/core.js b/src/core.js
index ba628c2..27e153a 100644
--- a/src/core.js
+++ b/src/core.js
@@ -182,10 +182,14 @@ function set( root, space, value ){
 	val = space.pop();
 
 	for( i = 0, c = space.length; i < c; i++ ){
 		nextSpace = space[ i ];
 			
+		if (nextSpace === '__proto__'){
+			return null;
+		}
+
 		if ( isUndefined(curSpace[nextSpace]) ){
 			curSpace[ nextSpace ] = {};
 		}
 			
 		curSpace = curSpace[ nextSpace ];
@@ -195,10 +199,14 @@ function set( root, space, value ){
 
 	return curSpace;
 }
 
 function _makeSetter( property, next ){
+	if (property === '__proto__'){
+		throw new Error('unable to access __proto__');
+	}
+
 	if ( next ){
 		return function setter( ctx, value ){
 			var t = ctx[property];
 
 			if ( !t ){
@@ -248,10 +256,14 @@ function get( root, path ){
 	space = parse(path);
 	if ( space.length ){
 		for( i = 0, c = space.length; i < c; i++ ){
 			nextSpace = space[i];
 				
+			if (nextSpace === '__proto__'){
+				return null;
+			}
+
 			if ( isUndefined(curSpace[nextSpace]) ){
 				return;
 			}
 			
 			curSpace = curSpace[nextSpace];
@@ -260,10 +272,14 @@ function get( root, path ){
 
 	return curSpace;
 }
 
 function _makeGetter( property, next ){
+	if (property === '__proto__'){
+		throw new Error('unable to access __proto__');
+	}
+
 	if (next){
 		return function getter( obj ){
 			try {
 				return next( obj[property] );
 			}catch( ex ){
diff --git a/src/core.spec.js b/src/core.spec.js
index 0d7c41b..4e4d332 100644
--- a/src/core.spec.js
+++ b/src/core.spec.js
@@ -2,79 +2,131 @@
 const {expect} = require('chai');
 
 const bmoor = require('./index.js');
 
 describe('Testing object setting/getting', function() {
-	it('should have get working', function(){
-		var t = {
-				eins : 1,
-				zwei: {
-					drei: 3
-				}
-			};
 
-		expect( bmoor.get(t,'eins') ).to.equal(1);
-		expect( bmoor.get(t,'zwei.drei') ).to.equal(3);
-	});
+	describe('::get', function(){
+		it('should be working', function(){
+			var t = {
+					eins : 1,
+					zwei: {
+						drei: 3
+					}
+				};
 
-	it('should have get working with empty strings', function(){
-		var t = {
-				eins : 1,
-				zwei: {
-					drei: 3
-				}
-			};
+			expect( bmoor.get(t,'eins') ).to.equal(1);
+			expect( bmoor.get(t,'zwei.drei') ).to.equal(3);
+		});
 
-		expect( bmoor.get(t,'') ).to.equal(t);
-	});
+		it('should be working with empty strings', function(){
+			var t = {
+					eins : 1,
+					zwei: {
+						drei: 3
+					}
+				};
 
-	it('should have makeGetter working', function(){
-		var t = {
-				eins : 1,
-				zwei: {
-					drei: 3
-				}
-			},
-			f1 = bmoor.makeGetter('eins'),
-			f2 = bmoor.makeGetter('zwei.drei');
+			expect( bmoor.get(t,'') ).to.equal(t);
+		});
 
-		expect( f1(t) ).to.equal(1);
-		expect( f2(t) ).to.equal(3);
+		it('should not allow __proto__', function(){
+			var t = bmoor.get({}, '__proto__');
+			
+			expect(t)
+			.to.equal(null);
+		});
 	});
+	
+	describe('::makeGetter', function(){
+		it('should be working', function(){
+			var t = {
+					eins : 1,
+					zwei: {
+						drei: 3
+					}
+				},
+				f1 = bmoor.makeGetter('eins'),
+				f2 = bmoor.makeGetter('zwei.drei');
 
-	it('should have makeGetter working with empty strings', function(){
-		var t = {
-				eins : 1,
-				zwei: {
-					drei: 3
-				}
-			},
-			f1 = bmoor.makeGetter('');
+			expect( f1(t) ).to.equal(1);
+			expect( f2(t) ).to.equal(3);
+		});
 
-		expect( f1(t) ).to.equal(t);
-	});
+		it('should fail with __proto__', function(){
+			let failed = false;
 
-	it('should have set working', function(){
-		var t = {};
+			try {
+				bmoor.makeGetter('__proto__.polluted');
+			} catch(ex){
+				failed = true;
+			}
+
+			expect(failed)
+			.to.equal(true);
+		});
 
-		bmoor.set(t,'eins',1);
-		bmoor.set(t,'zwei.drei',3);
+		it('should work with empty strings', function(){
+			var t = {
+					eins : 1,
+					zwei: {
+						drei: 3
+					}
+				},
+				f1 = bmoor.makeGetter('');
+
+			expect( f1(t) ).to.equal(t);
+		});
+	});	
 
-		expect( t.eins ).to.equal(1);
-		expect( t.zwei.drei ).to.equal(3);
+	describe('::set', function(){
+		it('should be working working', function(){
+			var t = {};
+
+			bmoor.set(t,'eins',1);
+			bmoor.set(t,'zwei.drei',3);
+
+			expect( t.eins ).to.equal(1);
+			expect( t.zwei.drei ).to.equal(3);
+		});
+
+		it('should not allow __proto__', function(){
+			var t = {};
+
+			bmoor.set(t,'__proto__.polluted', true);
+			
+			expect( t.polluted )
+			.to.not.equal(true);
+		});
 	});
 
-	it('should have makeSetter working', function(){
-		var t = {},
-			f1 = bmoor.makeSetter('eins'),
-			f2 = bmoor.makeSetter('zwei.drei');
+	describe('::makeSetter', function(){
+		it('should actually work', function(){
+			var t = {},
+				f1 = bmoor.makeSetter('eins'),
+				f2 = bmoor.makeSetter('zwei.drei');
 
-		f1(t,1);
-		f2(t,3);
+			f1(t,1);
+			f2(t,3);
 
-		expect( t.eins ).to.equal(1);
-		expect( t.zwei.drei ).to.equal(3);
+			expect( t.eins ).to.equal(1);
+			expect( t.zwei.drei ).to.equal(3);
+		});
+
+		it('should fail with __proto__', function(){
+			let failed = false;
+
+			try {
+				bmoor.makeGetter('__proto__.polluted');
+			} catch(ex){
+				failed = true;
+			}
+				
+
+			expect(failed)
+			.to.equal(true);
+		});
 	});
 
 	it('should have del working', function(){
 		var t = {
 			eins : 1,
diff --git a/src/object.spec.js b/src/object.spec.js
index 07f241a..7448534 100644
--- a/src/object.spec.js
+++ b/src/object.spec.js
@@ -3,46 +3,48 @@ const {expect} = require('chai');
 
 describe('bmoor.object', function() {
 	
 	const bmoor = require('./index.js');
 
-	it('should operate explode correctly', function(){
-		var t = {
-			'eins.zwei': 12,
-			'eins.drei': 13,
-			'fier': 4
-		};
-
-		expect(bmoor.object.explode(t))
-		.to.deep.equal({
-			eins: {
-				zwei: 12,
-				drei: 13
-			},
-			fier: 4
-		});
-	});
-
-	it('should operate makeExploder correctly', function(){
-		var t = {
+	describe('::explode', function(){
+		it('should operate explode correctly', function(){
+			var t = {
 				'eins.zwei': 12,
 				'eins.drei': 13,
 				'fier': 4
-			},
-			explode = bmoor.object.makeExploder( Object.keys(t) );
+			};
+
+			expect(bmoor.object.explode(t))
+			.to.deep.equal({
+				eins: {
+					zwei: 12,
+					drei: 13
+				},
+				fier: 4
+			});
+		});
+
+		it('should operate makeExploder correctly', function(){
+			var t = {
+					'eins.zwei': 12,
+					'eins.drei': 13,
+					'fier': 4
+				},
+				explode = bmoor.object.makeExploder( Object.keys(t) );
 
-		expect(explode(t))
-		.to.deep.equal({
-			eins: {
-				zwei: 12,
-				drei: 13
-			},
-			fier: 4
+			expect(explode(t))
+			.to.deep.equal({
+				eins: {
+					zwei: 12,
+					drei: 13
+				},
+				fier: 4
+			});
 		});
 	});
 
-	describe(':: implode', function(){
+	describe('::implode', function(){
 		it('should operate correctly', function(){
 			var t = {
 				time: {
 					start: 99,
 					stop: 100
@@ -111,36 +113,36 @@ describe('bmoor.object', function() {
 				'double': [
 					[0]
 				]
 			});
 		});
-	});
 
-	it('should operate implode correctly - with an ignore', function(){
-		var t = {
-			time: {
-				start: 99,
-				stop: 100
-			},
-			id: 'woot',
-			foo: {
-				bar: {
-					hello: 'world'
+		it('should operate implode correctly - with an ignore', function(){
+			var t = {
+				time: {
+					start: 99,
+					stop: 100
+				},
+				id: 'woot',
+				foo: {
+					bar: {
+						hello: 'world'
+					}
 				}
-			}
-		};
+			};
 
-		expect(bmoor.object.implode(t, {
-			ignore: {
-				time:{
-					start:true
-				},
-				id: true,
-				foo: true
-			}
-		})).to.deep.equal({
-			'time.stop': 100
+			expect(bmoor.object.implode(t, {
+				ignore: {
+					time:{
+						start:true
+					},
+					id: true,
+					foo: true
+				}
+			})).to.deep.equal({
+				'time.stop': 100
+			});
 		});
 	});
 
 	describe('::merge', function(){
 		it('should replace null correctly', function(){
@@ -175,6 +177,6 @@ describe('bmoor.object', function() {
 				arr: null,
 				arr2: [4, 5]
 			});
 		});
 	});
-});
\ No newline at end of file
+});

Vulnerable code:   "version": "0.8.11",
	it('should have get working', function(){
		var t = {
				eins : 1,
				zwei: {
					drei: 3
				}
			};
		expect( bmoor.get(t,'eins') ).to.equal(1);
		expect( bmoor.get(t,'zwei.drei') ).to.equal(3);
	});
	it('should have get working with empty strings', function(){
		var t = {
				eins : 1,
				zwei: {
					drei: 3
				}
			};
		expect( bmoor.get(t,'') ).to.equal(t);
	});
	it('should have makeGetter working', function(){
		var t = {
				eins : 1,
				zwei: {
					drei: 3
				}
			},
			f1 = bmoor.makeGetter('eins'),
			f2 = bmoor.makeGetter('zwei.drei');
		expect( f1(t) ).to.equal(1);
		expect( f2(t) ).to.equal(3);
	it('should have makeGetter working with empty strings', function(){
		var t = {
				eins : 1,
				zwei: {
					drei: 3
				}
			},
			f1 = bmoor.makeGetter('');
		expect( f1(t) ).to.equal(t);
	});
	it('should have set working', function(){
		var t = {};
		bmoor.set(t,'eins',1);
		bmoor.set(t,'zwei.drei',3);
		expect( t.eins ).to.equal(1);
		expect( t.zwei.drei ).to.equal(3);
	it('should have makeSetter working', function(){
		var t = {},
			f1 = bmoor.makeSetter('eins'),
			f2 = bmoor.makeSetter('zwei.drei');
		f1(t,1);
		f2(t,3);
		expect( t.eins ).to.equal(1);
		expect( t.zwei.drei ).to.equal(3);
	it('should operate explode correctly', function(){
		var t = {
			'eins.zwei': 12,
			'eins.drei': 13,
			'fier': 4
		};

		expect(bmoor.object.explode(t))
		.to.deep.equal({
			eins: {
				zwei: 12,
				drei: 13
			},
			fier: 4
		});
	});

	it('should operate makeExploder correctly', function(){
		var t = {
			},
			explode = bmoor.object.makeExploder( Object.keys(t) );
		expect(explode(t))
		.to.deep.equal({
			eins: {
				zwei: 12,
				drei: 13
			},
			fier: 4
	describe(':: implode', function(){
	});
	it('should operate implode correctly - with an ignore', function(){
		var t = {
			time: {
				start: 99,
				stop: 100
			},
			id: 'woot',
			foo: {
				bar: {
					hello: 'world'
			}
		};
		expect(bmoor.object.implode(t, {
			ignore: {
				time:{
					start:true
				},
				id: true,
				foo: true
			}
		})).to.deep.equal({
			'time.stop': 100
});
Fixed code:   "version": "0.8.12",
		if (nextSpace === '__proto__'){
			return null;
		}

	if (property === '__proto__'){
		throw new Error('unable to access __proto__');
	}

			if (nextSpace === '__proto__'){
				return null;
			}

	if (property === '__proto__'){
		throw new Error('unable to access __proto__');
	}

	describe('::get', function(){
		it('should be working', function(){
			var t = {
					eins : 1,
					zwei: {
						drei: 3
					}
				};
			expect( bmoor.get(t,'eins') ).to.equal(1);
			expect( bmoor.get(t,'zwei.drei') ).to.equal(3);
		});
		it('should be working with empty strings', function(){
			var t = {
					eins : 1,
					zwei: {
						drei: 3
					}
				};
			expect( bmoor.get(t,'') ).to.equal(t);
		});
		it('should not allow __proto__', function(){
			var t = bmoor.get({}, '__proto__');
			
			expect(t)
			.to.equal(null);
		});
	
	describe('::makeGetter', function(){
		it('should be working', function(){
			var t = {
					eins : 1,
					zwei: {
						drei: 3
					}
				},
				f1 = bmoor.makeGetter('eins'),
				f2 = bmoor.makeGetter('zwei.drei');
			expect( f1(t) ).to.equal(1);
			expect( f2(t) ).to.equal(3);
		});
		it('should fail with __proto__', function(){
			let failed = false;
			try {
				bmoor.makeGetter('__proto__.polluted');
			} catch(ex){
				failed = true;
			}

			expect(failed)
			.to.equal(true);
		});
		it('should work with empty strings', function(){
			var t = {
					eins : 1,
					zwei: {
						drei: 3
					}
				},
				f1 = bmoor.makeGetter('');

			expect( f1(t) ).to.equal(t);
		});
	});	
	describe('::set', function(){
		it('should be working working', function(){
			var t = {};

			bmoor.set(t,'eins',1);
			bmoor.set(t,'zwei.drei',3);

			expect( t.eins ).to.equal(1);
			expect( t.zwei.drei ).to.equal(3);
		});

		it('should not allow __proto__', function(){
			var t = {};

			bmoor.set(t,'__proto__.polluted', true);
			
			expect( t.polluted )
			.to.not.equal(true);
		});
	describe('::makeSetter', function(){
		it('should actually work', function(){
			var t = {},
				f1 = bmoor.makeSetter('eins'),
				f2 = bmoor.makeSetter('zwei.drei');
			f1(t,1);
			f2(t,3);
			expect( t.eins ).to.equal(1);
			expect( t.zwei.drei ).to.equal(3);
		});

		it('should fail with __proto__', function(){
			let failed = false;

			try {
				bmoor.makeGetter('__proto__.polluted');
			} catch(ex){
				failed = true;
			}
				

			expect(failed)
			.to.equal(true);
		});
	describe('::explode', function(){
		it('should operate explode correctly', function(){
			var t = {
			};

			expect(bmoor.object.explode(t))
			.to.deep.equal({
				eins: {
					zwei: 12,
					drei: 13
				},
				fier: 4
			});
		});

		it('should operate makeExploder correctly', function(){
			var t = {
					'eins.zwei': 12,
					'eins.drei': 13,
					'fier': 4
				},
				explode = bmoor.object.makeExploder( Object.keys(t) );
			expect(explode(t))
			.to.deep.equal({
				eins: {
					zwei: 12,
					drei: 13
				},
				fier: 4
			});
	describe('::implode', function(){
		it('should operate implode correctly - with an ignore', function(){
			var t = {
				time: {
					start: 99,
					stop: 100
				},
				id: 'woot',
				foo: {
					bar: {
						hello: 'world'
					}
			};
			expect(bmoor.object.implode(t, {
				ignore: {
					time:{
						start:true
					},
					id: true,
					foo: true
				}
			})).to.deep.equal({
				'time.stop': 100
			});
});