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エコシステムの中で形を変え、ユーザーのブラウザを攻撃者の踏み台に変える「高度な論理脆弱性」だ。
我々に求められているのは、教科書的なエンコード処理の徹底だけではない。「どこで入力がパースされ、どの実行コンテキストで処理されるか」という実行フローの全貌を、設計段階から可視化し、制御することだ。防御とは、常に攻撃者の思考を先回りするアーキテクチャの構築に他ならない。
コメント