アプリケーションの DOM XSS 攻撃対象領域を縮小します。
なぜ重要なのでしょうか。
DOM ベースのクロスサイト スクリプティング(DOM XSS)は、ウェブ セキュリティの最も一般的な脆弱性の一つであり、アプリケーションに非常に簡単に導入されます。Trusted Types により、危険なウェブ API 関数をデフォルトで安全に保つことで、アプリケーションの作成、セキュリティ レビュー、DOM XSS 脆弱性のない維持のためのツールが提供されます。Trusted Types は Chrome 83 でサポートされており、他のブラウザではpolyfillを利用できます。最新のブラウザ間のサポート情報については、ブラウザの互換性をご覧ください。
背景
何年もの間、DOM XSS は、ウェブ セキュリティにおいて最も蔓延する(そして危険な)脆弱性の一つです。
クロスサイト スクリプティングには 2 つの異なるグループがあります。一部の XSS 脆弱性は、ウェブサイトを構成する HTML コードを安全でない状態で作成するサーバーサイド コードによって発生します。また、クライアント側に根本原因があり、JavaScript コードがユーザー制御コンテンツを使用して危険な関数を呼び出すケースもあります。
サーバー側の XSS を防ぐため、文字列を連結して HTML を生成せず、代わりに安全なコンテキストの自動エスケープ テンプレート ライブラリを使用します。ノンスベースのコンテンツ セキュリティ ポリシーを使用して、必然的に発生するバグをさらに軽減します。
現在は、Trusted Types を使用することで、クライアント側(DOM ベース)の XSS をブラウザで回避できるようになりました。
API の概要
Trusted Types は、次のようなリスクの高いシンク機能を遮断することで機能します。すでにご存じの方もいらっしゃるかもしれませんが、ブラウザ ベンダーやウェブ フレームワークでは、セキュリティ上の理由からこれらの機能の使用をやめています。
- スクリプト操作:
<script src>
、<script>
要素のテキスト コンテンツの設定。 文字列から HTML を生成する:
innerHTML
、outerHTML
、insertAdjacentHTML
、<iframe> srcdoc
、document.write
、document.writeln
、DOMParser.parseFromString
プラグイン コンテンツの実行:
<embed src>
、<object data>
、<object codebase>
ランタイム JavaScript コードのコンパイル:
eval
、setTimeout
、setInterval
、new Function()
Trusted Types では、データを処理してから上記のシンク関数に渡す必要があります。文字列を使用するだけでは失敗します。ブラウザは、データの信頼性を判断できないためです。
anElement.innerHTML = location.href;Trusted Types を有効にすると、ブラウザは TypeError をスローし、文字列を含む DOM XSS シンクを使用できなくなります。
データが安全に処理されたことを示すために、Trusted Type という特別なオブジェクトを作成します。
anElement.innerHTML = aTrustedHTML;Trusted Types を有効にすると、ブラウザは HTML スニペットを想定するシンクで
TrustedHTML
オブジェクトを受け入れます。他の機密性の高いシンクにも TrustedScript
オブジェクトと TrustedScriptURL
オブジェクトがあります。
Trusted Types により、アプリケーションの DOM XSS 攻撃対象領域が大幅に縮小されます。これにより、セキュリティ レビューが簡単になり、ブラウザで実行時にコードをコンパイル、lint チェック、バンドルする場合に、タイプベースのセキュリティ チェックを適用できます。
Trusted Types の使用方法
コンテンツ セキュリティ ポリシー違反の報告に備える
レポート コレクタ(オープンソースの go-csp-collector など)をデプロイすることも、商用の同等のものを使用することもできます。ブラウザで違反をデバッグすることもできます。
js
document.addEventListener('securitypolicyviolation',
console.error.bind(console));
レポート専用の CSP ヘッダーを追加する
信頼できるタイプに移行するドキュメントに、次の HTTP レスポンス ヘッダーを追加します。
text
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
これで、すべての違反が //my-csp-endpoint.example
に報告されますが、ウェブサイトは機能し続けます。次のセクションでは、//my-csp-endpoint.example
の仕組みについて説明します。
Trusted Types 違反の特定
今後、Trusted Types が違反を検出するたびに、構成済みの report-uri
にレポートが送信されます。たとえば、アプリケーションが innerHTML
に文字列を渡すと、ブラウザから次のレポートが送信されます。
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
これは、39 行目の https://my.url.example/script.js
で、innerHTML
が <img src=x
で始まる文字列で呼び出されたことを示しています。この情報は、DOM XSS を導入している可能性があり、変更が必要なコードの部分を絞り込むのに役立ちます。
違反を解消する
Trusted Type の違反を修正する方法は 2 つあります。不適切なコードを削除するか、ライブラリを使用するか、Trusted Type ポリシーを作成するか、最後の手段としてデフォルトのポリシーを作成します。
問題のあるコードを書き換える
この非準拠の機能はもう必要なくなったのでしょうか。それとも、エラーが発生しやすい関数を使用せずに、最新の方法で書き換えることができるでしょうか。
el.innerHTML = '';
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
ライブラリを使用する
一部のライブラリでは、シンク関数に渡すことができる Trusted Types がすでに生成されています。たとえば、DOMPurify を使用して HTML スニペットをサニタイズし、XSS ペイロードを削除できます。
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify は Trusted Types をサポートしており、ブラウザで違反が発生しないように、サニタイズされた HTML を TrustedHTML
オブジェクトにラップして返します。
Trusted Type ポリシーを作成する
場合によっては、機能を削除できず、値をサニタイズして Trusted Type を作成するライブラリが存在しないこともあります。そのような場合は、Trusted Type オブジェクトを自分で作成します。
そのためには、まずポリシーを作成します。ポリシーは、入力に対して特定のセキュリティ ルールを適用する Trusted Types のファクトリです。
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
このコードは、createHTML()
関数で TrustedHTML
オブジェクトを生成できる myEscapePolicy
というポリシーを作成します。定義されたルールにより、<
文字が HTML エスケープされ、新しい HTML 要素が作成されなくなります。
ポリシーを次のように使用します。
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
デフォルト ポリシーを使用する
場合によっては、問題のあるコードを変更できない。たとえば、CDN からサードパーティ ライブラリを読み込む場合が該当します。その場合は、デフォルト ポリシーを使用します。
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
default
という名前のポリシーは、Trusted Type のみを受け入れるシンクで文字列が使用されている場合に使用されます。
コンテンツ セキュリティ ポリシーの適用に切り替える
アプリケーションで違反が発生しなくなったら、Trusted Types の適用を開始できます。
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
必要な操作は以上です。ウェブ アプリケーションがどれほど複雑であっても、DOM XSS 脆弱性をもたらす可能性があるのはポリシーのコードだけです。また、ポリシーの作成を制限することで、さらに制限できます。