うつろい journal の記事検索ページ — クエリ #q=...(URL フラグメント)を読んで「検索キーワード:○○」と表示する機能。実装はクライアント JS で完結している。
フラグメント(# 以降)はサーバには送信されない仕様(ブラウザがリクエストヘッダから除外する)。そのためサーバログには攻撃が一切残らない。WAF も気づけない。これが DOM-based XSS の特徴。
うつろい journal の記事検索ページ — クエリ #q=...(URL フラグメント)を読んで「検索キーワード:○○」と表示する機能。実装はクライアント JS で完結している。
フラグメント(# 以降)はサーバには送信されない仕様(ブラウザがリクエストヘッダから除外する)。そのためサーバログには攻撃が一切残らない。WAF も気づけない。これが DOM-based XSS の特徴。
フラグメント #q=... にペイロードを入れて、検索ページで alert を実行させてください。サーバには何も送らずに XSS を成立させる。
// 検索ページのクライアント 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*= イベントが実行される
innerHTML で挿入された <script> タグは、ブラウザ仕様上 JavaScript として実行されません(セキュリティ機能)。Lab 01 のような <script>alert(1)</script> は通用しないことに注意。
script タグの代わりに、onerror や onload などのイベントハンドラ属性を使います。<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 の怖さです。