Shikata Ga Nai

Private? There is no such things.

【有料試作版】PortSwigger LAB解説:Exploiting DOM clobbering to enable XSS(DOM ClobberingでXSS発火)

Hello there, ('ω')ノ

ねらい

このLABは、コメント欄に許可された“安全そうなHTML”だけで、グローバル変数を書き換えるDOM Clobbering(DOM汚染)を起こし、アプリのJSがそれを信じて使った瞬間にXSS(alert)を発火させるのがゴールです。 ポイントは、アプリ側のコードが使う次のパターンの危険性を突くこと:

let defaultAvatar = window.defaultAvatar || { avatar: '/resources/images/avatarDefault.svg' }

上記は、window.defaultAvatar が“何か値”ならそれを採用し、なければデフォルトを使う、という書き方。ここに「値があるように見せる偽物」をDOMから差し込めば、アプリがそれを採用してくれます。


まずは絵で理解(何が起きる?)

  1. コメントにタグを2つ投稿し、同じidを付ける → ブラウザはwindow.defaultAvatarというコレクション(HTMLCollection)を用意し、同名要素の束として扱います。

  2. 2つ目のname="avatar" を付ける → そのコレクションはavatarというプロパティで、この2つ目のを参照できるようになります(“名前付き要素”のショートカット)。

  3. そのhref に、DOMPurifyが通してしまうcid:スキームで""(ダブルクォート)を紛れ込ませた値を入れる → 実行時に" → "(実クォート)へデコードされ、最終的にdefaultAvatar.avatarcid:"onerror=alert(1)// という危ない文字列になる。

  4. アプリ側はコメント表示の途中でdefaultAvatar.avatarを画像のsrcなどに使う → 属性内が壊れて onerror=alert(1) が注入され、Chromeでalertが発火。


手順(クリックごとに“なぜ”が分かる)

1) 1回目のコメント投稿:DOMを“仕込み”で汚染

以下の2つのリンクを、同じコメントにそのまま貼って投稿します。

<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">

2) 2回目のコメント投稿(または再表示):仕込みが効いてalert発火

  • 同じ記事にテキストだけの適当なコメントを1つ投稿して、ページを再描画させます。
  • 再表示のタイミングで、先ほどの window.defaultAvatar が評価され、defaultAvatar.avatar として cid:"onerror=alert(1)// が使われ、alert(1) が出ます。 (Chrome限定の理由:この一連の“名前付き要素の解決”や“文字列化の挙動”がChrome基準のため)

どうしてこれでXSSになるの?(内部の理屈)

  • 危険パターン:window.X || {…} これは、「window.X“真”っぽい値(HTMLCollectionなど何でも)を置けば採用される」ことを意味します。
  • DOM Clobbering: idやnameを工夫して、window.defaultAvatar のようなグローバル変数名DOM由来の値を差し込む古典テクです。
  • DOMPurifyの穴(本LAB仕様): cid: を許し、"" に復元してしまうため、属性コンテキストを壊してonerrorを発火させられます。

コピペ用(そのまま使える)

1回目のコメント(仕込み)

<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">

2回目のコメント(再評価トリガ)

任意のテキスト

つまずきポイント&対処


実務での防御(覚えやすく3つ)

  1. window.X || {…} をやめる 明示的に型チェックし、想定外の値(HTMLCollectionなど)を拒否。
  2. 名前付き要素によるグローバル汚染を前提にしない DOM参照はdocument.getElementByIdなど明示的に。
  3. サニタイザの“通すスキーム”を最小化 不要なスキーム(例:cid:)は禁止し、属性内のクォート復元で壊れないよう設定。

まとめ

Best regards, (^^ゞ