1. 導入
この節は規範ではありません。
Web アプリケーションはクライアント側で HTML 文字列を扱う必要がしばしばあります。
これはクライアントサイドのテンプレート ソリューションの一部であったり、ユーザー生成コンテンツのレンダリングの一部であったりします。これを安全に行うのは難しいです。
文字列を安易に連結して
Element
の
innerHTML
に流し込む素朴な手法は、多くの予期しない形で JavaScript の実行を引き起こし得るため、危険が伴います。
[DOMPURIFY] のようなライブラリは、 挿入前に文字列を注意深く解析・サニタイズし、DOM を構築して許可リストを通してその構成要素をフィルタリングすることで、この問題に対処しようとします。 しかしこれは脆いアプローチであることが分かっています。というのも、Web に公開されている解析 API は、実際に 文字列を「実際の」DOM としてレンダリングする際のブラウザーの挙動と、必ずしも合理的に一致しないからです。 さらに、ライブラリは時間とともに変化するブラウザーの挙動を追い続ける必要があります。かつて安全だったものが、 プラットフォームレベルの新機能によって時限爆弾になってしまうこともあります。
ブラウザーは、自身がいつコードを実行するかについてかなり把握しています。任意の文字列から HTML を安全にレンダリングする方法を ブラウザー自身に教えることで、ユーザー空間のライブラリよりも改善できます。そうすれば、ブラウザーのパーサー実装の変更に合わせて 保守・更新される可能性もはるかに高くなります。本書は、そのための API を概説します。
1.1. 目標
-
ユーザー制御の HTML を安全に扱うメカニズムを開発者に提供し、注入時にスクリプトが直接実行されることを防ぐことで、 DOM ベースのクロスサイトスクリプティング攻撃のリスクを軽減する。
-
現在のユーザーエージェントの HTML 理解を考慮に入れ、その環境で安全に使用できる HTML 出力を実現する。
-
開発者が既定の要素および属性のセットを上書きできるようにする。 特定の要素や属性を追加することで、 スクリプト・ガジェット 攻撃を防げる場合がある。
1.2. API 概要
Sanitizer API は、HTML を含む文字列を DOM ツリーに解析し、結果のツリーをユーザー指定の構成に従って フィルタリングする機能を提供します。メソッドは 2 つの観点で用意されています:
-
Safe and unsafe: 「safe」なメソッドは、スクリプトを実行するマークアップを生成しません。つまり、XSS に対して安全であるはずです。 「unsafe」なメソッドは、与えられたものを解析しフィルタリングするだけです。 参照: § 4 セキュリティに関する考慮事項。
-
コンテキスト: メソッドは
ElementおよびShadowRootに定義され、これらのNodeの子を置き換えます。これは概ねinnerHTMLに相当します。さらにDocument上の静的メソッドもあり、 文書全体を解析し、概ねDOMParser.parseFromString()に相当します。
2. フレームワーク
2.1. サニタイザー API
Element
インターフェイスは、setHTML()
と
setHTMLUnsafe()
の 2 つのメソッドを定義します。いずれも HTML マークアップを含む DOMString
と、任意の構成を受け取ります。
partial interface Element { [CEReactions ]undefined setHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); [options CEReactions ]undefined setHTML (DOMString ,html optional SetHTMLOptions = {}); };options
Element
の
setHTMLUnsafe(html, options)
メソッドの手順は次のとおりです:
-
compliantHTML を、get trusted type compliant string アルゴリズムに
TrustedHTML、 this の 関連グローバルオブジェクト、html、"Element setHTMLUnsafe"、および "script" を与えて呼び出した結果とする。 -
target を、this が
template要素である場合はその template contents、そうでない場合は this とする。 -
Set and filter HTML を、target、this、 compliantHTML、options、false を与えて実行する。
Element
の
setHTML(html, options)
メソッドの手順は次のとおりです:
-
target を、this が
templateの場合はその template contents、 そうでない場合は this とする。 -
Set and filter HTML を、target、this、html、 options、true を与えて実行する。
partial interface ShadowRoot { [CEReactions ]undefined setHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {}); [options CEReactions ]undefined setHTML (DOMString ,html optional SetHTMLOptions = {}); };options
これらのメソッドは ShadowRoot
にも反映されています:
ShadowRoot
の
setHTMLUnsafe(html, options)
メソッドの手順は次のとおりです:
-
compliantHTML を、get trusted type compliant string アルゴリズムに、
TrustedHTML、 this の 関連グローバルオブジェクト、html、"ShadowRoot setHTMLUnsafe"、および "script" を与えて呼び出した結果とする。 -
Set and filter HTML を this、 this の shadow host(コンテキスト要素として)、 compliantHTML、options、false を与えて実行する。
ShadowRoot
の
setHTML(html, options)
メソッドの手順は次のとおりです:
-
Set and filter HTML を、this(target として)、this(コンテキスト 要素として)、 html、options、true を与えて実行する。
Document
インターフェイスは、文書全体を解析する 2 つの新しいメソッドを得ます:
partial interface Document {static Document parseHTMLUnsafe ((TrustedHTML or DOMString ),html optional SetHTMLUnsafeOptions = {});options static Document parseHTML (DOMString ,html optional SetHTMLOptions = {}); };options
parseHTMLUnsafe(html, options)
メソッドの手順は次のとおりです:
-
compliantHTML を、get trusted type compliant string アルゴリズムに、
TrustedHTML、 現在のグローバルオブジェクト、html、"Document parseHTMLUnsafe"、および "script" を与えて呼び出した結果とする。 -
document を新しい
Documentとし、 その content type は "text/html" とする。注: document には browsing context がないため、 スクリプティングは無効です。
-
document の allow declarative shadow roots を true に設定する。
-
Parse HTML from a string を document と compliantHTML を与えて実行する。
-
sanitizer を、get a sanitizer instance from options を options と false を与えて呼び出した結果とする。
-
sanitize を document に対して sanitizer と false を与えて呼び出す。
-
document を返す。
parseHTML(html, options)
メソッドの手順は次のとおりです:
-
document を新しい
Documentとし、 その content type は "text/html" とする。注: document には browsing context がないため、 スクリプティングは無効です。
-
document の allow declarative shadow roots を true に設定する。
-
Parse HTML from a string を document と html を与えて実行する。
-
sanitizer を、get a sanitizer instance from options を options と true を与えて呼び出した結果とする。
-
sanitize を document に対して sanitizer と true を与えて呼び出す。
-
document を返す。
2.2. SetHTML のオプションと構成オブジェクト。
setHTML()
系の
メソッドはすべて options ディクショナリを受け取ります。現時点では、このディクショナリのメンバーは 1 つだけ定義されています:
enum {SanitizerPresets };"default" dictionary { (SetHTMLOptions Sanitizer or SanitizerConfig or SanitizerPresets )= "default"; };sanitizer dictionary { (SetHTMLUnsafeOptions Sanitizer or SanitizerConfig or SanitizerPresets )= {}; };sanitizer
Sanitizer
構成オブジェクトは、フィルター構成をカプセル化します。
同じ構成は "safe" または "unsafe"
の両方のメソッドで使用でき、「safe」メソッドは渡された構成に対して暗黙に
removeUnsafe
を行い、構成が渡されない場合は既定の構成を用います。既定は「safe」と「unsafe」で異なります:
「safe」メソッドは既定で安全であることを目指し制限的で、「unsafe」メソッドは既定で無制限です。
構成の利用意図としては、ページのライフタイムの早い段階で 1 つ(または少数)の構成を作成し、必要に応じて再利用することです。これにより実装は構成を前処理できます。
構成オブジェクトは、構成ディクショナリを返すようにクエリできます。また、直接変更することもできます。
[Exposed =Window ]interface {Sanitizer constructor (optional (SanitizerConfig or SanitizerPresets )= "default"); // Query configuration:configuration SanitizerConfig get (); // Modify a Sanitizer’s lists and fields:boolean allowElement (SanitizerElementWithAttributes );element boolean removeElement (SanitizerElement );element boolean replaceElementWithChildren (SanitizerElement );element boolean allowAttribute (SanitizerAttribute );attribute boolean removeAttribute (SanitizerAttribute );attribute boolean setComments (boolean );allow boolean setDataAttributes (boolean ); // Remove markup that executes script.allow boolean removeUnsafe (); };
Sanitizer
には対応する SanitizerConfig
の
configuration が関連付けられています。
constructor(configuration)
メソッドの手順は次のとおりです:
-
configuration が
SanitizerPresetsの string である場合:-
configuration を built-in safe default configuration に設定する。
-
valid を、set a configuration を configuration と true を与えて this に対して実行した戻り値とする。
-
valid が false の場合、
TypeErrorを投げる。
get() メソッドの手順は次のとおりです:
-
config を this の configuration とする。
-
もし config["
elements"] が 存在するなら:-
任意の config["
elements"] の element について:-
もし element["
attributes"] が 存在するなら:-
element["
attributes"] を、昇順に並べ替える結果に設定する。 このとき attrA は より小さい項目 attrB とする。
-
-
もし element["
removeAttributes"] が 存在するなら:-
element["
removeAttributes"] を、昇順に並べ替える結果に設定する。 このとき attrA は より小さい項目 attrB とする。
-
-
-
config["
elements"] を、昇順に並べ替える結果に設定する。 このとき config["elements"] に対し、 elementA は より小さい項目 elementB とする。
-
-
さもなくば:
-
config["
removeElements"] を、昇順に並べ替える結果に設定する。 このとき config["removeElements"] に対し、 elementA は より小さい項目 elementB とする。
-
-
もし config["
replaceWithChildrenElements"] が 存在するなら:-
config["
replaceWithChildrenElements"] を、昇順に並べ替える結果に設定する。 このとき config["replaceWithChildrenElements"] に対し、 elementA は より小さい項目 elementB とする。
-
-
もし config["
attributes"] が 存在するなら:-
config["
attributes"] を、昇順に並べ替える結果に設定する。 このとき config["attributes"] に対し、 attrA は より小さい項目 attrB とする。
-
-
さもなくば:
-
config["
removeAttributes"] を、昇順に並べ替える結果に設定する。 このとき config["removeAttributes"] に対し、 attrA は より小さい項目 attrB とする。
-
-
config を返す。
allowElement(element) メソッドの手順は次のとおりです:
-
グローバルな許可リストか除去リストか、そして
-
それらのリストにすでに element が含まれているかどうか。
-
configuration を this の configuration とする。
-
element を、属性付き Sanitizer 要素の正規化の 結果に設定する(引数は element)。
-
もし configuration["
elements"] が 存在するなら:-
modified を、削除 の結果に設定する。 対象は configuration["
replaceWithChildrenElements"] からの element である。 -
コメント: 要素ごとの属性が グローバル属性と重複しないようにする必要がある。
-
もし configuration["
attributes"] が 存在するなら:-
もし element["
attributes"] が 存在するなら:-
element["
attributes"] を、重複を除去 した結果に設定する。 -
element["
attributes"] を、 差集合の結果に設定する。 対象は element["attributes"] から configuration["attributes"] を引いたものである。 -
もし configuration["
dataAttributes"] が true なら:-
削除: element["
attributes"] から、カスタムデータ属性 である すべての item を取り除く。
-
-
-
もし element["
removeAttributes"] が 存在するなら:-
element["
removeAttributes"] を、重複を除去 した結果に設定する。 -
element["
removeAttributes"] を、 積集合の結果に設定する。 対象は element["removeAttributes"] と configuration["attributes"] である。
-
-
-
さもなくば:
-
もし element["
attributes"] が 存在するなら:-
element["
attributes"] を、重複を除去 した結果に設定する。 -
element["
attributes"] を、 差集合の結果に設定する。 対象は element["attributes"] から element["removeAttributes"] を 既定値付き « » として引いたものである。 -
削除: element["
removeAttributes"] を取り除く。 -
element["
attributes"] を、 差集合の結果に設定する。 対象は element["attributes"] から configuration["removeAttributes"] を引いたものである。
-
-
もし element["
removeAttributes"] が 存在するなら:-
element["
removeAttributes"] を、重複を除去 した結果に設定する。 -
element["
removeAttributes"] を、 差集合の結果に設定する。 対象は element["removeAttributes"] から configuration["removeAttributes"] を引いたものである。
-
-
-
コメント: これは グローバル許可リストにすでに element が含まれているケースである。
-
current element を、 configuration["
elements"] 内の item とする。ただし item[name] が 等しい element[name] であり、かつ item[namespace] が 等しい element[namespace] であるもの。 -
もし element が 等しい current element ならば、 modified を返す。
-
true を返す。
-
-
さもなくば:
-
もし element["
attributes"] が 存在する または element["removeAttributes"] が 既定値付き « » で 空 ではないなら:-
ユーザーエージェントは、この操作がサポートされていないことを コンソールに警告として報告 してもよい。
-
false を返す。
-
-
modified を、削除 の結果に設定する。 対象は configuration["
replaceWithChildrenElements"] からの element である。 -
もし configuration["
removeElements"] が 含まない element なら:-
コメント: これは グローバル除去リストに element が含まれていないケースである。
-
modified を返す。
-
-
コメント: これは グローバル除去リストに element が含まれているケースである。
-
削除: configuration["
removeElements"] から element を取り除く。 -
true を返す。
-
replaceElementWithChildren(element)
メソッドの手順は次のとおりです:
-
configuration を this の configuration とする。
-
element を、Sanitizer 要素の正規化 の結果に設定する(引数は element)。
-
もし configuration["
replaceWithChildrenElements"] が 含む element なら:-
false を返す。
-
-
削除: configuration["
removeElements"] から element を取り除く。 -
追加: configuration["
replaceWithChildrenElements"] に element を追加する。 -
true を返す。
allowAttribute(attribute) メソッドの手順は
次のとおりです:
-
configuration を this の configuration とする。
-
attribute を、Sanitizer 属性の正規化 の結果に設定する(引数は attribute)。
-
もし configuration["
attributes"] が 存在するなら:-
コメント: グローバル許可リストがある場合、 attribute を追加する必要がある。
-
もし configuration["
dataAttributes"] が true で、かつ attribute が カスタムデータ属性 であるなら、false を返す。 -
もし configuration["
attributes"] が 含む attribute なら、 false を返す。 -
コメント: 要素ごとの許可/除去リストを 調整する。
-
もし configuration["
elements"] が 存在するなら:-
各 element について( configuration["
elements"] 内):-
もし element["
attributes"] が 既定値付き « » で 含む attribute なら:-
削除: element["
attributes"] から attribute を取り除く。
-
-
アサート: element["
removeAttributes"] は 既定値付き « » において 含まない attribute。
-
-
-
追加: configuration["
attributes"] に attribute を追加する。 -
true を返す。
-
-
さもなくば:
-
コメント: グローバル除去リストがある場合、 attribute を除去する必要がある。
-
もし configuration["
removeAttributes"] が 含まない attribute なら:-
false を返す。
-
-
削除: configuration["
removeAttributes"] から attribute を取り除く。 -
true を返す。
-
setComments(allow) メソッドの手順は次のとおりです:
setDataAttributes(allow) メソッドの手順は
次のとおりです:
-
configuration を this の configuration とする。
-
もし configuration["
attributes"] が 存在しない なら、false を返す。 -
もし configuration["
dataAttributes"] が allow と等しいなら、false を返す。 -
もし allow が true なら:
-
削除: configuration["
attributes"] から、 カスタムデータ属性 である任意の attr を取り除く。 -
もし configuration["
elements"] が 存在するなら:-
各 element について( configuration["
elements"] 内):-
もし element[
attributes] が 存在するなら:-
削除: element[
attributes] から、 カスタムデータ属性 である任意の attr を取り除く。
-
-
-
-
-
configuration["
dataAttributes"] を allow に設定する。 -
true を返す。
removeUnsafe() メソッドの手順は、
this の
configuration を、危険なものを除去 を
this の
configuration に対して呼び出した結果で更新することである。
2.3. 構成ディクショナリ
dictionary {SanitizerElementNamespace required DOMString ;name DOMString ?= "http://www.w3.org/1999/xhtml"; }; // Used by "elements"_namespace dictionary :SanitizerElementNamespaceWithAttributes SanitizerElementNamespace {sequence <SanitizerAttribute >;attributes sequence <SanitizerAttribute >; };removeAttributes typedef (DOMString or SanitizerElementNamespace );SanitizerElement typedef (DOMString or SanitizerElementNamespaceWithAttributes );SanitizerElementWithAttributes dictionary {SanitizerAttributeNamespace required DOMString ;name DOMString ?=_namespace null ; };typedef (DOMString or SanitizerAttributeNamespace );SanitizerAttribute dictionary {SanitizerConfig sequence <SanitizerElementWithAttributes >;elements sequence <SanitizerElement >;removeElements sequence <SanitizerElement >;replaceWithChildrenElements sequence <SanitizerAttribute >;attributes sequence <SanitizerAttribute >;removeAttributes boolean ;comments boolean ; };dataAttributes
2.4. 構成の不変条件
構成(configuration)は、開発者が自分の目的に合わせて修正することができ、また修正すべきものです。選択肢としては、新しく構成ディクショナリを一から書く、既存のSanitizerの構成を
修正用メソッドで変更する、あるいは既存のget()で
Sanitizerの構成を辞書として取得し、その辞書を編集して新しいSanitizerを作る、などがあります。
空の構成は(setHTMLUnsafe
のような "unsafe" メソッドで呼び出した場合に)すべてを許可します。
"default" 構成は組み込みの安全な既定構成を含みます。なお、「safe」と「unsafe」のサニタイザーメソッドは既定値が異なります。
すべての構成ディクショナリが有効とは限りません。有効な構成は、冗長(同じ要素を二重に許可するなど)や矛盾(同じ要素を削除と許可の両方に指定するなど)を避けます。
構成が有効であるためには、いくつかの条件を満たす必要があります:
-
グローバルの許可リストと除去リストの混在:
-
elementsまたはremoveElementsのいずれかは存在してよいが、 両方同時には不可。 どちらも存在しない場合、これはremoveElementsを « » に set したのと同等である。 -
attributesまたはremoveAttributesのいずれかは存在してよいが、 両方同時には不可。 どちらも存在しない場合、これはremoveAttributesを « » に set したのと同等である。 -
dataAttributesは概念的にはattributes許可リストの拡張である。dataAttributes属性は、attributesリストが用いられている場合にのみ許可される。
-
-
異なるグローバルリスト間の重複エントリ:
-
elements、removeElements、 およびreplaceWithChildrenElementsの間に重複エントリ(同一の要素)は存在しない。 -
attributesとremoveAttributesの間に重複エントリ(同一の属性)は存在しない。
-
-
同一要素上でのローカル許可/除去リストの混在:
-
attributesリストが存在する場合、同一要素上でattributesおよびremoveAttributesの各リストは、両方・いずれか一方・いずれもなしのいずれでも許可される。 -
removeAttributesリストが存在する場合、同一要素上でattributesおよびremoveAttributesの各リストは、いずれか一方またはいずれもなしは許可されるが、 両方同時は不可。
-
-
同一要素内での重複エントリ:
-
同一要素において、
attributesとremoveAttributesの間に重複エントリは存在しない。
-
elements
の要素許可リストは、特定の要素に対して、属性を許可または除去することも指定できる。これは [HTML] の構造(特定の要素に適用されるローカルな attributes と、
global attributes の両方を持つ)を反映することを意図している。グローバル属性とローカル属性は混在可能だが、
ある属性が一方のリストで許可され、他方のリストで禁止されるような曖昧な構成は、一般に無効であることに注意。
グローバル attributes
| グローバル removeAttributes
| |
|---|---|---|
ローカル attributes
| 属性は、いずれかのリストに一致すれば許可される。重複は許可されない。 | 属性はローカル許可リストにある場合にのみ許可される。 グローバル除去リストとローカル許可リストの間で重複エントリは許可されない。 この特定の要素に関してはグローバル除去リストは機能を持たないが、ローカル許可リストを持たない他の要素には適用されうる点に注意。 |
ローカル removeAttributes
| 属性は、グローバル許可リストに含まれ、かつローカル除去リストに含まれない場合に許可される。ローカル除去はグローバル許可リストの部分集合でなければならない。 | 属性はどちらのリストにもない場合に許可される。 グローバル除去リストとローカル除去リストの間で重複エントリは許可されない。 |
多くの場合、グローバルと要素ごとのリスト間で重複が許されない非対称性に注意されたい。ただし、グローバル許可リストと要素ごとの除去リストの組み合わせの場合は、後者は前者の部分集合でなければならない。上記表の重複にのみ焦点を当てた抜粋は次のとおり:
グローバル attributes
| グローバル removeAttributes
| |
|---|---|---|
ローカル attributes
| 重複は許可されない。 | 重複は許可されない。 |
ローカル removeAttributes
| ローカル除去はグローバル許可リストの部分集合でなければならない。 | 重複は許可されない。 |
dataAttributes
設定は、custom data attributes を許可する。上記の規則は、dataAttributes
を許可リストと見なせば、custom data attributes にも容易に拡張できる:
グローバル attributes
と dataAttributes
が設定されている
| |
|---|---|
ローカル attributes
| すべての custom data attributes が許可される。重複となるため、いかなる custom data attributes もいかなる許可リストにも列挙してはならない。 |
ローカル removeAttributes
| custom data attribute は、ローカル除去リストに列挙されていない限り許可される。 重複となるため、いかなる custom data attribute もグローバル許可リストに列挙してはならない。 |
これらの規則を言葉でまとめると:
-
グローバルとローカルの各リスト間の重複と相互作用:
-
グローバル
attributes許可リストが存在する場合、すべての要素のローカルリストは次を満たす:-
ローカル
attributes許可リストが存在する場合、これらのリスト間に重複エントリがあってはならない。 -
ローカル
removeAttributes除去リストが存在する場合、そのすべてのエントリはグローバルattributes許可リストにも列挙されていなければならない。 -
dataAttributesが true の場合、いかなる custom data attributes もいずれの許可リストにも列挙してはならない。
-
-
グローバル
removeAttributes除去リストが存在する場合:-
ローカル
attributes許可リストが存在する場合、これらのリスト間に重複エントリがあってはならない。 -
ローカル
removeAttributes除去リストが存在する場合、これらのリスト間に重複エントリがあってはならない。 -
ローカル
attributes許可リストとローカルremoveAttributes除去リストの両方が同時に存在してはならない。 -
dataAttributesは存在してはならない。
-
-
SanitizerConfig
config は、次の条件がすべて成り立つときに valid である:
-
config は、
elementsまたはremoveElementsのいずれかの key を持つが、両方は持たない。 -
config は、
attributesまたはremoveAttributesのいずれかの key を持つが、両方は持たない。 -
Assert:
SanitizerElementNamespaceWithAttributes、SanitizerElementNamespace、 およびSanitizerAttributeNamespaceの項目はすべて正規化されている(適切に canonicalize a sanitizer element または canonicalize a sanitizer attribute が実行されている)こと。 -
config[
elements]、 config[removeElements]、 config[replaceWithChildrenElements]、 config[attributes]、 および config[removeAttributes] は、存在する 場合でも、 重複を持たない。 -
config[
elements] と config[replaceWithChildrenElements] がいずれも 存在する 場合、 config[elements] と config[replaceWithChildrenElements] の intersection は empty である。 -
config[
removeElements] と config[replaceWithChildrenElements] がいずれも 存在する 場合、 config[removeElements] と config[replaceWithChildrenElements] の intersection は empty である。 -
config[
attributes] が 存在する 場合:-
-
各 config[
elements] の element について:-
element[
attributes] も element[removeAttributes] も(存在するなら)重複を持たない。 -
config[
attributes] と element[attributes] の 既定値付き « » における intersection は empty である。 -
element[
removeAttributes] の 既定値付き « » は、 config[attributes] の subset である。 -
dataAttributesが 存在 し、かつdataAttributesが true の場合:-
element[
attributes] は custom data attribute を含まない。
-
-
-
-
アサート: config[
dataAttributes] 存在する。 -
dataAttributesが true の場合:-
config[
attributes] は custom data attribute を含まない。
-
-
-
config[
removeAttributes] が 存在する 場合:-
config[
elements] が 存在する 場合、 各 config[elements] の element について:-
element[
attributes] と element[removeAttributes] が同時に 存在 してはならない。 -
element[
attributes] も element[removeAttributes] も(存在するなら)重複を持たない。 -
config[
removeAttributes] と element[attributes] の 既定値付き « » における intersection は empty である。 -
config[
removeAttributes] と element[removeAttributes] の 既定値付き « » における intersection は empty である。
-
-
config[
dataAttributes] は 存在しない。
-
Note: 設定の適用 を
dictionary
から行うと、少し正規化が実施される。特に、許可リストと除去リストの双方が欠落している場合、これは空の除去リストとして解釈される。そのため {} 自体は
valid
な構成ではないが、{removeElements:[],removeAttributes:[]} に正規化され、これは有効である。この正規化ステップは、欠落した dictionary
が空のものと一貫するように選ばれており、すなわち
setHTMLUnsafe(txt) が setHTMLUnsafe(txt, {sanitizer: {}}) と一貫するようにするためである。
3. アルゴリズム
Element
または DocumentFragment
の target、Element
の contextElement、文字列 html、辞書
options、boolean safe を受け取り、次を行う:
-
もし safe かつ contextElement の ローカル名 が "
script" であり、 contextElement の 名前空間 が HTML 名前空間 または SVG 名前空間 の場合、何もせずリターンする。 -
sanitizer を get a sanitizer instance from options に options と safe を与えて呼び出した結果とする。
-
newChildren を HTML フラグメントパースアルゴリズムに contextElement、html、true を指定して実行した結果とする。
-
fragment を新しい
DocumentFragmentとし、 その ノードドキュメント は contextElement の ノードドキュメント とする。 -
newChildren の各 node について、fragment に append する。
-
sanitize を fragment に sanitizer と safe で呼び出す。
Note: このアルゴリズムは SetHTMLOptions
と
SetHTMLUnsafeOptions
の両方で機能する。既定値のみが異なる。
-
sanitizerSpec を "
default" とする。 -
もし options["
sanitizer"] が 存在するなら:-
sanitizerSpec を options["
sanitizer"] に設定する。
-
-
Assert: sanitizerSpec は
Sanitizerのインスタンス、 string であってSanitizerPresetsのメンバー、または dictionary のいずれかである。 -
もし sanitizerSpec が string なら:
-
sanitizerSpec を 組み込みの安全な既定構成 に設定する。
-
Assert: sanitizerSpec は
Sanitizerのインスタンス、 または dictionary のいずれかである。 -
もし sanitizerSpec が dictionary なら:
-
sanitizer を新しい
Sanitizerインスタンスとする。 -
setConfigurationResult を、set a configuration を sanitizer 上で sanitizerSpec と not safe を用いて実行した結果とする。
-
もし setConfigurationResult が false なら、throw により
TypeErrorを投げる。 -
sanitizerSpec を sanitizer に設定する。
-
-
sanitizerSpec を返す。
3.1. サニタイズ
ParentNode
node、Sanitizer
sanitizer、そして boolean の safe を用いて、次の手順を実行する:
-
configuration を sanitizer の configuration の値とする。
-
もし safe が true なら、configuration を configuration に対して remove unsafe を呼び出した結果に設定する。
-
node と configuration に対して sanitize core を呼び出し、さらに handleJavascriptNavigationUrls を safe に設定して実行する。
ParentNode
node、SanitizerConfig
configuration、および
boolean
の handleJavascriptNavigationUrls を用いて、
node を起点に DOM ツリーを再帰的に処理する。手順は次のとおりである:
-
For each node の children の child について:
-
Assert: child は implements
Text、Comment、Element、 またはDocumentTypeである。Note: 現在、このアルゴリズムは HTML パーサの出力に対してのみ呼び出され、このアサーションは満たされるはずである。
DocumentTypeはparseHTMLおよびparseHTMLUnsafeの場合にのみ現れるべきである。将来このアルゴリズムが異なる文脈で用いられるなら、 この前提は再検討される必要がある。 -
もし child が implements
DocumentTypeなら、continue。 -
もし child が implements
Textなら、continue。 -
もし child が implements
Commentの場合: -
それ以外の場合:
-
elementName を、child の local name と namespace を持つ
SanitizerElementNamespaceとする。 -
もし configuration["
replaceWithChildrenElements"] が exists し、かつ configuration["replaceWithChildrenElements"] が contains elementName の場合:-
child に対して、configuration と handleJavascriptNavigationUrls を用いて sanitize core を呼び出す。
-
replace all を呼び出し、 child 内の child の children に置き換える。
-
-
さもなくば:
-
もし configuration["
removeElements"] が contains elementName の場合:
-
-
もし elementName が equals «[ "
name" → "template", "namespace" → HTML namespace ]» なら、 configuration と handleJavascriptNavigationUrls を用いて、 child の template contents に対して sanitize core を呼び出す。 -
もし child が shadow host であるなら、 configuration と handleJavascriptNavigationUrls を用いて child の shadow root に対して sanitize core を呼び出す。
-
elementWithLocalAttributes を « [] » とする。
-
もし configuration["
elements"] が exists し、 configuration["elements"] が contains elementName の場合:-
elementWithLocalAttributes を configuration["
elements"][elementName] に設定する。
-
-
For each child の attribute list 内の attribute について:
-
attrName を、attribute の local name と namespace を持つ
SanitizerAttributeNamespaceとする。 -
もし elementWithLocalAttributes["
removeAttributes"] が with default « » であり、かつ contains attrName の場合:-
remove attribute。
-
-
さもなくば、もし configuration["
attributes"] が exists の場合:-
もし configuration["
attributes"] が contain attrName ではなく、かつ elementWithLocalAttributes["attributes"] が with default « » であり、かつ contain attrName でもなく、さらに "data-" が code unit prefix ではなく、 attribute の local name に対して かつ namespace がnullではない、または configuration["dataAttributes"] が true ではない場合:-
remove attribute。
-
-
-
それ以外の場合:
-
もし elementWithLocalAttributes["
attributes"] が exists し、かつ elementWithLocalAttributes["attributes"] が contain attrName でないなら:-
remove attribute。
-
-
さもなくば、もし configuration["
removeAttributes"] が contains attrName の場合:-
remove attribute。
-
-
-
もし handleJavascriptNavigationUrls なら:
-
もし «[elementName, attrName]» が built-in navigating URL attributes list のエントリに一致し、かつ attribute が contains a javascript: URL であるなら、remove attribute。
-
もし child の namespace が is MathML Namespace であり、 かつ attr の local name が is "
href" で、attr の namespace がnullまたは XLink namespace であり、 かつ attr が contains a javascript: URL の場合、 remove attribute。 -
もし built-in animating URL attributes list が contains «[elementName, attrName]» で、かつ attr の value が is "
href" または "xlink:href" なら、remove attribute。
-
-
-
child に対して、configuration と handleJavascriptNavigationUrls を用いて sanitize core を呼び出す。
-
-
javascript: URL をナビゲーション時のみサポートします。ナビゲーション自体は XSS
脅威ではないため、ナビゲーション先が javascript: URL である場合のみ対応し、他のナビゲーション一般は扱いません。
宣言的ナビゲーションにはいくつか種類があります:
-
アンカー要素。(HTML・SVG 名前空間の
<a>) -
フォーム要素(form action の一部としてナビゲーションを引き起こす)。
-
[SVG11] のアニメーション。
最初の2つは 組み込みナビゲーションURL属性リスト でカバーされます。
MathML の場合は別ルールで対処します。本仕様には「名前空間ごとのグローバル」ルールの形式がないためです。
SVG のアニメーション要素は
組み込みアニメーションURL属性リスト でカバーされますが、
SVG アニメーション要素の解釈はアニメーション対象に依存し、サニタイズ中はその対象がわからないため、sanitize アルゴリズムは href 属性のアニメーション自体をブロックします。
3.2. 構成の変更
構成修正メソッドは Sanitizer
上のメソッドであり、
その構成内容を変更します。
これらのメソッドは有効性基準を維持します。
返り値はブーリアンで、構成が変更されたかどうかを呼び出し元に伝えます。
let s= new Sanitizer({ elements: [ "div" ]}); s. allowElement( "p" ); // true を返す。 div. setHTML( "<div><p>" , { sanitizer: s}); // `<div>` と `<p>` を許可。
let s= new Sanitizer({ elements: [ "div" ]}); s. removeElement( "p" ); // false を返す。なぜなら <p> はまだ許可されていないから。 div. setHTML( "<div><p>" , { sanitizer: s}); // `<div>` は許可、`<p>` は削除される。
SanitizerElement element を SanitizerConfig
configuration から 要素を除去する には:
-
グローバル許可リストか除去リストか、
-
それらにすでに element が含まれているかどうか。
-
element を、Sanitizer 要素の正規化 を element に対して行った結果に設定する。
-
modified を、 remove により configuration["
replaceWithChildrenElements"] から element を取り除いた結果に設定する。 -
さもなくば:
-
もし configuration["
removeElements"] が 含む element なら:-
コメント: グローバル除去リストがあり、すでに element を含んでいる。
-
modified を返す。
-
-
コメント: グローバル除去リストがあるが、element は含まれていない。
-
Add により element を configuration["
removeElements"] に追加する。 -
true を返す。
-
SanitizerConfig
configuration から 属性を除去する には:
注記: このメソッドは 2 つの場合を区別する。すなわち、グローバル許可リストがあるか、グローバル除去リストがあるかである。グローバル除去リストに attribute を追加する場合、有効性基準を維持するために要素ごとの許可/除去リストの手直しが必要となることがある。グローバル許可リストから attribute を削除する場合、ローカルの除去リストからも当該属性を削除しなければならないことがある。
-
attribute を、Sanitizer 属性の正規化 を attribute に対して行った結果に設定する。
-
もし configuration["
attributes"] が 存在するなら:-
コメント: グローバル許可リストがある場合、attribute を追加する必要がある。
-
もし configuration["
attributes"] が 含む attribute でないなら:-
false を返す。
-
-
コメント: 要素ごとの許可/除去リストを手直しする。
-
もし configuration["
elements"] が 存在するなら:-
各 configuration["
elements"] の element について:-
もし element["
removeAttributes"] が 既定値付き « » で 含む attribute の場合:-
Remove により element["
removeAttributes"] から attribute を取り除く。
-
-
-
-
Remove により configuration["
attributes"] から attribute を取り除く。 -
true を返す。
-
-
さもなくば:
-
コメント: グローバル除去リストがある場合、attribute を追加する必要がある。
-
もし configuration["
removeAttributes"] が 含む attribute なら false を返す。 -
コメント: 要素ごとの許可/除去リストを手直しする。
-
もし configuration["
elements"] が 存在するなら:-
各 configuration["
elements"] の element について:-
もし element["
attributes"] が 既定値付き « » で 含む attribute の場合:-
Remove により element["
attributes"] から attribute を取り除く。
-
-
もし element["
removeAttributes"] が 既定値付き « » で 含む attribute の場合:-
Remove により element["
removeAttributes"] から attribute を取り除く。
-
-
-
-
Append により attribute を configuration["
removeAttributes"] に追加する。 -
true を返す。
-
SanitizerConfig
configuration から unsafe を除去する には、
次を行う:
注記: このアルゴリズムの名称は remove unsafe であるが、 "unsafe" という用語は本仕様における厳密な意味で用いており、 文書へ挿入されたときに JavaScript が実行されるコンテンツを指す。言い換えると、このメソッドは XSS の機会を取り除く。
-
アサート: key set(built-in safe baseline configuration の) は equals «[ "
removeElements", "removeAttributes" ] » である。 -
result を false とする。
-
各 element( built-in safe baseline configuration[
removeElements] 内) について:-
要素を除去する を呼び出し、 configuration から element を除去する。
-
呼び出しが true を返したら、result を true に設定する。
-
-
各 attribute( built-in safe baseline configuration[
removeAttributes] 内) について:-
属性を除去する を呼び出し、 configuration から attribute を除去する。
-
呼び出しが true を返したら、result を true に設定する。
-
-
各 イベントハンドラー・コンテンツ属性に列挙された attribute について:
-
属性を除去する を呼び出し、 configuration から attribute を除去する。
-
呼び出しが true を返したら、result を true に設定する。
-
-
result を返す。
3.3. 構成を設定する
Sanitizer
sanitizer を受け取り、次を行う:
-
canonicalize configuration with allowCommentsAndDataAttributes を実行する。
-
もし configuration が 有効 でない場合、false を返す。
-
sanitizer の configuration を configuration に設定する。
-
true を返す。
3.4. 構成の正規化
Sanitizer
は configuration を正規化された形式で保存します。これにより様々な処理ステップが簡単になります。
elements
リスト {elements: ["div"]} は
{elements: [{name: "div", namespace: "http://www.w3.org/1999/xhtml"}]} のように保存されます。
SanitizerConfig
configuration
と boolean 型の
allowCommentsAndDataAttributes を用いて:
注: configuration は
[WebIDL]
によって JavaScript の値が SanitizerConfig
へ変換された結果であると仮定する。
-
configuration["
elements"] と configuration["removeElements"] のいずれも 存在しない場合、 設定する configuration["removeElements"] を « » に。 -
configuration["
attributes"] と configuration["removeAttributes"] のいずれも 存在しない場合、 設定する configuration["removeAttributes"] を « » に。 -
configuration["
elements"] が 存在する場合:-
elements を « » にする。
-
各 element を configuration["
elements"] から取り出し、次を行う:-
次を付加する: サニタイザー要素の属性を正規化する element の結果を elements に。
-
-
configuration["
elements"] を elements に設定する。
-
-
configuration["
removeElements"] が 存在する場合:-
elements を « » にする。
-
各 element を configuration["
removeElements"] から取り出し、次を行う:-
次を付加する: サニタイザー要素を正規化する element の結果を elements に。
-
-
configuration["
removeElements"] を elements に設定する。
-
-
configuration["
replaceWithChildrenElements"] が 存在する場合:-
elements を « » にする。
-
各 element を configuration["
replaceWithChildrenElements"] から取り出し、次を行う:-
次を付加する: サニタイザー要素を正規化する element の結果を elements に。
-
-
configuration["
replaceWithChildrenElements"] を elements に設定する。
-
-
configuration["
attributes"] が 存在する場合:-
attributes を « » にする。
-
各 attribute を configuration["
attributes"] から取り出し、次を行う:-
次を付加する: サニタイザー属性を正規化する attribute の結果を attributes に。
-
-
configuration["
attributes"] を attributes に設定する。
-
-
configuration["
removeAttributes"] が 存在する場合:-
attributes を « » にする。
-
各 attribute を configuration["
removeAttributes"] から取り出し、次を行う:-
次を付加する: サニタイザー属性を正規化する attribute の結果を attributes に。
-
-
configuration["
removeAttributes"] を attributes に設定する。
-
-
configuration["
comments"] が存在しない場合、 設定する configuration["comments"] を allowCommentsAndDataAttributes に。 -
configuration["
attributes"] が 存在し、かつ configuration["dataAttributes"] が存在しない場合、 設定する configuration["dataAttributes"] を allowCommentsAndDataAttributes に。
SanitizerElementWithAttributes
element に対して行うには:
-
result を、canonicalize a sanitizer element を element に対して実行した結果とする。
-
element が dictionary の場合:
-
element["
attributes"] が exists する場合:-
attributes を « » とする。
-
For each element["
attributes"] の attribute について:-
Append により、 canonicalize a sanitizer attribute を attribute に対して実行した結果を attributes に追加する。
-
-
Set により result["
attributes"] を attributes に設定する。
-
-
element["
removeAttributes"] が exists する場合:-
attributes を « » とする。
-
For each element["
removeAttributes"] の attribute について:-
Append により、 canonicalize a sanitizer attribute を attribute に対して実行した結果を attributes に追加する。
-
-
Set により result["
removeAttributes"] を attributes に設定する。
-
-
-
result["
attributes"] と result["removeAttributes"] のいずれも exist しない場合:-
Set により result["
removeAttributes"] を « » に設定する。
-
-
result を返す。
SanitizerElement
element について、
canonicalize a sanitizer name を element と
既定の名前空間として HTML namespace を用いて実行した結果を返す。
SanitizerAttribute
attribute について、
canonicalize a sanitizer name を attribute
と既定の名前空間として null を用いて実行した結果を返す。
-
Assert: name は
DOMStringか、dictionary のいずれかである。 -
もし name が
DOMStringなら、 «[ "name" → name, "namespace" → defaultNamespace]» を返す。 -
Assert: name は dictionary であり、 name["name"] と name["namespace"] の両方が exist する。
-
もし name["namespace"] が空文字列であるなら、null に設定する。
-
«[
"name" → name["name"],
"namespace" → name["namespace"]
]» を返す。
3.5. 補助アルゴリズム
この仕様で使われる正規化された
element
や attribute name
のリストでは、
リスト内に含まれるかどうかは "name" と "namespace"
の両方の一致によって決まります:
-
removed を false にする。
-
list の各 entry について:
-
もし item["name"] が entry["name"] と等しく、 item["namespace"] が entry["namespace"] と等しい場合:
-
entry を list から削除する。
-
removed を true にする。
-
-
-
removed を返す。
SanitizerElement
に対する intersection は
集合の積集合ですが、
各エントリは事前に 正規化 されています:
-
set A を « [] » にする。
-
set B を « [] » にする。
-
A の各 entry について set append で 正規化した結果を set A に追加。
-
B の各 entry について set append で 正規化した結果を set B に追加。
-
set A と set B の 積集合を返す。
3.6. 組み込み
組み込みは4つあります:
組み込みの安全な既定構成は次の通りです:
{ "elements" : [ { "name" : "math" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "merror" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mfrac" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mi" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mmultiscripts" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mn" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mo" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "fence" , "namespace" : null }, { "name" : "form" , "namespace" : null }, { "name" : "largeop" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "maxsize" , "namespace" : null }, { "name" : "minsize" , "namespace" : null }, { "name" : "movablelimits" , "namespace" : null }, { "name" : "rspace" , "namespace" : null }, { "name" : "separator" , "namespace" : null }, { "name" : "stretchy" , "namespace" : null }, { "name" : "symmetric" , "namespace" : null } ] }, { "name" : "mover" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accent" , "namespace" : null } ] }, { "name" : "mpadded" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "depth" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "lspace" , "namespace" : null }, { "name" : "voffset" , "namespace" : null }, { "name" : "width" , "namespace" : null } ] }, { "name" : "mphantom" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mprescripts" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mroot" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mrow" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "ms" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mspace" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "depth" , "namespace" : null }, { "name" : "height" , "namespace" : null }, { "name" : "width" , "namespace" : null } ] }, { "name" : "msqrt" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mstyle" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msub" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msubsup" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "msup" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtable" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtd" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "columnspan" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null } ] }, { "name" : "mtext" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "mtr" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "munder" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accentunder" , "namespace" : null } ] }, { "name" : "munderover" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [ { "name" : "accent" , "namespace" : null }, { "name" : "accentunder" , "namespace" : null } ] }, { "name" : "semantics" , "namespace" : "http://www.w3.org/1998/Math/MathML" , "attributes" : [] }, { "name" : "a" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "href" , "namespace" : null }, { "name" : "hreflang" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "abbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "address" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "article" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "aside" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "b" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "bdi" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "bdo" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "blockquote" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null } ] }, { "name" : "body" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "br" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "caption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "cite" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "code" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "col" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "colgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "span" , "namespace" : null } ] }, { "name" : "data" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "dd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "del" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "dfn" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "div" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dl" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "dt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "em" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figcaption" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "figure" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "footer" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h1" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h2" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h3" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h4" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h5" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "h6" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "head" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "header" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hgroup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "hr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "html" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "i" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ins" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "cite" , "namespace" : null }, { "name" : "datetime" , "namespace" : null } ] }, { "name" : "kbd" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "li" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "value" , "namespace" : null } ] }, { "name" : "main" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "mark" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "menu" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "nav" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ol" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "reversed" , "namespace" : null }, { "name" : "start" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "p" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "pre" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "q" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "rt" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ruby" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "s" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "samp" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "search" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "section" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "small" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "span" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "strong" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "sub" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "sup" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "table" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tbody" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "td" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "colspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null } ] }, { "name" : "tfoot" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "th" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "abbr" , "namespace" : null }, { "name" : "colspan" , "namespace" : null }, { "name" : "headers" , "namespace" : null }, { "name" : "rowspan" , "namespace" : null }, { "name" : "scope" , "namespace" : null } ] }, { "name" : "thead" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "time" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [ { "name" : "datetime" , "namespace" : null } ] }, { "name" : "title" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "tr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "u" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "ul" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "var" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "wbr" , "namespace" : "http://www.w3.org/1999/xhtml" , "attributes" : [] }, { "name" : "a" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "href" , "namespace" : null }, { "name" : "hreflang" , "namespace" : null }, { "name" : "type" , "namespace" : null } ] }, { "name" : "circle" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "r" , "namespace" : null } ] }, { "name" : "defs" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "desc" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "ellipse" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "cx" , "namespace" : null }, { "name" : "cy" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null } ] }, { "name" : "foreignObject" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "g" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "line" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "x1" , "namespace" : null }, { "name" : "x2" , "namespace" : null }, { "name" : "y1" , "namespace" : null }, { "name" : "y2" , "namespace" : null } ] }, { "name" : "marker" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "markerHeight" , "namespace" : null }, { "name" : "markerUnits" , "namespace" : null }, { "name" : "markerWidth" , "namespace" : null }, { "name" : "orient" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "refX" , "namespace" : null }, { "name" : "refY" , "namespace" : null }, { "name" : "viewBox" , "namespace" : null } ] }, { "name" : "metadata" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "path" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "d" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null } ] }, { "name" : "polygon" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "polyline" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "pathLength" , "namespace" : null }, { "name" : "points" , "namespace" : null } ] }, { "name" : "rect" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "pathLength" , "namespace" : null }, { "name" : "rx" , "namespace" : null }, { "name" : "ry" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "svg" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "height" , "namespace" : null }, { "name" : "preserveAspectRatio" , "namespace" : null }, { "name" : "viewBox" , "namespace" : null }, { "name" : "width" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "text" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] }, { "name" : "textPath" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "method" , "namespace" : null }, { "name" : "path" , "namespace" : null }, { "name" : "side" , "namespace" : null }, { "name" : "spacing" , "namespace" : null }, { "name" : "startOffset" , "namespace" : null }, { "name" : "textLength" , "namespace" : null } ] }, { "name" : "title" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [] }, { "name" : "tspan" , "namespace" : "http://www.w3.org/2000/svg" , "attributes" : [ { "name" : "dx" , "namespace" : null }, { "name" : "dy" , "namespace" : null }, { "name" : "lengthAdjust" , "namespace" : null }, { "name" : "rotate" , "namespace" : null }, { "name" : "textLength" , "namespace" : null }, { "name" : "x" , "namespace" : null }, { "name" : "y" , "namespace" : null } ] } ], "attributes" : [ { "name" : "alignment-baseline" , "namespace" : null }, { "name" : "baseline-shift" , "namespace" : null }, { "name" : "clip-path" , "namespace" : null }, { "name" : "clip-rule" , "namespace" : null }, { "name" : "color" , "namespace" : null }, { "name" : "color-interpolation" , "namespace" : null }, { "name" : "cursor" , "namespace" : null }, { "name" : "dir" , "namespace" : null }, { "name" : "direction" , "namespace" : null }, { "name" : "display" , "namespace" : null }, { "name" : "displaystyle" , "namespace" : null }, { "name" : "dominant-baseline" , "namespace" : null }, { "name" : "fill" , "namespace" : null }, { "name" : "fill-opacity" , "namespace" : null }, { "name" : "fill-rule" , "namespace" : null }, { "name" : "font-family" , "namespace" : null }, { "name" : "font-size" , "namespace" : null }, { "name" : "font-size-adjust" , "namespace" : null }, { "name" : "font-stretch" , "namespace" : null }, { "name" : "font-style" , "namespace" : null }, { "name" : "font-variant" , "namespace" : null }, { "name" : "font-weight" , "namespace" : null }, { "name" : "lang" , "namespace" : null }, { "name" : "letter-spacing" , "namespace" : null }, { "name" : "marker-end" , "namespace" : null }, { "name" : "marker-mid" , "namespace" : null }, { "name" : "marker-start" , "namespace" : null }, { "name" : "mathbackground" , "namespace" : null }, { "name" : "mathcolor" , "namespace" : null }, { "name" : "mathsize" , "namespace" : null }, { "name" : "opacity" , "namespace" : null }, { "name" : "paint-order" , "namespace" : null }, { "name" : "pointer-events" , "namespace" : null }, { "name" : "scriptlevel" , "namespace" : null }, { "name" : "shape-rendering" , "namespace" : null }, { "name" : "stop-color" , "namespace" : null }, { "name" : "stop-opacity" , "namespace" : null }, { "name" : "stroke" , "namespace" : null }, { "name" : "stroke-dasharray" , "namespace" : null }, { "name" : "stroke-dashoffset" , "namespace" : null }, { "name" : "stroke-linecap" , "namespace" : null }, { "name" : "stroke-linejoin" , "namespace" : null }, { "name" : "stroke-miterlimit" , "namespace" : null }, { "name" : "stroke-opacity" , "namespace" : null }, { "name" : "stroke-width" , "namespace" : null }, { "name" : "text-anchor" , "namespace" : null }, { "name" : "text-decoration" , "namespace" : null }, { "name" : "text-overflow" , "namespace" : null }, { "name" : "text-rendering" , "namespace" : null }, { "name" : "title" , "namespace" : null }, { "name" : "transform" , "namespace" : null }, { "name" : "transform-origin" , "namespace" : null }, { "name" : "unicode-bidi" , "namespace" : null }, { "name" : "vector-effect" , "namespace" : null }, { "name" : "visibility" , "namespace" : null }, { "name" : "white-space" , "namespace" : null }, { "name" : "word-spacing" , "namespace" : null }, { "name" : "writing-mode" , "namespace" : null } ], "comments" : false , "dataAttributes" : false }
注: 含まれる[MathML] マークアップは [SafeMathML] に基づいています。
組み込みの安全なベースライン構成は、スクリプト内容のみをブロックすることを目的としています。内容は以下の通りです:
{ "removeElements" : [ { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "embed" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "frame" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "iframe" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "object" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "use" } ], "removeAttributes" : [] }
警告: remove unsafe アルゴリズムは、 イベントハンドラー内容属性 ([HTML] で定義)も追加で除去することを規定します。 もしユーザーエージェントが [HTML]仕様に独自の イベントハンドラー内容属性 を拡張して定義する場合は、その扱い方を決める責任があります。現在の イベントハンドラー内容属性 リストを使うと、安全なベースライン構成は実質的に次のようになります:
{ "removeElements" : [ { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "embed" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "frame" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "iframe" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "object" }, { "namespace" : "http://www.w3.org/1999/xhtml" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "script" }, { "namespace" : "http://www.w3.org/2000/svg" , "name" : "use" } ], "removeAttributes" : [ "onafterprint" , "onauxclick" , "onbeforeinput" , "onbeforematch" , "onbeforeprint" , "onbeforeunload" , "onbeforetoggle" , "onblur" , "oncancel" , "oncanplay" , "oncanplaythrough" , "onchange" , "onclick" , "onclose" , "oncontextlost" , "oncontextmenu" , "oncontextrestored" , "oncopy" , "oncuechange" , "oncut" , "ondblclick" , "ondrag" , "ondragend" , "ondragenter" , "ondragleave" , "ondragover" , "ondragstart" , "ondrop" , "ondurationchange" , "onemptied" , "onended" , "onerror" , "onfocus" , "onformdata" , "onhashchange" , "oninput" , "oninvalid" , "onkeydown" , "onkeypress" , "onkeyup" , "onlanguagechange" , "onload" , "onloadeddata" , "onloadedmetadata" , "onloadstart" , "onmessage" , "onmessageerror" , "onmousedown" , "onmouseenter" , "onmouseleave" , "onmousemove" , "onmouseout" , "onmouseover" , "onmouseup" , "onoffline" , "ononline" , "onpagehide" , "onpagereveal" , "onpageshow" , "onpageswap" , "onpaste" , "onpause" , "onplay" , "onplaying" , "onpopstate" , "onprogress" , "onratechange" , "onreset" , "onresize" , "onrejectionhandled" , "onscroll" , "onscrollend" , "onsecuritypolicyviolation" , "onseeked" , "onseeking" , "onselect" , "onslotchange" , "onstalled" , "onstorage" , "onsubmit" , "onsuspend" , "ontimeupdate" , "ontoggle" , "onunhandledrejection" , "onunload" , "onvolumechange" , "onwaiting" , "onwheel" ] }
javascript:"
ナビゲーションが「安全でない」とされる)は以下の通りです:
«[
[
{ "name" → "a", "namespace" → HTML namespace
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "area", "namespace" → HTML namespace
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "base", "namespace" → HTML namespace
},
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "button", "namespace" → HTML namespace
},
{ "name" → "formaction", "namespace" → null }
],
[
{ "name" → "form", "namespace" → HTML namespace
},
{ "name" → "action", "namespace" → null }
],
[
{ "name" → "input", "namespace" → HTML namespace
},
{ "name" → "formaction", "namespace" → null }
],
[
{ "name" → "a", "namespace" → SVG namespace },
{ "name" → "href", "namespace" → null }
],
[
{ "name" → "a", "namespace" → SVG namespace },
{ "name" → "href", "namespace" → XLink
namespace }
],
]»
組み込みアニメーションURL属性リストは、
[SVG11] において宣言的にナビゲーション要素を
"javascript:" URL へ変更するために使用されることがあります。内容は以下の通りです:
«[
[
{ "name" → "animate", "namespace" → SVG namespace },
{ "name" → "attributeName", "namespace" → null] }
],
[
{ "name" → "animateMotion", "namespace" → SVG namespace },
{ "name" → "attributeName", "namespace" → null }
],
[
{ "name" → "animateTransform", "namespace" → SVG namespace },
{ "name" → "attributeName", "namespace" → null }
],
[
{ "name" → "set", "namespace" → SVG namespace },
{ "name" → "attributeName", "namespace" → null }
],
]»
4. セキュリティ考慮事項
Sanitizer API は、与えられた HTML コンテンツを走査し、構成に従って要素や属性を除去することで DOM ベースのクロスサイトスクリプティングを防ぐことを目的としています。指定された API は、スクリプト実行可能なマークアップを残すような Sanitizer オブジェクトの構築を許してはならず、もしそうなった場合は脅威モデル上のバグです。
とはいえ、Sanitizer API の正しい利用だけでは防げないセキュリティ上の問題も存在し、それについては以下のセクションで説明します。
4.1. サーバーサイド反射型および保存型XSS
このセクションは規範的ではありません。
Sanitizer API は DOM 内だけで動作し、既存の DocumentFragment を走査・フィルターする機能を提供します。Sanitizer はサーバーサイドの反射型XSSや保存型XSSには対応しません。
4.2. DOMクラッタリング
このセクションは規範的ではありません。
DOMクラッタリングとは、攻撃者が id や name 属性を使って要素に名前をつけ、HTML要素の children
などのプロパティを悪意のある内容で上書きしてアプリケーションを混乱させる攻撃です。
Sanitizer API はデフォルトでは DOMクラッタリング攻撃を防ぎませんが、構成で id や name 属性を除去することができます。
4.3. スクリプトガジェットによるXSS
このセクションは規範的ではありません。
スクリプトガジェットとは、攻撃者が有名なJavaScriptライブラリなどの既存アプリケーションコードを利用して、攻撃者自身のコードを実行させる手法です。これは無害に見えるコードや一見無害なDOMノードを注入し、フレームワークがそれを解析・解釈することで JavaScript の実行につなげます。
Sanitizer API ではこの種の攻撃を防ぐことはできません。ページ作成者は未知の要素を許可する場合は特に注意し、data- や slot
属性、<slot> や <template>
など広く使われるテンプレート・フレームワーク固有のマークアップも明示的に設定する必要があります。これらの制限は網羅的ではないので、ページ作成者はサードパーティライブラリの挙動もよく確認してください。
4.4. 変異型XSS
このセクションは規範的ではありません。
変異型XSS(mXSS)は、HTMLスニペットを誤った文脈でパースした際にパーサの文脈不一致を利用する攻撃です。特に、一度パースしたHTMLフラグメントを文字列としてシリアライズし、別の親要素に再挿入した場合、同じように解釈される保証はありません。例えば外部コンテンツやタグの入れ子ミスによるパース挙動の変化を利用します。
Sanitizer API は文字列をノードツリー化する機能のみを提供します。文脈は各サニタイザー関数によって暗黙的に与えられ、Element.setHTML()
は現在の要素、Document.parseHTML() は新しいドキュメントを作成します。そのためSanitizer API自体は変異型XSSの直接的な影響は受けません。
もし開発者がサニタイズ済みノードツリーを .innerHTML
などで文字列にし、再度パースする場合はmXSSが発生しうるため、この運用は推奨されません。どうしても文字列でHTMLを扱う場合は、その文字列は信頼できないものとして扱い、DOM挿入時に再度サニタイズすべきです。つまり、一度サニタイズ→シリアライズしたHTMLは、もはやサニタイズ済みとは考えられません。
mXSSのより完全な解説は [MXSS] を参照してください。
5. 謝辞
本仕様は cure53 の [DOMPURIFY]、
Internet Explorer の window.toStaticHTML()
、そして Ben Bucksch による
[HTMLSanitizer]
に着想を得ています。Anne van Kesteren、Krzysztof Kotowicz、
Andrew C. H. Mc Millan、Tom Schuster、Luke Warlow、Guillaume Weghsteen、
Mike West 各氏の貴重なフィードバックにも感謝します。