CVE-2019-10793 (JavaScript, published 2020-02-18T16:15Z)
Description: value: dot-object before 2.1.3 is vulnerable to Prototype Pollution. The set function could be tricked into adding or modifying properties of Object.prototype using a __proto__ payload.
Severity: MEDIUM (CVSS v3 base score: 6.3)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/rhalff/dot-object
Files changed:
index.js
src/dot-object.js
test/array_notation.js
test/dot-json.js
test/str.js
Commit message: guard for possible prototype polution


Vulnerable code:
  return path.split(sep)
    obj[k] = obj[k] ||
      (this.useArray && isIndex(a[0]) ? [] : {})
    if (!this.override &&
      isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) {
      if (i === (keys.length - 1)) {
    obj = obj.filter(function (n) { return n !== undefined })
      ret = ret[keys[0]].filter(function (v) { return v !== undefined })
DotObject.prototype.transfer = function (source, target, obj1, obj2, mods, merge) {
    this.set(target,
      _process(
        this.pick(source, obj1, true),
        mods
      ), obj2, merge)
    this.set(target,
        JSON.parse(
          JSON.stringify(
            this.pick(source, obj1, false)
          )
        ),
      ), obj2, merge)
    if (i === (keys.length - 1)) {
  *    "some": {
  *      "thing": "else"
  *    }
  *    "some.thing": "name"
  Object.keys(recipe).forEach(function (key) {
    this.set(recipe[key], this.pick(key, obj), tgt)
  }.bind(this))
  Object.keys(obj).forEach(function (key) {
    var index = isArray && this.useBrackets ? '[' + key + ']' : key
    if (
      (
        (
          (isObject(obj[key]) && !isEmptyObject(obj[key])) ||
          (Array.isArray(obj[key]) && (!this.keepArray && (obj[key].length !== 0)))
        )
      )
    ) {
      if (isArray && this.useBrackets) {
        var previousKey = path[path.length - 1] || ''
        return this.dot(obj[key], tgt, path.slice(0, -1).concat(previousKey + index))
      } else {
        return this.dot(obj[key], tgt, path.concat(index))
      }
    } else {
      if (isArray && this.useBrackets) {
        tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key]
        tgt[path.concat(index).join(this.separator)] = obj[key]
    }
  }.bind(this))
DotObject.dot = wrap('dot')

;['override', 'overwrite'].forEach(function (prop) {
})

;['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) {
  return path.split(sep)
    obj[k] = obj[k] ||
      (this.useArray && isIndex(a[0]) ? [] : {})
    if (!this.override &&
      isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) {
      if (i === (keys.length - 1)) {
    obj = obj.filter(function (n) { return n !== undefined })
      ret = ret[keys[0]].filter(function (v) { return v !== undefined })
DotObject.prototype.transfer = function (source, target, obj1, obj2, mods, merge) {
    this.set(target,
      _process(
        this.pick(source, obj1, true),
        mods
      ), obj2, merge)
    this.set(target,
        JSON.parse(
          JSON.stringify(
            this.pick(source, obj1, false)
          )
        ),
      ), obj2, merge)
    if (i === (keys.length - 1)) {
  *    "some": {
  *      "thing": "else"
  *    }
  *    "some.thing": "name"
  Object.keys(recipe).forEach(function (key) {
    this.set(recipe[key], this.pick(key, obj), tgt)
  }.bind(this))
  Object.keys(obj).forEach(function (key) {
    var index = isArray && this.useBrackets ? '[' + key + ']' : key
    if (
      (
        (
          (isObject(obj[key]) && !isEmptyObject(obj[key])) ||
          (Array.isArray(obj[key]) && (!this.keepArray && (obj[key].length !== 0)))
        )
      )
    ) {
      if (isArray && this.useBrackets) {
        var previousKey = path[path.length - 1] || ''
        return this.dot(obj[key], tgt, path.slice(0, -1).concat(previousKey + index))
      } else {
        return this.dot(obj[key], tgt, path.concat(index))
      }
    } else {
      if (isArray && this.useBrackets) {
        tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key]
        tgt[path.concat(index).join(this.separator)] = obj[key]
    }
  }.bind(this))
DotObject.dot = wrap('dot')

;['override', 'overwrite'].forEach(function (prop) {
})

;['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) {
        fields: [{
          subfield: 'value'
        }, {
          subfield: 'value1'
        }]

Fixed code:
const blacklist = ['__proto__', 'prototype', 'constructor']


  const parts = path.split(sep)

  const check = parts.filter(part => blacklist.indexOf(part) === -1)

  if (check.length !== parts.length) {
    throw Error('Refusing to update blacklisted property ' + path)
  }

  return parts
    obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {})
    if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) {
      if (i === keys.length - 1) {
    obj = obj.filter(function (n) {
      return n !== undefined
    })
      ret = ret[keys[0]].filter(function (v) {
        return v !== undefined
      })
DotObject.prototype.transfer = function (
  source,
  target,
  obj1,
  obj2,
  mods,
  merge
) {
    this.set(
      target,
      _process(this.pick(source, obj1, true), mods),
      obj2,
      merge
    )
    this.set(
      target,
        JSON.parse(JSON.stringify(this.pick(source, obj1, false))),
      ),
      obj2,
      merge
    )
    if (i === keys.length - 1) {
 *    "some": {
 *      "thing": "else"
 *    }
 *    "some.thing": "name"
  Object.keys(recipe).forEach(
    function (key) {
      this.set(recipe[key], this.pick(key, obj), tgt)
    }.bind(this)
  )
  Object.keys(obj).forEach(
    function (key) {
      var index = isArray && this.useBrackets ? '[' + key + ']' : key
      if (
        ((isObject(obj[key]) && !isEmptyObject(obj[key])) ||
          (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0))
      ) {
        if (isArray && this.useBrackets) {
          var previousKey = path[path.length - 1] || ''
          return this.dot(
            obj[key],
            tgt,
            path.slice(0, -1).concat(previousKey + index)
          )
        } else {
          return this.dot(obj[key], tgt, path.concat(index))
        }
        if (isArray && this.useBrackets) {
          tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key]
        } else {
          tgt[path.concat(index).join(this.separator)] = obj[key]
        }
    }.bind(this)
  )
DotObject.dot = wrap('dot');
['override', 'overwrite'].forEach(function (prop) {
});
['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) {
const blacklist = ['__proto__', 'prototype', 'constructor']


  const parts = path.split(sep)

  const check = parts.filter(part => blacklist.indexOf(part) === -1)

  if (check.length !== parts.length) {
    throw Error('Refusing to update blacklisted property ' + path)
  }

  return parts
    obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {})
    if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) {
      if (i === keys.length - 1) {
    obj = obj.filter(function (n) {
      return n !== undefined
    })
      ret = ret[keys[0]].filter(function (v) {
        return v !== undefined
      })
DotObject.prototype.transfer = function (
  source,
  target,
  obj1,
  obj2,
  mods,
  merge
) {
    this.set(
      target,
      _process(this.pick(source, obj1, true), mods),
      obj2,
      merge
    )
    this.set(
      target,
        JSON.parse(JSON.stringify(this.pick(source, obj1, false))),
      ),
      obj2,
      merge
    )
    if (i === keys.length - 1) {
 *    "some": {
 *      "thing": "else"
 *    }
 *    "some.thing": "name"
  Object.keys(recipe).forEach(
    function (key) {
      this.set(recipe[key], this.pick(key, obj), tgt)
    }.bind(this)
  )
  Object.keys(obj).forEach(
    function (key) {
      var index = isArray && this.useBrackets ? '[' + key + ']' : key
      if (
        ((isObject(obj[key]) && !isEmptyObject(obj[key])) ||
          (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0))
      ) {
        if (isArray && this.useBrackets) {
          var previousKey = path[path.length - 1] || ''
          return this.dot(
            obj[key],
            tgt,
            path.slice(0, -1).concat(previousKey + index)
          )
        } else {
          return this.dot(obj[key], tgt, path.concat(index))
        }
        if (isArray && this.useBrackets) {
          tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key]
        } else {
          tgt[path.concat(index).join(this.separator)] = obj[key]
        }
    }.bind(this)
  )
DotObject.dot = wrap('dot');
['override', 'overwrite'].forEach(function (prop) {
});
['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) {

  describe('Refuse to update __proto__', function () {
    var obj = { path: [] }

    ;(() => Dot.set('path[0].__proto__.toString', 'test', obj)).should.throw(/Refusing to update/)
  })

  it('Dot.object should disallow to set __proto__', function () {
    var row = { '__proto__.toString': 'hi' }

    var dot = new Dot()
    ;(() => dot.object(row)).should.throw(/Refusing to update/)
  })
        fields: [
          {
            subfield: 'value'
          },
          {
            subfield: 'value1'
          }
        ]

  it('cannot set __proto__ property', function () {
    (() => Dot.str('__proto__.toString', 'hi', {})).should.throw(
      /Refusing to update/
    );
    ({}.toString().should.deepEqual('[object Object]'))
  })