Web Platform Incubator Community Groupは先頃、HTML Sanitizer APIのDraft Community Group Reportを公開した。HTML Sanitizer APIは、信頼できないHTML文字列をサニタイズ(sanitize、消毒)して、ドキュメントDOMに安全に挿入可能なものにするものだ。HTML文字列のサニタイズの最も一般的なユースケースは、クロスサイトスクリプティング(XSS)攻撃を防止することだ。
APIのプロポーザルには、新たな提案の背景となる論拠が詳しく説明されている。
Webアプリケーションでは、クライアントサイドのテンプレートソリューションの一部として、あるいはユーザが生成したコンテンツのレンダリング処理の一環として、クライアントサイドにあるHTML文字列の処理が必要になることが多々あります。しかしながら、これを安全に行うのは困難です — 文字列を連結して
エレメント
のinnerHTML
に詰め込む、という単純なアプローチは、予期しないさまざまな方法でのJavaScript実行を可能にすることから、多くの危険性を伴います。[ユーザ空間のライブラリが]脆弱なアプローチであることは明白です[...]が、任意の文字列から安全にHTMLを描く方法をブラウザに教えて、それをブラウザ自体のパーザ実装の変更と合わせて維持し、更新していくことによって、ユーザ空間ライブラリを改善することは可能です。
APIの目標は3つある。DOMベースのクロスサイトスクリプティング攻撃に関するリスクを低減すること、HTML出力を現行のユーザエージェントによる理解範囲を考慮した安全なものにすること、エレメントやアトリビュートの既定セットをオーバーライドしてスクリプトガジェット攻撃を防止できるようにすることだ。
HTML Sanitiazer APIは3つのユースケースをカバーする。最初のユースケースは、DOMノードのツリーとして利用可能な入力データのある場合だ。この場合、開発者はツリーのコンテンツをサニタイズした上で、次のようにサニタイズされたDOMを組み込むことができる。
let s = new Sanitizer();
// Case: The input data is available as a tree of DOM nodes.
let userControlledTree = ...;
element.replaceChildren(s.sanitize(userControlledTree));
第2のユースケースは、文字列形式のHTMLコンテンツがあって、ターゲットとするDOMエレメントが既知である場合だ。この場合は、以下のようにSanitizer APIを使用することができる。
const user_string = "..."; // The user string.
const sanitizer = new Sanitizer( ... ); // Our Sanitizer;
// We want to insert the HTML in user_string into a target element with id
// target. That is, we want the equivalent of target.innerHTML = value, except
// without the XSS risks.
document.getElementById("target").setHTML(user_string, sanitizer);
第3のユースケースでは、文字列形式のHTMLコンテンツがあり、ターゲットとするDOMエレメントは分かっている(div
、span
など)が、サニタイズ直後に挿入できない、あるいはしたくない場合だ。この場合は、APIを次のように使用できる。
let s = new Sanitizer();
let forDiv = s.sanitizeFor("div", userControlledInput);
// Later:
document.querySelector(`${forDiv.localName}#target`).replaceChildren(forDiv.children);
HTMLコンテンツのサニタイズとは、意味的に有害な部分(スクリプト実行など)をHTML文字列から取り除くことだ。文字列をDOMノードに変換するには、HTML解析アルゴリズムで指定された方法によって解析する必要がある。文字列からDOMへの解析はコンテキスト依存である。すなわち、周囲のコンテキストによって、同じHTML文字列が異なるDOMノードに解析される場合があるのだ。
例えば"<em>bla
"という文字列は、<div>
および<textarea>
それぞれのコンテキストで次のように解析される。
<div><em>bla</div> <div><em>bla</em></div>
<textarea><em>bla</textarea> <textarea><em>bla</textarea>
"<td>text
"という文字列ならば、<table>
内とそれ以外(<div>
)では次のように解釈が異なる。
<table><td>text</table> <table><td>text</table>
<div><td>text</div> <div>text</div>
これらの例で示したような解析処理のコンテキスト依存性のため、ユーザが開発する文字列間HTMLサニタイズライブラリは、mXSSと呼ばれるXSS形式攻撃クラスに対して本質的に脆弱である。mXSS攻撃は、文字列間HTMLサニタイズAPIがサニタイズ実行時に、必要なコンテキスト情報にアクセスできない、という事実を突くものだ。現実のライブラリにおいては、2つのmXSS形式エクスプロイトの存在がこちらとこちらで確認されている。
Sanitizer APIはSanitizer API WICGでインキュベートされている。W3C標準(すなわちWeb標準)ではなく、W3C Standars Trackにも従っていない。現時点では、デフォルトで使用可能なブラウザは存在しないが、Chrome 93以降でc2>about://flags/#enable-experimental-web-platform-features flagを有効にすれば、早期実装をプレビューすることができる。