【実務・中級編】クロスサイトスクリプティング(XSS)の分類とDOMベースXSSの仕組み – アプリケーションセキュリティ & 安全な開発防御ガイド

XSSは「終わった脆弱性」か?――DOMベースXSSが突きつける現代の脅威

「XSS対策? htmlspecialchars を通せば終わりでしょ」。
もし君のチームでそんな会話が聞こえてきたら、そのプロジェクトは既に危うい。確かに反射型や蓄積型XSSは、サーバーサイドでエスケープを徹底すれば防げる。だが、現代のWebアプリケーションは「クライアントサイド」で動く巨大なロジックの塊だ。

今日は、教科書的な説明はすっ飛ばして、現場で本当に恐ろしい「DOMベースXSS」の深淵に切り込む。インフラからフロントまでを統括する立場として、君たちが明日から書くコードに何をすべきか、泥臭い現実を語ろう。

—

1. XSSの「今」を知る:3つの分類と落とし穴

まず前提を整理する。XSSは大きく3つに分類されるが、攻撃者が狙うポイントは明確に異なる。

  • 反射型 (Reflected XSS): ユーザーの入力をURLパラメータ等から受け取り、即座に画面へ出力する。サーバーサイドのレスポンスに直接悪意あるスクリプトが混入する。
  • 蓄積型 (Stored XSS): データベースに悪意あるスクリプトを保存させる。掲示板やプロフィール欄が標的。全ユーザーが被害に遭うため、影響範囲が最も広い。
  • DOMベースXSS: これが今回の主役だ。「サーバーのレスポンスは関係ない」。JavaScriptがURLのフラグメント(#以降)やローカルストレージからデータを読み取り、そのまま innerHTML 等の「シンク(危険な実行関数)」に流し込むことで発生する。

なぜDOMベースが厄介なのか?
サーバーログには何も残らない。WAF(Web Application Firewall)をすり抜ける。なぜなら、悪意あるコードはサーバーを経由せず、クライアントのブラウザ内で完結するからだ。

—

2. DOMベースXSSの「PoC」:なぜJavaScriptは騙されるのか

攻撃者は、開発者が「サーバーに送らないデータなら安全だろう」と油断している隙を突く。例えば、こんな脆弱なコードを君は書いていないか?

// 脆弱な例:URLのパラメータをそのまま表示するコード
const params = new URLSearchParams(window.location.search);
const username = params.get(“name”);
// ここでinnerHTMLに直接流し込むのが”シンク”(危険な接点)
document.getElementById(“welcome-msg”).innerHTML = “ようこそ、” + username + “さん!”;

もし攻撃者が ?name= というURLを送りつければ、ブラウザは即座にそのスクリプトを実行する。開発者が必死にサーバーサイドでエスケープ処理を書いていても、このコードには何の影響も与えないんだ。

—

3. 完全防御のためのセキュア実装術

対策の鉄則は「信頼できない入力値を直接シンクに渡さない」こと、そして「実行コンテキストを制御する」ことだ。

① JavaScriptでのセキュアな実装(修正例)

innerHTML は禁じ手だ。テキストを表示するだけなら textContent を使う。これだけで、ブラウザはタグをパースせず、純粋な文字列として扱うようになる。

// 修正後:textContentを使用する
const params = new URLSearchParams(window.location.search);
const username = params.get(“name”);

// textContentなら、悪意あるタグもただの文字として表示される(安全)
document.getElementById(“welcome-msg”).textContent = “ようこそ、” + username + “さん!”;

② CSP(Content Security Policy)によるインフラレベルの防御

コードの修正が追いつかないレガシーな箇所がある場合でも、CSPがあれば「防波堤」を作れる。HTTPレスポンスヘッダーに以下を設定せよ。

Nginx設定例:インラインスクリプトの実行を禁止し、攻撃を防ぐ
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’;”;

  • script-src 'self':信頼できるソースからのスクリプトのみ実行を許可。
  • object-src 'none':プラグイン経由の攻撃を遮断。

—

4. プロの現場で生き残るための「思考の癖」

セキュリティは「ツールを入れたら終わり」ではない。以下の3点をチームの文化として根付かせてほしい。

1. 「危険なシンク」を覚える: innerHTML, outerHTML, document.write(), eval(), setTimeout(string)。これらを見つけたら、「なぜこれが必要なんだ?」と一度立ち止まること。
2. DOMPurifyの導入: どうしてもHTMLをレンダリングする必要がある場合は、自作のフィルタリングなど考えず、[DOMPurify](https://github.com/cure53/dompurify) のような枯れたライブラリを使い、サニタイズ(無害化)を徹底すること。
3. WAFは「最後の砦」と心得る: WAFは重要だが、DOMベースXSSのようなクライアントサイド攻撃には無力な場合が多い。アプリケーション側のロジックで防御するのが、真のエンジニアの流儀だ。

最後に

セキュリティの戦いに「完全な正解」はない。しかし、脆弱性を一つずつ消し込み、堅牢な設計を積み重ねることはできる。今日話したDOMベースXSSの仕組みを理解すれば、君たちの書くJavaScriptは確実に一段上のレベルに達するはずだ。

コードをレビューする時、innerHTML を見つけたら、この記事を思い出してくれ。それが、システムを守るための第一歩だ。健闘を祈る。

コメント

タイトルとURLをコピーしました