VYPR
Critical severityNVD Advisory· Published Aug 18, 2020· Updated Sep 16, 2024

Prototype Pollution

CVE-2020-7707

Description

The property-expr package before 2.0.3 is vulnerable to Prototype Pollution via the setter function, allowing attackers to pollute Object.prototype.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

The property-expr package before 2.0.3 is vulnerable to Prototype Pollution via the setter function, allowing attackers to pollute Object.prototype.

Overview

CVE-2020-7707 is a Prototype Pollution vulnerability in the property-expr npm package versions before 2.0.3. The root cause lies in the setter function, which constructs a path to set a property on an object without sanitizing the input for dangerous property names such as __proto__, constructor, or prototype [1]. This allows an attacker to inject properties into the global Object.prototype.

Exploitation

The vulnerability can be exploited by providing a crafted path string to the setter function. For example, a path like __proto__.polluted would cause the function to set a property on Object.prototype [1]. This is a classic "property definition by path" style of Prototype Pollution [3][4]. The attack requires no authentication and can be triggered if the application uses user-controlled input as part of the path.

Impact

Successful exploitation leads to Prototype Pollution, where properties are injected into the base Object.prototype. This can result in unexpected behavior, denial of service, or remote code execution depending on how the application uses object properties [3]. The vulnerability can also be used to bypass security checks or tamper with application logic.

Mitigation

The issue was fixed in version 2.0.3 of property-expr by adding checks that reject paths containing __proto__, constructor, or prototype [1]. Users are advised to upgrade to at least version 2.0.3. No workaround is available other than updating the package.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
property-exprnpm
< 2.0.32.0.3

Affected products

2

Patches

1
df846910915d

fix: prototype polution vector

https://github.com/jquense/exprJason QuenseAug 17, 2020via ghsa
3 files changed · +58 20
  • compiler.js+12 4 modified
    @@ -8,7 +8,7 @@ function makeSafe(path, param) {
         parts = split(path),
         isLast
     
    -  forEach(parts, function(part, isBracket, isArray, idx, parts) {
    +  forEach(parts, function (part, isBracket, isArray, idx, parts) {
         isLast = idx === parts.length - 1
     
         part = isBracket || isArray ? '[' + part + ']' : '.' + part
    @@ -36,7 +36,15 @@ function expr(expression, safe, param) {
     
     module.exports = {
       expr,
    -  setter: function(path) {
    +  setter: function (path) {
    +    if (
    +      path.indexOf('__proto__') !== -1 ||
    +      path.indexOf('constructor') !== -1 ||
    +      path.indexOf('prototype') !== -1
    +    ) {
    +      return (obj) => obj
    +    }
    +
         return (
           setCache.get(path) ||
           setCache.set(
    @@ -46,7 +54,7 @@ module.exports = {
         )
       },
     
    -  getter: function(path, safe) {
    +  getter: function (path, safe) {
         var key = path + '_' + safe
         return (
           getCache.get(key) ||
    @@ -55,5 +63,5 @@ module.exports = {
             new Function('data', 'return ' + expr(path, safe, 'data'))
           )
         )
    -  }
    +  },
     }
    
  • index.js+24 13 modified
    @@ -7,14 +7,14 @@ function Cache(maxSize) {
       this._maxSize = maxSize
       this.clear()
     }
    -Cache.prototype.clear = function() {
    +Cache.prototype.clear = function () {
       this._size = 0
       this._values = Object.create(null)
     }
    -Cache.prototype.get = function(key) {
    +Cache.prototype.get = function (key) {
       return this._values[key]
     }
    -Cache.prototype.set = function(key, value) {
    +Cache.prototype.set = function (key, value) {
       this._size >= this._maxSize && this.clear()
       if (!(key in this._values)) this._size++
     
    @@ -41,23 +41,34 @@ module.exports = {
     
       normalizePath: normalizePath,
     
    -  setter: function(path) {
    +  setter: function (path) {
         var parts = normalizePath(path)
     
         return (
           setCache.get(path) ||
    -      setCache.set(path, function setter(data, value) {
    -        var index = 0,
    -          len = parts.length
    +      setCache.set(path, function setter(obj, value) {
    +        var index = 0
    +        var len = parts.length
    +        var data = obj
    +
             while (index < len - 1) {
    +          let part = parts[index]
    +          if (
    +            part === '__proto__' ||
    +            part === 'constructor' ||
    +            part === 'prototype'
    +          ) {
    +            return obj
    +          }
    +
               data = data[parts[index++]]
             }
             data[parts[index]] = value
           })
         )
       },
     
    -  getter: function(path, safe) {
    +  getter: function (path, safe) {
         var parts = normalizePath(path)
         return (
           getCache.get(path) ||
    @@ -73,8 +84,8 @@ module.exports = {
         )
       },
     
    -  join: function(segments) {
    -    return segments.reduce(function(path, part) {
    +  join: function (segments) {
    +    return segments.reduce(function (path, part) {
           return (
             path +
             (isQuoted(part) || DIGIT_REGEX.test(part)
    @@ -84,17 +95,17 @@ module.exports = {
         }, '')
       },
     
    -  forEach: function(path, cb, thisArg) {
    +  forEach: function (path, cb, thisArg) {
         forEach(Array.isArray(path) ? path : split(path), cb, thisArg)
    -  }
    +  },
     }
     
     function normalizePath(path) {
       return (
         pathCache.get(path) ||
         pathCache.set(
           path,
    -      split(path).map(function(part) {
    +      split(path).map(function (part) {
             return part.replace(CLEAN_QUOTES_REGEX, '$2')
           })
         )
    
  • test.js+22 3 modified
    @@ -2,15 +2,17 @@ const a = require('assert')
     const expr = require('./index')
     const compiler = require('./compiler')
     
    +const root = (typeof global == 'object' && global) || this
    +
     function runSetterGetterTests({ setter, getter }) {
       let obj = {
         foo: {
           bar: ['baz', 'bux'],
           fux: 5,
           '00N40000002S5U0': 1,
           N40000002S5U0: 2,
    -      'FE43-D880-21AE': 3
    -    }
    +      'FE43-D880-21AE': 3,
    +    },
       }
     
       // -- Getters --
    @@ -47,7 +49,24 @@ function runSetterGetterTests({ setter, getter }) {
       a.strictEqual(obj.foo.bar[1], 'bot')
     
       setter('[\'foo\']["bar"][1]')(obj, 'baz')
    +
       a.strictEqual(obj.foo.bar[1], 'baz')
    +  //
    +  ;['__proto__', 'constructor', 'prototype'].forEach((keyToTest) => {
    +    setter(`${keyToTest}.a`)({}, 'newValue')
    +
    +    a.notEqual(root['a'], 'newValue')
    +
    +    const b = 'oldValue'
    +
    +    a.equal(b, 'oldValue')
    +    a.notEqual(root['b'], 'newValue')
    +
    +    setter(`${keyToTest}.b`)({}, 'newValue')
    +    a.equal(b, 'oldValue')
    +    a.notEqual(root['b'], 'newValue')
    +    a.equal(root['b'], undefined)
    +  })
     }
     
     console.log('--- Test Start ---')
    @@ -89,7 +108,7 @@ a.strictEqual(expr.join(parts), 'foo.baz["bar"][1]')
     
     let count = 0
     
    -expr.forEach('foo.baz["bar"][1]', function(part, isBracket, isArray, idx) {
    +expr.forEach('foo.baz["bar"][1]', function (part, isBracket, isArray, idx) {
       count = idx
     
       switch (idx) {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.