ハクソク

世界を動かす技術を、日本語で。

SVGのサニタイズに関する悩み

概要

  • ScratchはSVG関連の脆弱性を長年抱えている
  • SVGのサニタイズ対策は毎年新たな問題が発覚し続けている
  • XSSやHTTPリークなど多様な攻撃手法が発見されてきた
  • 複雑化するサニタイズ処理でも完全な安全性は実現できていない
  • 今後も新たな脆弱性が発生し続ける可能性が高い

ScratchにおけるSVGサニタイズの歴史と課題

  • Scratchはユーザー生成SVGをDOMに挿入し、信頼性の高いバウンディングボックス計測などの目的で利用
  • SVGのDOM挿入はどんなに短時間でも根本的に危険な操作
  • サニタイズのために複雑なパーサやフィルタを増築するアプローチを採用
  • しかし根本的な解決には至らず、毎年新しい抜け道が発見される状況

2019年:scriptタグによるXSS

  • SVG内の**<script>タグ**でJavaScriptが実行され、XSSが発生
  • 攻撃者は他ユーザーの権限でコメント投稿・プロジェクト削除等が可能
  • Scratch DesktopではNode.js統合により任意コード実行にも発展
  • 正規表現でscriptタグ除去することで一時的に対策

2020年:正規表現の不備によるXSS(CVE-2020-7750)

  • **大文字<SCRIPT>**やイベントハンドラ属性(onerror等)で回避可能
  • DOMPurify導入でscript除去を強化

2022年:image要素のhrefによるHTTPリーク

  • <image>要素のhref属性で外部リクエスト発生
  • DOMPurifyは実行コードは除去するがHTTPリークは防げず
  • IPアドレス漏洩や位置情報推測が可能
  • DOMPurifyのフックで外部URLのhrefを除去することで対策

2023年:CSS @importによるHTTPリーク

  • <style>内の**@import**で外部リクエスト誘発
  • JavaScript製CSSパーサを導入し、@importを除去

2024年:Paper.js経由のXSS

  • サニタイズ前のSVGがPaper.js(コスチュームエディタ用ライブラリ)に渡されXSS
  • scratch-svg-rendererだけでなくPaper.js読み込み時にもサニタイズ適用
  • サーバー側でも何らかの保護があるとされるが詳細不明

2025年:CSS url()によるHTTPリーク

  • CSSの**url()**関数で外部リクエスト発生
  • style属性や<style>内のurl()を検出し除去する処理を追加

2026年:サニタイズコードのバグによるHTTPリーク

  • エスケープコードによるurl()の回避
  • style属性内で複数のurl()がある場合の不備
  • CSS変数(var(--name))経由のurl()参照
  • さらなる複雑なサニタイズ処理追加で対応

2026年:長大なCSSトランジションによる全ページ再スタイリング

  • SVG内の長いtransitionと全要素へのtransform適用で、ページ全体のスタイルを攻撃者が操作可能
  • reportボタン非表示、likeボタン巨大化、偽の案内表示などフィッシング等の悪用例
  • 現在も未修正

2026年:image-set()によるHTTPリーク

  • CSSの**image-set()**関数で外部リクエスト発生
  • url()以外の方法でHTTPリークが可能
  • 現時点で未修正

20XX年:新しいCSS仕様による将来的なHTTPリーク

  • CSS Units Level 4やCSS Images Level 4など新仕様の実装進展で、また新たな攻撃手法が登場する可能性

サニタイズの限界と今後の展望

  • SVGサニタイズの複雑化は進むが、イタチごっこの様相
  • SVG仕様・CSS仕様の拡張やブラウザ実装の変化により新たな抜け道が定期的に発生
  • 根本的な安全性確保は困難であり、SVGのDOM挿入自体を極力避ける設計が望ましい
  • Scratchに限らず、ユーザー生成SVGを扱う全てのサービスで同様のリスクが存在

Hackerたちの意見

