SVGは「ただの画像」ではない:悪魔が潜むベクター画像の脅威と防御策
こんにちは。現場で泥をすすりながらインシデント対応をしてきた身から言わせてもらえば、「画像アップロード機能」は、Webアプリにおける最大の鬼門の一つです。
多くのエンジニアは「拡張子が .jpg か .png かチェックしているから大丈夫」と胸をなでおろしますが、そこに .svg が混ざった瞬間、セキュリティの境界線は跡形もなく崩れ去ります。なぜなら、SVGは画像ではなく、「実行可能なXMLドキュメント」だからです。
今日は、SVGに潜むXSS(クロスサイトスクリプティング)の罠と、それを実務レベルで完封するための実装テクニックを共有します。
—
1. なぜSVGは危険なのか:攻撃のメカニズム
SVG(Scalable Vector Graphics)は、ブラウザが直接解釈して描画するXML形式の画像です。ここには、HTMLと同様にスクリプトを埋め込むための「遊び」が用意されています。
攻撃者が仕掛ける罠(PoC)
攻撃者は、一見無害なロゴやアイコンのSVGファイルの中に、以下のようなコードを忍ばせます。
もしこのファイルをユーザーがアップロードし、Webアプリがそれを タグや直接のリンクとして表示した場合、ブラウザはそのSVGをレンダリングするタイミングで onload イベントを発火させます。 つまり、閲覧者のブラウザ上で攻撃者のJavaScriptが実行されるわけです。これがSVGによるXSSの正体です。
—
2. 実務で使える防御策:3つの防波堤
単なる拡張子チェックは無意味です。以下の3層構造で守りを固めます。
防御策①:Content-Typeの強制とContent-Disposition
SVGをブラウザが「スクリプトを含むドキュメント」として解釈しないよう、ヘッダーで制御します。特に重要なのは Content-Disposition: attachment です。これにより、ブラウザはSVGを「開く」のではなく「ダウンロード」させるようになります。
Nginxの設定例:
SVGファイルへのリクエストに対するセキュリティヘッダー
location ~ \.svg$ {
# 実行を阻止するために強制的にダウンロードさせる
add_header Content-Disposition ‘attachment; filename=”image.svg”‘;
# ブラウザがMIMEタイプを勝手に推測するのを防ぐ
add_header X-Content-Type-Options ‘nosniff’;
}
防御策②:SVGの正規化とサニタイズ(PHP実装例)
アップロードされたSVGをそのまま保存するのは自殺行為です。DOMDocument を使用して、スクリプトに関連するタグや属性を徹底的に削除します。
/
function sanitizeSvg($filePath) {
$dom = new DOMDocument();
$dom->load($filePath);
// 危険なタグを削除
$badTags = [‘script’, ‘foreignObject’];
foreach ($badTags as $tag) {
$nodes = $dom->getElementsByTagName($tag);
foreach ($nodes as $node) {
$node->parentNode->removeChild($node);
}
}
// 危険な属性(on系イベントハンドラ)を削除
$xpath = new DOMXPath($dom);
$nodes = $xpath->query(‘//[@[starts-with(name(), “on”)]]’);
foreach ($nodes as $node) {
foreach ($node->attributes as $attr) {
if (strpos($attr->name, ‘on’) === 0) {
$node->removeAttribute($attr->name);
}
}
}
$dom->save($filePath);
}
防御策③:CSP(Content Security Policy)でトドメを刺す
万が一、サニタイズをすり抜けても、CSPで「インラインスクリプトの実行」を禁止していれば攻撃は不発に終わります。
HTTPヘッダーのCSP設定
Content-Security-Policy: default-src ‘self’; script-src ‘self’; object-src ‘none’;
object-src 'none' を設定することで、SVG内の やプラグインの実行を物理的に遮断します。
---
3. シニアエンジニアからの教訓
最後に、現場で必ず守ってほしいルールを3つ伝えます。
1. ユーザー入力を信じない: 画像であっても、それは「外部から持ち込まれたデータ」です。アップロード時に getimagesize() や finfo_file でMIMEタイプを確認し、SVGなら必ず上記のサニタイズを通してください。
2. ストレージを分離する: SVGをWebサーバーと同じドメインで提供せず、専用のクリーンなCDNや別ドメインでホストしてください。これにより、万が一XSSが発動しても、メインサイトのCookie(セッションID)にアクセスされるリスクを劇的に下げられます。
3. 「見せる」なら変換する: 可能であれば、アップロードされたSVGをサーバー側で一度ラスター画像(PNGなど)に変換してから保存する運用が最も安全です。
セキュリティは「魔法の杖」一つで解決するものではありません。今回紹介したヘッダー設定、サニタイズ、CSPという「多層防御」を組み合わせることで初めて、システムは堅牢になります。
明日からの開発で、ぜひこの防御ラインを実装してください。あなたの書くコードが、次のインシデントを防ぐ盾になることを期待しています。
コメント