CVE-2020-28274 (Other, published 2020-12-08T22:15Z)
Description: value: Prototype pollution vulnerability in 'deepref' versions 1.1.1 through 1.2.1 allows attacker to cause a denial of service and may lead to remote code execution.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/isaymatato/deepref
Files changed:
.gitignore
.jscsrc
.jshintignore
.jshintrc
Makefile
README.md
index.js
package.json
test/index.js
Commit message: Disallow __proto__ assignment

Disallow assignment to the prototype using `set()`.

This resolves a Prototype Pollution vulnerability. An attacker could
have assigned a value to the target object's prototype, which can modify
how objects behave throughout the entire application.


Vulnerable code:

Fixed code:
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
{
  "esnext": true,
  "verbose": true,
  "disallowEmptyBlocks": true,
  "disallowKeywordsOnNewLine": ["else"],
  "disallowMultipleLineBreaks": true,
  "disallowMultipleVarDecl": {
    "allExcept": ["undefined", "require"]
  },
  "disallowNewlineBeforeBlockStatements": true,
  "disallowSpaceAfterObjectKeys": true,
  "disallowSpaceAfterPrefixUnaryOperators": true,
  "disallowSpaceBeforePostfixUnaryOperators": true,
  "disallowSpaceBeforeSemicolon": true,
  "disallowSpacesInFunction": {
    "beforeOpeningRoundBrace": true
  },
  "disallowSpacesInsideParentheses": true,
  "disallowSpacesInCallExpression": true,
  "disallowTabs": true,
  "disallowTrailingComma": false,
  "disallowTrailingWhitespace": true,
  "disallowVar": false,
  "excludeFiles": ["public/libs"],
  "maximumLineLength": 80,
  "requireArrayDestructuring": false,
  "requireBlocksOnNewline": 1,
  "requireCamelCaseOrUpperCaseIdentifiers": true,
  "requireCapitalizedConstructors": true,
  "requireCommaBeforeLineBreak": true,
  "requireCurlyBraces": [
    "if",
    "else",
    "for",
    "while",
    "do",
    "try",
    "catch",
    "switch"
  ],
  "requireDotNotation": true,
  "requireEnhancedObjectLiterals": false,
  "requireLineBreakAfterVariableAssignment": true,
  "requireObjectDestructuring": false,
  "requireOperatorBeforeLineBreak": ["."],
  "requireParenthesesAroundArrowParam": false,
  "requireSemicolons": true,
  "requireSpaceAfterBinaryOperators": true,
  "requireSpaceAfterKeywords": [
    "do",
    "for",
    "if",
    "else",
    "switch",
    "case",
    "try",
    "void",
    "while",
    "with",
    "return",
    "typeof"
  ],
  "requireSpaceAfterLineComment": true,
  "requireSpaceBeforeBinaryOperators": true,
  "requireSpaceBeforeBlockStatements": true,
  "requireSpaceBeforeKeywords": [
    "else",
    "while",
    "catch"
  ],
  "requireSpaceBeforeObjectValues": true,
  "requireSpaceBetweenArguments": true,
  "requireSpacesInFunction": {
    "beforeOpeningCurlyBrace": true
  },
  "requireSpacesInConditionalExpression": true,
  "requireSpacesInForStatement": true,
  "requireSpacesInsideObjectBrackets": "all",
  "requireTrailingComma": false,
  "validateIndentation": 2,
  "validateParameterSeparator": ", ",
  "validateQuoteMarks": {
    "mark": "'",
    "escape": true
  }
}
public/libs/*
{
  "asi": false,
  "bitwise": true,
  "browser": true,
  "curly": true,
  "forin": true,
  "latedef": "nofunc",
  "noarg": true,
  "node": true,
  "nonew": true,
  "predef": [
    "$",
    "jQuery",

    "jasmine",
    "beforeEach",
    "describe",
    "expect",
    "it",

    "angular",
    "inject",
    "module"
  ],
  "undef": true,
  "unused": true
}
REPORTER = spec

test:
	./node_modules/mocha/bin/mocha \
	--reporter $(REPORTER) \
	test/*.js

.PHONY: test
DeepRef
=========

Write to a deeply nested reference within an object.

## Installation
  ```
  npm install deepref --save
  ```

## Usage
  ```javascript
  var deep = require('deepref');

  var obj = {};

  deep.set(obj, 'a', 'obj.a is set to this');

  console.dir(obj);
  // { a: 'obj.a is set to this' }

  deep.set(obj, 'b.c', 'we can set nested fields');

  console.dir(obj);
  /*
  * { a: 'obj.a is set to this',
  *   b: { c: 'we can set nested fields' } }
  */

  deep.set(obj, 'd.0', 'we can even..');
  deep.set(obj, 'd.1', '..create arrays..');
  deep.set(obj, 'd.2', '..by using an integer as the index');

  console.dir(obj);
  /*
   *{ a: 'obj.a is set to this',
   *  b: { c: 'we can set nested fields' },
   *  d:
   *   [ 'we can even..',
   *     '..create arrays..',
   *     '..by using an integer as the index' ] }
  */
  ```

## Decorate
Use deep.decorate(obj) to give the object the 'setKey' method, which works the same as deep.set
  ```javascript
  var obj = {};

  deep.decorate(obj);

  obj.setKey('a.b.c', 123);

  console.log(obj.a.b.c);
  // Now set to to 123;
  ```
  
Use deep.undecorate(obj) to remove 'setKey'
  ```javascript
  var obj = {};

  deep.decorate(obj);

  obj.setKey('a.b.c', 123);

  deep.undecorate(obj);

  console.log(typeof obj.setKey);
  // obj.setKey is now undefined
  ```

## Tests
  ```
  npm test
  ```

## Contributing

Please use the included style guide.  If you change anything, please test
and add unit tests for any new functionality.  If you're fixing a bug, please
add a unit test that would have failed before the bug was fixed.  Thanks!
module.exports = {
  /**
  * Set a deeply nested value of an object
  * This alters the original object
  *
  * @param  {Object} object The object you want to alter
  * @param  {String} path The reference path
  * @param  {*} value The value you want to set
  * @return {Object} object Retuns the altered object
  */
  set: set,

  decorate: function(object) {
    object.setKey = function(path, value) {
      return set(object, path, value);
    };
  },
  undecorate: function(object) {
    if (typeof object.setKey === 'function') {
      delete object.setKey;
    }
  }
};

function set(object, path, value) {
  var reference = Reference.createFromPath(object, path);
  reference.set(value);
  return reference.resolve();
}

function Reference(pointer, field, parent) {
  this.pointer = pointer;
  this.field = field;
  this.parent = parent || null;
}

Reference.prototype.set = function(value) {
  if (typeof this.field === 'undefined') {
    throw new Error('Cannot set, field is undefined');
    return null;
  }
  this.pointer[this.field] = value;
  return this;
};

Reference.prototype.resolve = function() {
  var reference = this;
  while (reference.parent) {
    reference = reference.getParent();
  }
  return reference.pointer;
};

Reference.createFromPath = function(pointer, path) {
  var reference = new Reference(pointer);
  path = path.split('.');
  path.forEach(function(field) {
    if (field === '__proto__') {
      throw new Error('Cannot assign reference to prototype')
    }

    var isNumber = checkIfStringIsNumber(field);
    var hasParent = reference.getParent() ? true : false;
    if (isNumber) {
      field = parseInt(field);
    }

    reference = reference.getChild(field);

    if (isNumber && hasParent) {
      reference.convertToArray();
    }
  });

  return reference;
};

function checkIfStringIsNumber(field) {
  return /^\d+$/.test(field);
}

Reference.prototype.getParent = function() {
  return this.parent || null;
};

Reference.prototype.getChild = function(field) {
  var parent = this;

  if (!this.field) {
    return new Reference(this.pointer, field, parent);
  }

  this.initializeChildAsObject();
  var pointer = this.get();

  return new Reference(pointer, field, parent);
};

Reference.prototype.get = function() {
  if (!this.field) {
    return this.pointer;
  } else {
    return this.pointer[this.field];
  }
};

Reference.prototype.initializeChildAsObject = function() {
  if (this.getType() !== 'object') {
    this.set({});
  }
  return this;
};

Reference.prototype.getType = function(value) {
  return typeof this.pointer[this.field];
};

Reference.prototype.convertToArray = function() {
  if (!this.parent) {
    throw new Error('Cannot convert to array without a parent reference!');
    return null;
  }

  this.pointer = this.parent.convertChildToArray();
};

Reference.prototype.convertChildToArray = function() {
  var child = this.get();
  if (!Array.isArray(child)) {
    this.set([]);
    this.mapKeys(child);
  }
  return this.get();
};

Reference.prototype.mapKeys = function(source) {
  if (typeof source !== 'object') {
    throw new Error('Cannot map from source, source is not an object');
    return null;
  }

  Object.keys(source).forEach(function(key) {
    this.pointer[ this.field ][key] = pointer[key];
  });

  return this;
};



{
  "name": "deepref",
  "version": "1.2.1",
  "description": "Write to a deeply nested reference within an object",
  "main": "index.js",
  "scripts": {
    "test": "make test"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/isaymatato/deepref.git"
  },
  "keywords": [
    "deep",
    "reference",
    "nested",
    "object"
  ],
  "author": "Mateo Williford <mateowilliford@gmail.com>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/isaymatato/deepref/issues"
  },
  "homepage": "https://github.com/isaymatato/deepref#readme",
  "devDependencies": {
    "chai": "^3.5.0",
    "mocha": "^2.4.5"
  }
}
var chai = require('chai');
var assert = chai.assert,
    expect = chai.expect,
    should = chai.should();
var deepref = require('../index');
var set = deepref.set;
var decorate = deepref.decorate;
var undecorate = deepref.undecorate;

describe('#set', function() {
  it('sets obj[0] to true', function() {
    var obj = set({}, '0', true);
    obj[0].should.equal(true);
  });

  it('sets obj.a to true', function() {
    var obj = set({}, 'a', true);
    obj.a.should.equal(true);
  });

  it('sets obj.a.b[0] to true', function() {
    var obj = set({}, 'a.b.0', true);
    obj.a.b[0].should.equal(true);
  });

  it('sets obj to ["A","B","C"]', function() {
    var obj = {};
    obj = set(obj, '0', 'A');
    obj = set(obj, '1', 'B');
    obj = set(obj, '2', 'C');
    obj[0].should.equal('A');
    obj[1].should.equal('B');
    obj[2].should.equal('C');
  });

  it('sets obj.a to ["A","B","C"]', function() {
    var obj = {};
    obj = set(obj, 'a.0', 'A');
    obj = set(obj, 'a.1', 'B');
    obj = set(obj, 'a.2', 'C');
    obj.a[0].should.equal('A');
    obj.a[1].should.equal('B');
    obj.a[2].should.equal('C');
  });

  context('attempt to modify prototype', function() {
    it('throws an error', function() {
      assert.throws(
        () => set({}, '__proto__.foo', 'hacker_attack!'),
        Error, 'Cannot assign reference to prototype')
    })
  })
});

describe('#decorate', function() {
  it('gives obj setKey method', function() {
    var obj = {};
    decorate(obj);
    expect(obj.setKey).to.be.a('function');
  });

  it('obj.setKey(\'a.b\') sets obj.a.b to 1', function() {
    var obj = {};
    decorate(obj);
    obj.setKey('a.b', 1);
    obj.a.b.should.equal(1);
  });
});

describe('#undecorate', function() {
  it('removes setKey method from object', function() {
    var obj = {};
    decorate(obj);
    expect(obj.setKey).to.be.a('function');
    undecorate(obj);
    expect(obj.setKey).to.not.be.a('function');
  });
});