はじめに
「クリック不要、マウスを乗せただけで発火」
2010 年 9 月、Twitter の Web クライアントで発生した “mouseover 系 XSS” は、短時間で大規模拡散(worm-like)へ発展した代表例です。
本記事では、DOM-Based XSS の構造(Source/Sink)と、なぜワーム化しやすいのかを整理し、最後に 無害 PoC(教学版) として「悪い実装/修正実装」をローカルで再現できる形で添えます。
※ 実サービスを攻撃できるコードや拡散コードは扱いません。
1. 事件の概要
報道・分析によれば、当時の Twitter では hover(onmouseover)だけでスクリプトが実行される 形の XSS が悪用され、ポップアップ・表示改変・リダイレクトなどが多発しました。
さらに一部は worm-like(蠕虫的) に拡散し、リツイート等の拡散挙動が観測されたとされています。
//<![CDATA[
(function(g){var a=location.href.split("#!")[1];if(a){g.location=g.HBR=a;}})(window);
//]]>
2. DOM-Based XSS とは何か
DOM-Based XSS は、
- サーバー応答の HTML ではなく
- ブラウザ側 JavaScript が
- URL / DOM 由来の値をそのまま危険な場所(Sink)へ流し込む
ことで発生する XSS です。OWASP / PortSwigger でも重要なカテゴリとして整理されています。
典型の Source / Sink
| 役割 | 例 |
|---|---|
| Source |
location.href / location.hash / document.URL
|
| Sink |
location / innerHTML / eval / setTimeout(string)
|
3. なぜ onmouseover が“加速装置”になったのか
多くの XSS は「クリック」など明確な操作が必要ですが、hover は違います。
- 操作の自覚がほぼない
- タイムライン閲覧中に自然に発火する
- 画面上の複数要素で連続発火し得る
そのため、感染拡大の摩擦が極端に小さくなる。これが 2010 年の“爆発力”の背景です。
4. XSS ワーム(XSS Worm)とは
XSS ワームは「XSS が成立する」だけではなく、
- トリガ(hover など)
- 実行
- 自己複製(同じ payload を別の可視領域へ)
- 拡散(次のユーザーへ)
を備えた自己増殖型の攻撃です。Samy ワーム(MySpace, 2005)は歴史的な先例としてよく参照されます。
5. 無害 PoC
“DOM XSS の悪い実装”と“修正実装”をローカルで再現する
ここからは ローカル HTML で完結する無害 PoC です。
- ネットワーク送信なし
- 自己拡散なし
- 実サービスに影響なし
- 目的は「Source→Sink の危険な流し方」を体感すること
5.1 手順
- 下の内容を
dom-xss-demo.htmlとして保存 - ブラウザで開く
- URL の末尾に次を付ける(例)
#!javascript:alert(document.domain) - Vulnerable ボタンは発火、Fixed はブロックすることを確認
5.2 デモコード
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>DOM XSS Demo (Harmless)</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; line-height: 1.6; padding: 16px; }
code { background: #f4f4f4; padding: 2px 6px; border-radius: 4px; }
pre { background: #0b1020; color: #eaeaea; padding: 12px; border-radius: 8px; overflow:auto; }
button { padding: 10px 14px; margin: 6px 0; cursor: pointer; }
.box { border: 1px solid #ddd; border-radius: 10px; padding: 12px; margin: 12px 0; }
</style>
</head>
<body>
<h1>DOM XSS Demo(無害・教学用)</h1>
<p>
例:URL の末尾に <code>#!javascript:alert(document.domain)</code> を付けてから、下のボタンを押してください。
</p>
<div class="box">
<h2>❌ Vulnerable(悪い例)</h2>
<p>
URL の <code>#!</code> 以降を取り出し、検証せずに <code>location</code> へ代入します。
<br/>→ <code>javascript:</code> などが通ると “実行” に繋がります。
</p>
<button id="runVuln">Run vulnerable handler</button>
<pre id="vulnOut"></pre>
</div>
<div class="box">
<h2>✅ Fixed(良い例)</h2>
<p>
<strong>http/https のみ許可</strong>するプロトコル白リスト方式でブロックします。
</p>
<button id="runFixed">Run fixed handler</button>
<pre id="fixedOut"></pre>
</div>
<script>
// Source: location.href(攻撃者がコントロール可能)
function getHashBangPayload() {
const raw = location.href.split("#!")[1];
return raw ? raw.trim() : "";
}
// ❌ 悪い例:入力をそのまま location へ流す(危険な Sink)
function vulnerable() {
const a = getHashBangPayload();
document.getElementById("vulnOut").textContent =
"Parsed = " + a + "\n\n[!] This is intentionally vulnerable for teaching.";
if (a) {
// Sink: location
location = a;
}
}
// ✅ 良い例:URL として解析し、許可したプロトコルだけ通す
function fixed() {
const a = getHashBangPayload();
const out = document.getElementById("fixedOut");
out.textContent = "Parsed = " + a;
if (!a) return;
let url;
try {
// 相対 URL も扱えるよう base を指定
url = new URL(a, location.origin);
} catch (e) {
out.textContent += "\nBlocked: invalid URL";
return;
}
const allowed = new Set(["http:", "https:"]);
if (!allowed.has(url.protocol)) {
out.textContent += "\nBlocked: protocol not allowed -> " + url.protocol;
return;
}
out.textContent += "\nAllowed. Navigating to: " + url.href;
// デモなので本当に遷移させたくない場合は次行をコメントアウト
location.href = url.href;
}
document.getElementById("runVuln").addEventListener("click", vulnerable);
document.getElementById("runFixed").addEventListener("click", fixed);
</script>
</body>
</html>
5.3 この PoC が教えてくれること
- URL(location)を Source として扱うのは危険
- location 代入は強い Sink(遷移・スキーム解釈が絡む)
- 修正は「ブラックリスト」より プロトコル白リストが堅い
- DOM-Based XSS はサーバーを通らないので、WAF/ログ頼みだと見落としやすい
まとめ:DOM は攻撃面である
2010 年の Twitter 事件は、
- DOM 入力(URL)を安易に信頼し
- 低摩擦トリガ(hover)で発火し
- SNS の拡散機構と結びついた
とき、XSS が ワームとして暴走することを示しました。
「DOM はただの UI ではなく攻撃面」
この前提で設計することが最大の防御です。