ちなみに、Google スライドが SVG サポートを持っていない理由は、15 年近く前からその機能をリクエストするチケットがあるのにね。
ワークアラウンド: https://simonsocolow.com/tech/uploading-svg-to-google-slides...
彼らは完全にサニタイズできるはずなのに、ガジェットプロキシの時と同じように、古いリクエストを修正することを選ばないんだ。私のシートにデータURLサポートを追加するためのチケットみたいに、毎年先送りにされてる。
最初に思ったのは、「実際の使用ケースの90%をカバーするかもしれない小さなサブセットの SVG をサポートする」ってこと。SVGには「塗りつぶしのあるパスの集まり」と「ちょっと危険な賢いもの」の2種類があると思うけど、ほとんどの実際のSVGは前者だよね。これについて120秒以上考えた人に否定されるのを期待してるよ。 :)
ちなみに、同じことを考えたよ。好きな部分をパース(検証はしない)して、入力を再構築または拒否するって感じ。
これがブラウザレベルで新しいフォーマットとして実装されるのが一番いいんじゃないかな。そうじゃないと、「ユーザースペース」で扱うのはすごく遅くて面倒になると思う。
アニメーション付きの SVG は、サニタイズ後にアニメーションが全部消えちゃうことが多い気がする。
あなたの言う通りだと思うけど、こういうことに対する業界標準がないのが致命的だね。人々は、自分が使っているツールから出力された SVG をブラウザに入れたいと思っている。それは不当なリクエストじゃないよね。でも、ツールが何かの obscure SVG 機能を使っている場合、それがフィルタリングされない保証はない。OpenGL と OpenGL ES のような合意された標準が SVG にもあればいいな。SVG-ES。静的でスクリプトなしの要素についてはみんな合意していると思う。
誰かがもう君のアイデアを実装したみたいだよ。 https://tinyvg.tech/
> 最初に思ったのは「おそらく90%の実際の使用ケースをカバーする小さなサブセットのSVGをサポートすること」だね。リンク先の投稿は、ホワイトリストの代わりにブラックリストを使っている人についてだったみたい。認識できないものを通すなら、サブセットがどんなに小さくても関係ないよ。ほとんどのSVGは安全だし、危険な部分はかなり明白だよ。スクリプトタグ、イメージタグ、feImageタグ、onで始まる属性、HTMLを埋め込むこと、DTDトリック、名前空間トリック、外部のものを読み込むCSS(プレゼンテーション属性も考慮してね。スタイル属性/タグだけじゃないから)。残りはかなり安全だよ。
BIMI/VMCのように、一部の実装で使われているSVG Tinyプロファイルがあるよ。
大人がいないとこうなるんだよね、プロジェクトの過剰拡張が起きる。SVGはスクリプティングをサポートするべきじゃなかった。SVGでスクリプティングが必要なら、別のファイルフォーマットにすればいい。ほとんどのユーザーがロゴをシャープに見せる方法を探していただけなのに、どれだけの時間が無駄になったか想像もつかないよ。
適切なサニタイザーは、/https?:/が含まれる属性をすべて削除すべきだと思う。多分、あなたが管理する信頼できるドメインのサブツリーへのアクセスを許可して、テクスチャなどを保存する場所を作るべきだね。
これがAndroidのやり方だね。独自のベクターアセットフォーマットがあって、Android StudioにはSVGをインポートするためのアクションがある。
ページにインラインで入っている理由は、バウンディングボックスみたいなものを簡単に測るためみたいだね(詳しいことはわからないけど、その部分はカバーしてなかった)。Scratch とそのユーザー提出の SVG の使い方には詳しくないけど、なぜインラインである必要があったのか、もっと知りたいな。 (これは適切なサニタイズの課題についてのコメントではないけど、同じようなことを自分でもやったことがあるからね)
彼らはgetBBox [1]を実行したいんだけど、SVGがDOMのどこかに存在していないとエラーが出るんだ。これをやらなきゃいけないのは、SVGが特に古いバージョンのScratchで作られた場合、非常に不正確なビューボックスを持っていることが多いから。getBBoxはSVGの中のものがどれくらい大きいかをより正確に理解するための一番簡単な方法なんだ。[1]: https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphics...
tinyVGみたいなのがもっと広まってほしいけど、実際にそうなるとは思えないな。欠けてるのはアニメーションのサポートだと思うけど、これは結構ニッチだね。でも、タグよりはマシかな。 https://tinyvg.tech/
インラインタグに追加できるサンドボックス属性があればいいのに。SVG内のスクリプトやイベントハンドラみたいな「動的」なものを全部オプトアウトできる属性とか、iframeみたいに「親」HTMLページのコンテキストやクッキーにアクセスできないようにするためのもの。これが実装されると、別の問題が出てきそうだけど…ブラウザが実際にサポートするのを待たなきゃいけないしね。ここでの本当の解決策は、CSPと基本的なサニタイズだと思う。
作者が批判してた点のほとんどは、実際には普通のCSS機能なんだよね。外部リクエストを一切受け入れたくないだけ。要するに、インラインSVGをブラウザがIMG埋め込みSVGを扱うのと同じように扱ってほしいってこと(スクリプトや外部リクエストを読み込まない)。サニタイズに関しては、SVGからスクリプトを取り除くのはすでに可能だし、他に何をしたいかもできる。ただ、DOMPurifyみたいなライブラリはサイズが膨らまないように、ブラウザがIMG埋め込みを扱うのと同じように振る舞うために必要な追加のパースを処理するプリセットを含んでないから、開発者が自分で追加する必要があるんだ。でも、インラインSVGに対してIMG埋め込みと同じ効果を得るためのシンプルな属性があればいいよね。
幸いなことに、CSPがあれば基本的なサニタイズすら必要ないんだ。これって便利だよね。この記事のほとんどの問題は、シンプルなサニタイズが全然シンプルじゃないことのデモンストレーションだから。
それがどう機能するのか分からないな。HTMLのタグはドキュメントの境界じゃないから、別のドキュメントじゃないのに親ドキュメントにアクセスするのをどうやって防ぐの?これをやりたいならiframeのsrcdocを使えばいいよ。
img src="file.svg" って、それでうまくいく?
このアーティクルがHTTPリーク問題の唯一の信頼できる解決策、CSPを含んでるのは嬉しいね。最近学んだことなんだけど、CSPヘッダーは通常HTTPヘッダーを使って設定されるけど、HTML内でも直接設定できるんだ。例えば、HTTPヘッダーが関与しないページで直接生成されたHTMLの場合: "> これがうまくいかない気がするのは、信頼できないコンテンツ内のJavaScriptがDOMを使ってそのmetaタグを削除したり変更したりできるからなんだけど、実際にはすべてのモダンブラウザがそれをロックダウンしていて、そのmetaタグが読み込まれたら悪意のあるコードがそれを妨害する前にCSPルールを永久的なものとして扱うんだ。数週間前にClaude Codeに実験をしてもらったんだけど、これを示すのに役立ったよ: https://github.com/simonw/research/tree/main/test-csp-iframe...
`srcdoc`については知らなかったけど、ダブルクォートを使ってサンドボックスを逃れることでインジェクションの脆弱性があるみたいだね。DOM操作を使って衛生的に構築すればうまくいくかもしれないけど、確実に失敗する可能性もあるね。
いいね、お気に入りにした…メールリーダーがCSSをサポートするのに役立ちそうだけど、スクリプトはなしで。
追加のCSPディレクティブは、許可されるものを狭めるだけだよ。ヘッダーと一緒に使うと、-sでCSPをさらに制限できるけど、広げることはできない。
俺が考えてるアイデア(このケースにはあまり適用できないと思うけど)は、ユーザーコンテンツに対してSec-Fetch-ヘッダーを厳しく制限すること。もしサーバーが信頼できないSVGを提供する気があるなら、Sec-Fetch-Destが正しい値でない限り、全く提供を拒否することができる。‘document’や‘iframe’は正しい値じゃないからね。これによって、例えばSVGファイルへのリンクを貼ったり、埋め込みのようなあまり安全でないメカニズムを使って読み込ませたりすることで、ユーザーやブラウザを騙すのが難しくなるはず。ユーザーコンテンツに対するCSPを厳しく制限することも合わせてやるべきだね。(うーん、すべての画像はCSPヘッダーを設定して提供されるべきだよね。)
HTMLサニタイザーAPIには、デフォルト設定で許可されているSVGのサブセットがあります。でも、CSSのサニタイズには全く役に立たないよ。スタイルはデフォルトでは単純に許可されてないからね。 https://developer.mozilla.org/en-US/docs/Web/API/HTML_Saniti... https://developer.mozilla.org/en-US/docs/Web/API/HTML_Saniti...
いい参考だね、記事と一緒に。私はGoでSVGサニタイザーを作ったから、これを参考にしてもっと厳しくしようと思う。
ごめん、Scratchプロジェクトが大好きなんだけど、これだけは言わせてほしい。SVGの中にXSSが見つかったって、攻撃者がNodeにアクセスできる状態で、修正方法が正規表現でのサニタイズって??? しかも、これをScratchのユーザーが発見したの?さらに悪いことに、OPの最新の投稿「Scratchの全バージョンは任意のコード実行に脆弱です」では、現在のバージョンで似たようなものをどうやって悪用するかが具体的に書いてあって、責任ある開示については一切触れずに「ねぇ、私のプロジェクト見て!これにはRCEがないよ!」って宣伝してるだけ。これは無責任すぎて、悪意があるとさえ言える。
その投稿には、2024年2月にこれをScratchに開示したって書いてある。その投稿のPOCは、当時提供したPOCと機能的に同じだし、その後のやり取りでも同様だった。
Cloudflareもこの道を少し進んだね: https://github.com/cloudflare/svg-hush
SVGがCSSやJSを使って、表示するのにブラウザ全体を引っ張ってくるのが嫌なんだよね。単純なベクター画像フォーマットじゃなくて、HTMLの拡張みたいになっちゃってる。新しいフォーマットが必要かも。もし誰かが作るなら、フォントの埋め込みやテキストのラップ、ちゃんとしたアニメーションも追加してほしいな。