Lodash Path Resolution Vulnerabilities and Fixes
Lodash is one of the most widely used utility libraries in the
JavaScript ecosystem, but its deep object manipulation functions
historically harbored significant prototype pollution vulnerabilities.
These flaws stemmed from how internal path resolution utilities parsed
object keys and property paths without properly validating dangerous
accessors like __proto__ and
constructor.prototype. Over several major releases, the
Lodash maintainers implemented native internal protections within core
utilities—such as _.set, _.merge, and
_.defaultsDeep—to sanitize path traversal, block
unauthorized prototype access, and prevent property injection into the
global object scope.
The Mechanics of Path Resolution Vulnerabilities
In JavaScript, objects inherit properties from
Object.prototype. When a library recursively creates or
updates nested structures based on user-supplied paths (e.g.,
'a.b.c' or ['a', 'b', 'c']), it must resolve
each segment step-by-step.
Prior to major security patches, Lodash did not adequately sanitize
segments matching special object properties. Attackers could supply
payloads with keys like __proto__ or
constructor.prototype. When Lodash functions traversed
these segments, the internal traversal logic followed standard
JavaScript property access, navigating directly to the global object
prototype. Any property assigned at the end of such a path was then
added to every object in the JavaScript runtime, causing Prototype
Pollution, leading to unexpected behavior, denial of service (DoS), or
remote code execution (RCE).
Key Historical CVEs and Affected Utilities
Several high-profile vulnerabilities centered directly on Lodash's object path resolution utilities:
1.
CVE-2018-3721 and CVE-2018-16487: _.defaultsDeep and
_.merge
- Affected Functions:
_.merge,_.mergeWith,_.defaultsDeep - Resolution Versions: Fixed in versions 4.17.5 and 4.17.11
- Vulnerability: When processing deeply nested
objects, recursive merge operations copied properties from source
objects to destination objects without verifying if a key was
__proto__. Passing an object containing{ "__proto__": { "admin": true } }allowed the target object's prototype to be modified natively.
2.
CVE-2019-10744: _.defaultsDeep via constructor
Traversal
- Affected Functions:
_.defaultsDeep,_.merge - Resolution Version: Fixed in version 4.17.12
- Vulnerability: Although earlier patches blocked
direct
__proto__properties, attackers bypassed this by targetingconstructor.prototype. Becauseconstructorwas evaluated as a standard property rather than an internal reference,defaultsDeepaccepted a path such asconstructor.prototype.pollutedand recursively assigned properties directly toObject.prototype.
3. CVE-2020-8203:
_.zipObjectDeep Path Parsing
- Affected Functions:
_.zipObjectDeep,_.set,_.setWith - Resolution Version: Fixed in version 4.17.19
- Vulnerability: The
_.zipObjectDeeputility pairs an array of property identifiers with an array of values to construct a deep object. If a path array contained__proto__(such as['__proto__.polluted', 'value']), the internal helper resolved the path and executed assignments directly on the root object prototype without evaluating the safety of the path tokens.
Native Patch Implementations
The Lodash core team addressed these vulnerabilities by altering the internal functions responsible for object creation, property retrieval, and path traversal:
- Disallowing Dangerous Keys: Internal utilities,
notably
baseSet,baseMerge, andbaseAssignValue, were updated to explicitly ignore or bypass keys matching__proto__,toString,valueOf, andconstructor. - Path Token Sanitization: Path parsers that break
string paths into array tokens (e.g., converting
'a.b.c'to['a', 'b', 'c']) were modified. If a path segment attempts to access the object's prototype directly, the resolution logic halts or drops the operation instead of traversing the reference. - Safe Prototype Checks with
safeGet: Lodash introduced and improved internal guards such assafeGet(). Instead of using direct index property access (object[key]),safeGetverifies whether accessing the property exposes prototype inheritance chains before yielding the reference. - Constructor Property Guarding: Checks were added to
ensure that if a key equals
'constructor', property assignment only proceeds if the value is an own property of the object (usingObject.prototype.hasOwnProperty), preventing navigation down toconstructor.prototype.