Hello there, ('ω')ノ
ねらい
このLABは、コメント欄に許可された“安全そうなHTML”だけで、グローバル変数を書き換えるDOM Clobbering(DOM汚染)を起こし、アプリのJSがそれを信じて使った瞬間にXSS(alert)を発火させるのがゴールです。 ポイントは、アプリ側のコードが使う次のパターンの危険性を突くこと:
let defaultAvatar = window.defaultAvatar || { avatar: '/resources/images/avatarDefault.svg' }
上記は、window.defaultAvatar が“何か値”ならそれを採用し、なければデフォルトを使う、という書き方。ここに「値があるように見せる偽物」をDOMから差し込めば、アプリがそれを採用してくれます。
まずは絵で理解(何が起きる?)
コメントにタグを2つ投稿し、同じidを付ける → ブラウザはwindow.defaultAvatarというコレクション(HTMLCollection)を用意し、同名要素の束として扱います。
2つ目のに name="avatar" を付ける → そのコレクションはavatarというプロパティで、この2つ目のを参照できるようになります(“名前付き要素”のショートカット)。
アプリ側はコメント表示の途中でdefaultAvatar.avatarを画像のsrcなどに使う → 属性内が壊れて onerror=alert(1) が注入され、Chromeでalertが発火。
手順(クリックごとに“なぜ”が分かる)
1) 1回目のコメント投稿:DOMを“仕込み”で汚染
以下の2つのリンクを、同じコメントにそのまま貼って投稿します。
<a id=defaultAvatar> <a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
- なぜ2つ? 同じid=defaultAvatarが複数あると、ブラウザはwindow.defaultAvatarにHTMLCollectionを作り、さらに name="avatar" により、defaultAvatar.avatar で2つ目のにアクセスできるようになります。
- なぜ cid: ? LAB環境のDOMPurifyはcid:を許容し、しかもダブルクォート(")をURLエンコードしないため、" が 実行時 に "(ダブルクォート)へ戻り、属性を壊せます。
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:"onerror=alert(1)//">
2回目のコメント(再評価トリガ)
任意のテキスト
つまずきポイント&対処
alertが出ない
- 2つのが同じコメント内に入っているか確認。
- 2回目のコメント(またはページ再読み込み)でスクリプトが再評価されるタイミングを作る。
- ブラウザはChromeを使用(LAB要件)。
サニタイズで弾かれる
- 文字が変換されていないか確認(特に" 部分)。コピペを推奨。
実務での防御(覚えやすく3つ)
- window.X || {…} をやめる 明示的に型チェックし、想定外の値(HTMLCollectionなど)を拒否。
- 名前付き要素によるグローバル汚染を前提にしない DOM参照はdocument.getElementByIdなど明示的に。
- サニタイザの“通すスキーム”を最小化 不要なスキーム(例:cid:)は禁止し、属性内のクォート復元で壊れないよう設定。
まとめ
- アプリの“グローバル変数にフォールバック代入”という書き方は、DOM Clobberingに弱く、
- コメント欄にid/nameを仕込んだを入れるだけでwindow.defaultAvatarを“本物っぽい偽物”へ差し替えられます。
- さらにcid:と" の実行時復元を利用して、属性を壊してonerror=alert(1)を発火——これが本LABの勝ち筋です(Chrome前提)。
Best regards, (^^ゞ