0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】DOM-Based XSS Case Study:2010 年 Twitter(現 X)XSS ワーム事件の深度解析

0
Last updated at Posted at 2025-12-26

はじめに

「クリック不要、マウスを乗せただけで発火」
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 が成立する」だけではなく、

  1. トリガ(hover など)
  2. 実行
  3. 自己複製(同じ payload を別の可視領域へ)
  4. 拡散(次のユーザーへ)

を備えた自己増殖型の攻撃です。Samy ワーム(MySpace, 2005)は歴史的な先例としてよく参照されます。


5. 無害 PoC

“DOM XSS の悪い実装”と“修正実装”をローカルで再現する

ここからは ローカル HTML で完結する無害 PoC です。

  • ネットワーク送信なし
  • 自己拡散なし
  • 実サービスに影響なし
  • 目的は「Source→Sink の危険な流し方」を体感すること

5.1 手順

  1. 下の内容を dom-xss-demo.html として保存
  2. ブラウザで開く
  3. URL の末尾に次を付ける(例)
    #!javascript:alert(document.domain)
  4. 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 ではなく攻撃面」
この前提で設計することが最大の防御です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?