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

XSSの「本質」を解剖する:静的解析では見抜けないDOM汚染の深淵

Webセキュリティの世界において、XSS(Cross-Site Scripting)は「枯れた脆弱性」として軽視されがちだ。しかし、現代のSPA(Single Page Application)やマイクロフロントエンドアーキテクチャにおいて、XSSは単なるアラート表示のオモチャではない。それは、セッションハイジャック、権限昇格、さらにはブラウザを基点とした組織内ネットワークへのプロキシ攻撃へと進化する、極めて凶悪なエントリーポイントだ。

本稿では、反射型・蓄積型の古典的モデルを短く整理し、真の脅威である「DOMベースXSS」の実行フローを低レイヤの視点から紐解く。

—

1. XSSの三類型:コンテキストの解釈における「死角」

XSSの本質は「データとコードの境界の消失」にある。

  • 反射型 (Reflected XSS): HTTPリクエストに含まれるパラメータが、サーバーサイドのロジックを介して即座にレスポンスへ埋め込まれる。これは「サーバーが媒介する反射」であり、WAFのシグネチャベース検知にかかりやすい。
  • 蓄積型 (Stored XSS): データベースに悪意あるスクリプトが永続化される。ユーザーが閲覧するたびに実行されるため、インパクトは最大級だ。
  • DOMベースXSS: ここが今日の主戦場だ。サーバーは一切関与せず、ブラウザ内のJavaScriptが「信頼できない入力(Source)」を「実行可能なシンク(Sink)」へと受け渡す過程で発生する。

反射型や蓄積型が「サーバー側のエンコード不備」を突くのに対し、DOMベースはブラウザのJSエンジンの実行コンテキストにおける論理の欠陥を突く。

—

2. DOMベースXSSのメカニズム:Sinkへの到達経路

DOMベースXSSを理解するには、フロントエンドコードを「データフローのグラフ」として捉える必要がある。攻撃者は、以下の「Sink(実行される関数)」の挙動を熟知している。

代表的な危ないSink(実行口)

  • eval(), setTimeout(), setInterval() (文字列をコードとして解釈)
  • element.innerHTML, document.write() (HTML構造を動的に生成)
  • location.href, location.replace() (URIによるリダイレクト)

特に、モダンなフレームワーク(React, Vue.jsなど)を利用していても、dangerouslySetInnerHTML や v-html を不用意に使えば、防御機構は無力化する。

コード例:脆弱なデータフロー

// ソース:URLハッシュ(#以降)から値を取得
const source = decodeURIComponent(window.location.hash.substring(1));

// シンク:危険なメソッドへ直接渡す
// 攻撃者が # と入力するとJSが実行される
document.getElementById(‘profile-name’).innerHTML = source;

このコードの根本的な問題は、「フロントエンドのロジックが、外部からの入力を一度もサニタイズせず、HTML解析器(Parser)の文脈に放り込んでいる」点にある。

—

3. 次世代の防御層:DOMPurifyとCSPの「二重鍵」

現代のアーキテクトが取るべき防衛策は、単なるバリデーションではない。「実行コンテキストの厳格な分離」である。

A. DOMPurifyによる無害化

フロントエンドで動的にHTMLを挿入する際は、必ずDOMPurifyを利用せよ。これはHTMLのパースツリーを走査し、ホワイトリスト外の属性(onmouseoverやjavascript:スキーム)を物理的に削ぎ落とす。

import DOMPurify from ‘dompurify’;

// 信頼できない入力をクリーンなDOMノードに変換する
const cleanHTML = DOMPurify.sanitize(userInput);
element.innerHTML = cleanHTML;

B. CSP(Content Security Policy)によるガードレイル

インラインスクリプトの実行を禁止し、信頼できるソースからのスクリプトのみを許可する。CSPはXSSの「実行」を物理的に遮断する最後の砦だ。

HTTPレスポンスヘッダーの例
Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted-cdn.com; object-src ‘none’;

特に script-src 'unsafe-inline' を許可してはならない。これがあると、XSS脆弱性を見つけた攻撃者は瞬時にコードを実行できる。

—

4. チーフホワイトハッカーの視点:アーキテクチャの監査

私がインフラやアプリケーションを監査する際、必ず確認するのが「データフローのトレース」だ。特に以下の観点は、CVEを未然に防ぐためのチェックリストとして活用してほしい。

1. フレームワークの抽象化レベル: innerHTML を使っていないか? 仮想DOM(Virtual DOM)の恩恵を自ら捨てていないかを確認する。
2. API通信とJSON: APIレスポンスを eval() や new Function() で処理していないか。JSONのパースには常に JSON.parse() を強制する。
3. プロンプトインジェクションの対比: LLMを利用するアプリケーションでは、フロントエンドのXSSが「AIの出力」を介して間接的に発生するリスクがある。AIが生成したコードをそのままブラウザで実行させるのは自殺行為だ。

結論

XSSはもはや「古い脆弱性」ではない。それは、複雑化するJSエコシステムの中で形を変え、ユーザーのブラウザを攻撃者の踏み台に変える「高度な論理脆弱性」だ。

我々に求められているのは、教科書的なエンコード処理の徹底だけではない。「どこで入力がパースされ、どの実行コンテキストで処理されるか」という実行フローの全貌を、設計段階から可視化し、制御することだ。防御とは、常に攻撃者の思考を先回りするアーキテクチャの構築に他ならない。

コメント

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