1. このページの目的
Sanitizer API を使ってみる。
2. Sanitizer API の機能
- このAPIはあくまでサニタイズを行う。サニタイズは危険な文字列を削除することであり、エスケープ(一部の文字を置き換える)とは異なる。
- そのため、HTMLコードを表示したい場合は、予めHTMLタグをエスケープしておく必要があるはず。
- このAPIでは、サニタイズした文字列を返すのではなく、要素への出力までをまとめて行う。
- オプションを渡すことで、動作をカスタマイズすることが可能。デフォルトでは、より安全な側の処理になっている。
3. デモ
Safe DOM manipulation with the Sanitizer API に載っているコード
サニタイズ結果の文字列はコンソール上にも出力しているので、Chrome DevTools で確認できる。
3-1. 危険な箇所(ここではHTMLタグの属性)は削除される
Safe DOM manipulation with the Sanitizer API
const $span3_1 = document.querySelector('#output3-1');
const user_input3_1 = `<em>hello world</em><img src="" onerror=alert(0)>`
$span1.setHTML(user_input3_1, { sanitizer: new Sanitizer() });
3-2. <script>
タグを渡すとどうなるか?
個人的に試したかったもの。
const $span3_2 = document.querySelector('#output3-2');
const user_input3_2 = '<script>alert(1)<\/script>';
$span3_2.setHTML(user_input3_2, { sanitizer: new Sanitizer() });
- 何も挿入されない。
<script>
タグごと削除された。
3-2a. onerror
を渡すとどうなるか?
個人的に試したかったもの。
const $span3_2a = document.querySelector('#output3-2a');
const user_input3_2a = '<img src=/ onerror=alert(1)>';
$span3_2a.setHTML(user_input3_2a, { sanitizer: new Sanitizer() });
onerror
属性が削除された。onerror
属性の値を指定しなくても同様であった。
3-3. オプションを試す (allowElements, blockElements, dropElements)
Safe DOM manipulation with the Sanitizer API
const str3_3 = `hello <b><i>world</i></b>`
const $p3_3a = document.querySelector('#output3-3a');
const $p3_3b = document.querySelector('#output3-3b');
const $p3_3c = document.querySelector('#output3-3c');
const $p3_3d = document.querySelector('#output3-3d');
$p3_3a.setHTML(str3_3)
// <p>hello <b><i>world</i></b></p>
$p3_3b.setHTML(str3_3, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <p>hello <b>world</b></p>
$p3_3c.setHTML(str3_3, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <p>hello <i>world</i></p>
$p3_3d.setHTML(str3_3, { sanitizer: new Sanitizer({allowElements: []}) })
// <p>hello world</p>
- allowElements を指定すると、指定した以外のタグは許可されなくなる。
- 初期状態だと、allowElements には いろいろなタグが指定された状態になっている。(
Sanitizer.getDefaultConfiguration().allowElements
を実行すると確認できる) - 初期状態だと、blockElements は何も指定されていない状態になっている。(
Sanitizer.getDefaultConfiguration().blockElements
を実行すると確認できる)
3-4. オプションを試す (allowAttributes, dropAttributes)
Safe DOM manipulation with the Sanitizer API
const str3_4 = `<span id=foo class=bar style="color: red">hello</span>`
const $p3_4a = document.querySelector('#output3-4a');
const $p3_4b = document.querySelector('#output3-4b');
const $p3_4c = document.querySelector('#output3-4c');
const $p3_4d = document.querySelector('#output3-4d');
const $p3_4e = document.querySelector('#output3-4e');
const $p3_4f = document.querySelector('#output3-4f');
$p3_4a.setHTML(str3_4)
// <p><span id="foo" class="bar" style="color: red">hello</span></p>
$p3_4b.setHTML(str3_4, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <p><span style="color: red">hello</span></p>
$p3_4c.setHTML(str3_4, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <p><span>hello</span></p>
$p3_4d.setHTML(str3_4, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <p><span style="color: red">hello</span></p>
$p3_4e.setHTML(str3_4, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <p><span class="bar" style="color: red">hello</span></p>
$p3_4f.setHTML(str3_4, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <p>hello</p>
- allowAttributes を指定すると、指定した以外の属性は許可されなくなる。
- 初期状態だと、allowAttributes には いろいろな要素がカテゴリ毎に指定された状態になっている。(
Sanitizer.getDefaultConfiguration().allowAttributes
を実行すると確認できる)
3-5. オプションを試す (custom-elem)
Safe DOM manipulation with the Sanitizer API
customElements.define('custom-elem', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `Hello!`;
}
});
const str3_5 = `<custom-elem>hello</custom-elem>`
const $p3_5a = document.querySelector('#output3-5a');
const $p3_5b = document.querySelector('#output3-5b');
$p3_5a.setHTML(str3_5)
// <p></p>
const sanitizer3_5 = new Sanitizer({
allowCustomElements: true,
allowElements: ["p", "custom-elem"]
})
$p3_5b.setHTML(str3_5, { sanitizer3_5 })
// <p><custom-elem>hello</custom-elem></p>
- 元の記事と結果が違う。2つ目も何も出力されない。
3-6. DOMPurify の例
Safe DOM manipulation with the Sanitizer API
const $p3_6 = document.querySelector('#output3-6');
const user_input3_6 = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized3_6 = DOMPurify.sanitize(user_input3_6)
$p3_6.innerHTML = sanitized3_6
// `<em>hello world</em><img src="">`
- DOMPurify では文字列が生成される。それを要素にセットする。2段階の処理が必要。
- どの要素の中に出力する内容なのか?は考慮されない。
4. デモ
HTML Sanitizer API に載っているコード
4-1. どの要素の中かによって、処理が異なる
1.3. The Trouble With Strings, EXAMPLE 2
const $div4_1a = document.querySelector('#output4-1a');
const user_input4_1a = `<em>bla`
$div4_1a.setHTML(user_input4_1a, { sanitizer: new Sanitizer() });
const $textarea4_1b = document.querySelector('#output4-1b');
const user_input4_1b = `<em>bla`
$textarea4_1b.setHTML(user_input4_1b, { sanitizer: new Sanitizer() });
<em>
の閉じタグが追加される。<textarea>
タグ内だと、HTMLタグがエスケープされる。
4-2. どの要素の中かによって、処理が異なる
1.3. The Trouble With Strings, EXAMPLE 3
const user_input4_2 = `<td>text`
const $table4_2a = document.querySelector('#output4-2a');
$table4_2a.setHTML(user_input4_2, { sanitizer: new Sanitizer() });
const $div4_2b = document.querySelector('#output4-2b');
$div4_2b.setHTML(user_input4_2, { sanitizer: new Sanitizer() });
<table>
内に、`<td>text` を追加すると、<tbody>
や<tr>
なども追加される。<div>
内に、`<td>text` を追加すると、<td>
タグは削除される。
4-3. HTMLのコードを表示する
<pre><code>
タグにHTMLコードを書いてみる。
const user_input4_3_1 = `<em>text</em>`
const $span4_3_1 = document.querySelector('#output4-3-1');
$span4_3_1.setHTML(user_input4_3_1, { sanitizer: new Sanitizer() });
const user_input4_3_2 = `<em>text</em>`
const $span4_3_2 = document.querySelector('#output4-3-2');
$span4_3_2.setHTML(user_input4_3_2, { sanitizer: new Sanitizer() });
- HTMLタグを含むコードを画面に表示したい場合は、HTMLエスケープした文字列を Sanitizer API に渡すことになるようだ。HTMLエスケープしている時点で、ほとんどの危険性は取り除かれているはず。
5. メモ
- どのHTMLタグに対してどのような処理が行われるか?がハッキリ分からないので使いづらい。
- HTML Sanitizer API を見ても、そこまでは説明されていない。
6. 参考
- HTML Sanitizer API
- GitHub - WICG/sanitizer-api
- HTML Sanitizer API - Web APIs | MDN
- Sanitizer インタフェース(オブジェクト)(設定とサニタイズ処理を扱う)
- Sanitizer.sanitize() サニタイズされた DocumentFragment もしくは Document を返す。
- Sanitizer.sanitizeFor() HTML Element を返す
- Element インタフェースが拡張された
- Element.setHTML()(内部で Sanitizer オブジェクトを利用している)
- DocumentFragment
- Sanitizer インタフェース(オブジェクト)(設定とサニタイズ処理を扱う)
- Safe DOM manipulation with the Sanitizer API
- New in Chrome 105 - Chrome Developers
※ 2022年9月現在で、Sanitizer API: sanitize、Sanitizer API: sanitizeFor は未実装。