SVGという「トロイの木馬」:アプリケーションの境界を無力化するXSSの深淵
多くのエンジニアが「画像アップロード機能」を安直に捉えている。MIMEタイプをチェックし、拡張子を制限すれば安全だという認識は、残念ながら数年前の遺物だ。特にSVG(Scalable Vector Graphics)は、画像という皮を被った「実行可能なXMLドキュメント」であり、現代のWebアプリケーションにおける最大の盲点の一つと言える。
本稿では、SVGがなぜXSSの温床となるのか、その低レイヤの挙動と、現場で通用する防衛アーキテクチャについて、チーフホワイトハッカーの視点から解剖する。
—
1. なぜSVGは「画像」であって「画像ではない」のか
SVGはXMLベースのベクタ画像フォーマットであり、ブラウザはこれを単なるピクセルデータの集合体ではなく、DOMの一部として解釈する。つまり、SVGファイルの中に記述されたスクリプトは、ブラウザの実行コンテキスト(Origin)でそのまま実行される。
攻撃者が仕掛けるのは、典型的には以下のようなコードだ。
さらに巧妙なケースでは、 のように、静的なタグの属性にイベントハンドラを埋め込む。これはDOM型XSSの変種であり、静的な解析ツール(SAST)をすり抜けることが多い。
—
2. 脆弱性の根本原因:暗黙の信頼とContent-Typeの罠
多くの開発現場では、Content-Type: image/svg+xml が付与されているから安全だ、あるいはアップロード時にライブラリでバリデーションしているから大丈夫だ、と高を括っている。しかし、ここには以下のプロトコル的な落とし穴がある。
1. MIME Sniffing: ブラウザが Content-Type を無視し、ファイルの中身を精査して勝手に実行形式と判断する挙動。X-Content-Type-Options: nosniff が設定されていない限り、攻撃は成立する。
2. DOMの汚染: アップロードされたSVGを タグの src 属性として読み込む場合、ブラウザは「画像」として処理するため、スクリプトは実行されない。しかし、 や 、あるいは直接URLをブラウザに打ち込んだ場合、それは「ドキュメント」として処理され、スクリプトが発火する。
—
3. 実践的防御アーキテクチャ:多層防御の構築
単一のバリデーションは必ず破られる。我々が構築すべきは、攻撃者が「やりたいこと」を物理的に不可能にするガードレイルだ。
A. CSP(Content Security Policy)による実行禁止
最も強力な防衛線は、CSPを用いてSVG内でのスクリプト実行を根本から遮断することだ。
HTTPレスポンスヘッダーの例
Content-Security-Policy: default-src ‘self’; script-src ‘none’; object-src ‘none’;
script-src 'none' を設定することで、万が一SVGにスクリプトが混入していても、ブラウザは実行を拒否する。
B. アップロード時の厳格なサニタイズ
単なる拡張子チェックは無意味だ。DOMParserを用いてXML構造を解析し、危険なタグや属性をホワイトリスト方式で排除する。
// Node.js環境でのサニタイズ例 (DOMPurify等の活用)
const createDOMPurify = require(‘dompurify’);
const { JSDOM } = require(‘jsdom’);
const window = new JSDOM(”).window;
const DOMPurify = createDOMPurify(window);
function sanitizeSvg(dirtySvgString) {
// スクリプトタグ、on系属性、外部リソース参照を徹底的に削除
return DOMPurify.sanitize(dirtySvgString, {
USE_PROFILES: { svg: true },
FORBID_TAGS: [‘script’, ‘foreignObject’], // 実行可能な要素を排除
FORBID_ATTR: [‘onload’, ‘onerror’, ‘onmouseover’] // イベントハンドラを排除
});
}
C. 配信時の戦略:サンドボックスドメインの分離
可能であれば、ユーザーがアップロードしたコンテンツはメインドメイン(example.com)とは別のドメイン(usercontent-example.com)で配信すべきだ。これにより、XSSが成功してもメインサイトのCookieやLocal Storageにはアクセスできない。
—
4. プロの視点:生成AI時代における新たな脅威
最近のトレンドとして、生成AIが作成したSVGコードに、攻撃者が意図的に脆弱性を忍び込ませる「プロンプトインジェクション」経由の攻撃が増加している。AIは「動く画像を作って」という指示に対し、悪意の有無に関わらず onload を埋め込んだSVGを生成することがある。
アーキテクトとして、我々は「人間が作ったコード」だけでなく、「AIが生成したコード」もまた、等しく信頼できない外部入力であるという前提に立つべきだ。
まとめ:セキュリティは「信頼の断絶」から始まる
SVGに限らず、セキュリティの本質は「入力値に対する過度な信頼をどこまで排除できるか」に集約される。
1. Content-Typeを強制せよ(nosniff)
2. スクリプト実行を遮断せよ(CSP script-src 'none')
3. コンテンツを孤立させよ(サンドボックスドメイン)
これら三位一体の防衛層を構築して初めて、エンジニアは「画像機能」を正当に実装したと言える。技術的な好奇心を攻撃ベクトルに向け、防衛側にその知見を還元し続けること。それこそが、我々セキュリティスペシャリストの矜持だ。
コメント