utsuroi xss labs Lab 03 / 5 — Apprentice
03

DOM-based XSS — URL フラグメントから innerHTML 注入

DOM-based XSS via URL fragment

うつろい journal の記事検索ページ — クエリ #q=...(URL フラグメント)を読んで「検索キーワード:○○」と表示する機能。実装はクライアント JS で完結している。

フラグメント(# 以降)はサーバには送信されない仕様(ブラウザがリクエストヘッダから除外する)。そのためサーバログには攻撃が一切残らない。WAF も気づけない。これが DOM-based XSS の特徴。

フラグメント #q=... にペイロードを入れて、検索ページで alert を実行させてください。サーバには何も送らずに XSS を成立させる。

URL: 未送信
journal.utsuroi.example.com / search
クライアント JavaScript(脆弱な実装)
// 検索ページのクライアント JS
const hash = location.hash.substring(1);  // #以降を取得
const params = new URLSearchParams(hash);
const q = params.get('q');

// ★脆弱な実装: フラグメント値を innerHTML にそのまま
document.getElementById('search-display').innerHTML = q;

location.hash はブラウザ内の値で、サーバには送信されない。サーバ側の対策(エスケープ、WAF 等)では DOM-based XSS は防げない。クライアント JS 側で対策する必要がある。

ペイロードの流れ
URL: https://journal.utsuroi.example.com/search#q=...
                                            ↑ サーバに送信されない部分
   ↓
location.hash → JavaScript が読む
   ↓
innerHTML へ展開 → ブラウザが HTML としてパース
   ↓
<script> や on*= イベントが実行される
解析結果
    ヒント 1 — innerHTML と script タグ

    innerHTML で挿入された <script> タグは、ブラウザ仕様上 JavaScript として実行されません(セキュリティ機能)。Lab 01 のような <script>alert(1)</script> は通用しないことに注意。

    ヒント 2 — 代替ベクトル

    script タグの代わりに、onerroronload などのイベントハンドラ属性を使います。<img src=x onerror=...> は古典中の古典。

    正解(ネタバレ)

    ペイロード:
    <img src=x onerror=alert('DOM-XSS')>

    このフラグメントを URL に含めて開くと、検索ページの JS が innerHTML<img> 要素を挿入。src=x は存在しない画像を読み込みに行くので必ず失敗 → onerror が発火 → alert が実行されます。

    このペイロードを攻撃者が SNS や URL 短縮サービスで配布すれば、被害者がクリックするだけで JavaScript が走ります。サーバログには 一切痕跡が残らないのが DOM-based XSS の怖さです。