Hello there, ('ω')ノ
概要(短く)
- 攻撃のアイデア:
Object.prototypeに任意プロパティを追加できれば、ページ内の多くのオブジェクトがその値を継承する。 - クライアント側での典型的な入力経路:クエリ文字列、ハッシュ(フラグメント)、JSON入力(postMessageやXHR)など。
- 検査方法:ブラウザのコンソールで
Object.prototype.<任意キー>を覗いて成功を確認する。成功したらすぐ消す(クリーンアップ)。
手順(ステップバイステップ)
1) まずは「安全な」識別子を決める
テストで使うキーはユニークにして、他の影響を避ける。例えば:
polluteTest_20250917_abc123
(※必ず自分で生成したランダムか日時入りなどのユニーク文字列を使ってください)
2) クエリ文字列で試す(ブラウザのアドレスバーに貼る)
典型例:
https://vulnerable-website.com/?__proto__[polluteTest_20250917_abc123]=1
あるいはドット表記:
https://vulnerable-website.com/?__proto__.polluteTest_20250917_abc123=1
ページを読み込み直したら、ブラウザのコンソールで確認。
// 成功確認 Object.prototype.polluteTest_20250917_abc123 // 期待: "1" や "true" など(攻撃が成功していれば値が見える)
成功なら Object.prototype にプロパティが追加されているはずです。失敗なら次へ。
3) URLハッシュ(フラグメント)で試す
フラグメント(#以降)を使うサイトがあります:
https://vulnerable-website.com/#__proto__[polluteTest_20250917_abc123]=1
同様にコンソールで Object.prototype... を確認。
4) JSON入力を試す(例:postMessage、XHRのbody、フォーム)
WebアプリがJSONをそのまま JSON.parse() → merge() に渡す場合:
// ブラウザのコンソール、デベロッパーツールでテストできる(被検サイトにPOSTしないこと) const malicious = '{"__proto__":{"polluteTest_20250917_abc123":"1"}}'; const obj = JSON.parse(malicious); console.log(obj.hasOwnProperty('__proto__')); // true(JSONから作るとキーとして扱われる)
もしその obj が merge(target, obj) のような処理に渡ると、前述した通りプロトタイプ汚染が起きることがあります。
5) constructor-based trick(成功しないときの別手法)
一部の実装では __proto__ がブロックされていても、constructor.prototype を通して汚染できる場合があります。
例(クエリ):
?constructor[prototype][polluteTest_20250917_abc123]=1
JSONでは:
{ "constructor": { "prototype": { "polluteTest_20250917_abc123": "1" } } }
これも同様に merge() の仕方によっては Object.prototype に影響します。
6) 成功確認とクリーンアップ(必須)
成功を確認したら必ず削除して元に戻します。放置するとテストブラウザ内で予期せぬ動作を招きます。
// 確認 console.log(Object.prototype.polluteTest_20250917_abc123); // 消す(クリーンアップ) delete Object.prototype.polluteTest_20250917_abc123;
(delete で消えない特殊ケースもあるので、その場合はブラウザを再起動してください)
実務での検査ポイント(ガジェット探索)
Prototype Pollution が実害になるかは「ガジェット(gadget)」が存在するかにかかっています。見つけるべき典型的なパターン:
外部リソースを動的に組み立てしている箇所:
script.src,img.src,iframe.src,link.hrefなど。 例:script.src = config.transport_url + '/example.js'→transport_urlがガジェットになり得る。DOMに直接挿入する箇所:
innerHTML,insertAdjacentHTML(サニタイズ無しの挿入は特に危険)危険なAPIの呼び出しに使われる値:
eval(),setTimeout(string),Function()などに間接的に渡るプロパティライブラリの設定オブジェクト: サードパーティのウィジェットやライブラリに渡す
optionsやconfig。未定義時にプロトタイプを参照する実装は要注意。
実戦で役に立つ「コツとポイント」
- 最小で目立たないテストを使う:
polluteTest_<epoch>_<rand>のようにユニークでかつ他のロジックに影響しにくいキーにする。 - 影響範囲を想像する:例えば
isAdminといった既存の重要キーは絶対に使わない(テストで誤って実害を生む可能性がある)。 hasOwnPropertyを使って確認:Object.prototypeにあるプロパティか確認したいときは{}.hasOwnProperty.call(Object.prototype, 'key')等。- 読み取り検出(高度):
Object.defineProperty(Object.prototype, 'x', { get(){ debugger; return 'v'; }})のようにして、読み出し箇所でデバッガが止まるか確認できる(ただし乱用はしない)。 - 自動化ツール:Burpの拡張、DOM Invaderなどは効率化に役立ちます。手動で試すことを先に覚えておくとツール結果の精査ができる。
- ログを取る:どのURLで何を試して何が返ったかをメモしておく(テスト履歴)。
- 必ずクリーンアップ:テスト後は
delete Object.prototype.<key>を忘れずに。ブラウザ再起動が確実。
よくある誤解と注意点
- JSON.parse() は安全? → いいえ。
JSON.parse()自体は__proto__を普通のキーとして作るが、それをどのように処理(マージ)するかで危険になる。 - オブジェクトリテラルとJSONの違い:
{ __proto__: {...} }のリテラルはプロトタイプを書き換えるが、JSON.parse('{"__proto__":{}}')は単なるキーでhasOwnProperty('__proto__') === trueになる点を覚えておく。 - すべてのサイトでできるわけではない:堅牢なサイトは入力をサニタイズしたり、プロトタイプを
nullにしたオブジェクト(Object.create(null))を使っているため影響を受けない。
簡単チェックリスト(検査時)
- クエリストリング、ハッシュ、JSONを順番に試す。
- 成功確認:
Object.prototype.<あなたのキー>をコンソールで確認。 - ガジェット探索:動的にURLやHTMLを組み立てている箇所を探す。
- 攻撃シナリオを想像(外部スクリプト読み込み、innerHTMLによるXSSなど)。
- クリーンアップしてから次のテストへ。
最後に(重要な倫理と法的注意)
- 必ず許可を得た環境(テスト用ラボや合意済みの対象)でのみテストしてください。 無断で他人のサイトをテストすると不正アクセスや損害賠償の問題になります。
- 練習用ラボ(例えばPortSwiggerのLABSやその他のCTF環境)で実践を繰り返すことを強く推奨します。
Best regards, (^^ゞ