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

さようなら InnerHTML、こんにちは SetHTML: Firefox 148における強化されたXSS保護

概要

  • Cross-site scripting (XSS) は依然としてウェブで最も一般的な脆弱性の一つ
  • Sanitizer API は未信頼なHTMLを安全にDOMへ挿入するための標準API
  • Firefox 148 が最初にSanitizer APIを実装し、今後他ブラウザも対応予定
  • setHTML() メソッドで安全なHTML挿入が容易に
  • Trusted Types との併用で更なるセキュリティ向上が可能

XSS(クロスサイトスクリプティング)とその課題

  • XSS脆弱性 は、ユーザー生成コンテンツ経由で攻撃者が任意のHTMLやJavaScriptを注入できる状態
  • 攻撃者による ユーザー操作の監視・データ窃取 のリスク
  • CWE-79 で10年以上トップ3にランクインする深刻な問題
  • Content-Security-Policy (CSP) の標準化でMozillaがリード
    • サイトごとに読み込めるリソースを制限し、XSS対策の強力な手段
  • CSPの導入障壁 :既存サイトへの大幅なアーキテクチャ変更や専門家による継続的なレビューが必要

Sanitizer APIの登場とメリット

  • Sanitizer API は、悪意あるHTMLを無害化する標準的な方法を提供
  • setHTML()メソッド でHTML挿入時に自動でサニタイズ
  • 例:
    • document.body.setHTML('<h1>Hello my name is <img src="x" onclick="alert(\'XSS\')">');
    • <h1>Hello my name is</h1>だけが残り、危険な<img>onclick属性は除去
  • innerHTML の置き換えで安全性向上、コード変更も最小限
  • カスタム設定 も可能:用途に合わせて許可する要素や属性を指定

導入と実験方法

  • Sanitizer API Playground で事前に動作確認が可能
  • Trusted Types との連携でHTML解析・挿入の一元管理
    • setHTML()導入後、Trusted Typesの強制も容易
    • setHTML()のみ許可し、他の危険なHTML挿入方法をブロック可能

Firefox 148の対応と今後の展望

  • Firefox 148 はSanitizer APIおよびTrusted Typesの両方をサポート
  • 開発者は 専任セキュリティチームや大幅な実装変更なし でXSS対策が可能
  • 今後他ブラウザも標準対応 見込み、ウェブ全体のセキュリティ向上に貢献

著者紹介

  • Tom Schuster :複数のセキュリティ関連記事を執筆
  • Frederik Braun :Mozilla Firefoxのセキュリティ開発者
    • Sanitizer APIやSubresource Integrityなどの標準仕様に貢献
    • 趣味は小説読書やヨーロッパのサイクリング
  • Christoph Kerschbaumer :Security Engineeringマネージャー

参考リンク・クレジット

  • イラスト:Desi Ratna(Website)、Made by Made(Person)、Andy Horvath(Hacker)
  • Frederik Braun公式サイト:https://frederikbraun.de

Hackerたちの意見

こういうのはいつも緊張するんだよね。ユーザーからの入力を安全に処理できるメソッドと、脆弱性を引き起こす可能性があるメソッドが混ざっちゃうから、どれがどれか名前だけじゃ全然わからないし。理想的には、そういう危険な関数は名前から明確に危険だってわかるように設計するべきなんだけど、後からそれをやるのは簡単じゃないんだよね。それに、HTMLを「サニタイズ」するものにはちょっと懐疑的なんだ。穴がある歴史が長いし、そもそも「サニタイズ」って何を意味するのか、何が「安全」とされるのかもすぐにはわからないから。

要するに、innerHTMLとsetHTMLを混ぜないってこと。innerHTMLの使用を全部排除して、古い機能が必要なときは新しいsetHTMLUnsafeを使うって感じ。

デフォルト設定の「安全」についてのリンクもあるよ。 https://wicg.github.io/sanitizer-api/#built-in-safe-default-... でも、同意するけど、僕のデフォルトのアプローチは、信頼できないコンテンツがある場合はinnerTextだけを使うことが多いかな。だから、彼らのデモがこれだとしたら:container.SetHTML(Hello, {name}); 僕のはこうなる:let greetingHeader = container.CreateElement("h1"); greetingHeader.innerText = Hello, {name};

realSetSafeHTML()

「安全」という概念があいまいなのはその通りだけど、ここでの目標は特にXSSに対して安全であることなんだ。スクリプトが実行される可能性のある要素やプロパティは削除される。この機能はユーザーエージェントに組み込まれていて、安全でない要素がDOMに追加されるのを防ぐから、文字列から文字列へのサニタイザーよりも正しく処理しやすいはず。要素が現在DOMに追加されているかどうかを判断するのは、「このHTML文字列にスクリプトタグが含まれているかどうか」を判断するよりも基本的に簡単だからね。

ブラウザネイティブのサニタイザーAPIには、ライブラリアプローチにはない利点があるんだ。それは、ブラウザがレンダリングに使うのと同じHTMLパーサーを使っていること。DOMPurifyみたいなライブラリは、別のコンテキストでパースしてから再シリアライズするけど、歴史的にその往復がバイパスの大半を生んできたんだよね。サニタイザーとレンダラーが同じパーサーを共有していると、ミューテーションXSS攻撃は隠れる場所がなくなる。

ちょっとしたサニタイズでもないよりはマシ?ブラウザに任せてるなら、もうかなりヤバいことになってるよ。

参考までに、ページを「Content-Security-Policy: require-trusted-types-for 'script'」で提供すると、サニタイズしないメソッドに通常の文字列を渡すのをブロックできるよ。

理想を言えば、ウェブ開発者としてどこかに古いAPI(innerHTMLみたいな)を禁止するグローバルプロパティを設定できるべきだけど、その場合は「あなたのウェブサイトはXより古いブラウザでは動かない」という大きな注意事項がつくね。でも、もしかしたらそれに関するウェブ標準がすでにあるかも。古いブラウザの場合はバックアップコンテンツを用意する必要があるし。

名前からはどっちがどっちか全然わからないよね。setHTML と setHTMLUnsafe があるけど、これ以上わかりやすくするのは難しいかも。

こういうのが出てきて嬉しいけど、ブラウザのサポートが広がるまでには時間がかかりそうだね。 https://caniuse.com/mdn-api_element_sethtml

確かに、どんなブラウザAPIでも、数年後には使えるようになるかもしれないし、最近のバージョンに満足しているなら数ヶ月で使えるようになるかもね。その間はポリフィルがあるかもしれないし。

Hacker Newsで議論の続きを見る