1. Reflected XSS #reflected
最も古典的な XSS。攻撃者が用意した URL を被害者がクリックすると、その URL に含まれるペイロードが応答 HTML に埋め込まれて返り、被害者のブラウザで実行されます。
典型的な攻撃フロー:
- 攻撃者が脆弱な URL を作る:
https://example.com/search?q=<script>alert(1)</script> - 攻撃者が SNS、メール、フィッシングサイト経由で被害者にこの URL を踏ませる
- 被害者のブラウザがその URL を開くと、サーバが
qパラメータをそのまま HTML に埋め込んで返す - 被害者のブラウザが
<script>を実行
Reflected XSS の特徴は「永続化されない」こと。攻撃者は被害者ごとに URL を踏ませる必要があります。
2. Stored XSS #stored
Reflected より被害が深刻なタイプ。攻撃者が一度ペイロードをサイトに保存すれば(投稿、コメント、レビュー、プロフィール等)、そのページを見る他のユーザ全員のブラウザで自動的に実行されます。
典型例:
- 商品レビュー欄に
<script>...</script>を投稿、商品ページを開く全ユーザに到達 - SNS の投稿、コメント、プロフィール
- 掲示板、Q&A サイト、Wiki
Stored XSS は被害者を「だます」必要がない。普通にサイトを利用するだけで攻撃が発動します。SNS のような大規模サイトでは、1つの Stored XSS で何百万ユーザの Cookie が抜かれるリスクがある「ワーム化」も実例があります(2005年 Samy worm 等)。
3. DOM-based XSS #dom-based
クライアント JavaScript がユーザ入力を取得して、それを innerHTML や document.write 等で DOM に注入することで発生する XSS。サーバ側のレスポンスには攻撃文字列が含まれません。
典型例:
// クライアント JavaScript の脆弱な実装
const search = location.hash.substring(1); // # 以降を取得
document.getElementById('result').innerHTML = '検索:' + search;
URL の #(フラグメント)以降はサーバには送信されない仕様。攻撃者が https://example.com/#<img src=x onerror=alert(1)> を被害者に踏ませると、サーバには気づかれずに XSS が成立する。
サーバログには攻撃が一切残らないため、運用側が異変に気づきにくいのが DOM-based XSS の特徴。WAF も基本的にサーバ通信を監視するので、フラグメント経由の攻撃は素通りします。
4. 3つの比較 #comparison
| Reflected | Stored | DOM-based | |
|---|---|---|---|
| 攻撃文字列の場所 | URL/フォーム | サーバ DB | URL フラグメント等 |
| 持続性 | なし(URL 単位) | あり(永続) | なし(URL 単位) |
| 被害範囲 | URL を踏んだ人だけ | 該当ページ訪問者全員 | URL を踏んだ人だけ |
| サーバログ | 残る | 残る | 残らない場合あり |
| 必要な仕掛け | フィッシング等 | 投稿機能の悪用 | フィッシング等 |
5. XSS の真の被害 #why-dangerous
alert(1) はあくまで証明であって、目的ではない。
XSS の練習問題でよく alert(1) や alert(document.domain) を実行させますが、これはあくまで「JavaScript が実行できた」ことを示す目印。本当の攻撃ではもっと深刻な操作が行われます。
- セッション Cookie の窃取 —
document.cookieを取得して攻撃者サーバに送信。被害者になりすましてログイン可能に。 - CSRF 防御の回避 — 同一オリジンから任意のリクエストを送れるので、CSRF 対策の Token 等も取得して送信できる。
- キーロガー設置 — フォームの入力を監視してパスワードや個人情報を盗む。
- ページ改ざん — 偽のログインフォームを表示してフィッシング。
- マルウェア配布 — ダウンロードリンクを差し替える。
これらは Same-Origin Policy の制約下でも実行可能なので、XSS が成立した時点で「攻撃者がそのサイト上で被害者と同じ権限を持つ」と考えるべき。