Trusted Types を使用して DOM ベースの XSS 攻撃を防止する

ウェブ機能: 信頼できるタイプ

Trusted Types は、DOM ベースのクロスサイト スクリプティング(DOM XSS)を防止するセキュリティ機能です。この機能では、「危険な」ブラウザ API(シンクと呼ばれる)に渡されるデータを、まず TrustedHTML などの特定の型に変換する必要があります。

デフォルトでは、多くの DOM API(innerHTML や document.write など)は未加工の文字列を受け入れます。信頼できないソースからの悪意のあるスクリプトが文字列に含まれている場合、そのスクリプトが実行される可能性があります。Trusted Types はこの動作を変更します。CSP を介して有効にすると、これらのシンクは、承認されたポリシーによって作成された TrustedHTML オブジェクトのみを受け入れるようになります。

主な実装の詳細

Trusted Types を使用するには、次の 4 つの主な手順を行います。

  1. 文字列をサニタイズまたは変換する方法を指定するセキュリティ ポリシーを定義します。
  2. そのポリシーを使用して、未加工の文字列から Trusted Type オブジェクトを作成します。
  3. DOM シンクに書き込むときに Trusted Type オブジェクトを使用する。
  4. コンテンツ セキュリティ ポリシー(CSP)を使用して Trusted Types を適用する。

1. ポリシーの作成

ポリシーは、コンテンツをサニタイズするための一連のルールです。trustedTypes.createPolicy() を使用して作成します。通常は、DOMPurify などのサニタイザー ライブラリまたはアプリケーションの HTML エスケープ ルールに委任します。

import DOMPurify from 'dompurify';

const myPolicy = trustedTypes.createPolicy('sanitize-html', {
  createHTML: (input) =>
    // Return a sanitized string; createPolicy() wraps it in a TrustedHTML object.
    // Example using DOMPurify:
    DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});

// Example using HTML escaping (for environments without third-party sanitizers):
const escapePolicy = trustedTypes.createPolicy('escape-html', {
  createHTML: (input) =>
    String(input)
      .replaceAll('&', '&')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#39;'),
});

2. ポリシーの使用

文字列をシンクに直接渡す代わりに、ポリシーの作成メソッドを呼び出します。

const untrustedInput = '<p>Hello, <strong>world</strong>!</p><img src="x" onerror="alert(1)">';

// In an enforced environment, this would throw a TypeError:
// element.innerHTML = untrustedInput;

// This returns a TrustedHTML object with unsafe elements and attributes removed:
const cleanHTML = myPolicy.createHTML(untrustedInput);
// Result: "<p>Hello, <strong>world</strong>!</p><img src=\"x\">"

3. Trusted Types を使用するようにシンクを移行する

シンクに書き込む各インスタンスを更新して、最初に Trusted Type オブジェクトに変換します。コードの更新が必要な場所を特定するには、TrustedType ではなく文字列を使用してシンクに書き込まれたときにログを記録する一時的な default ポリシーを定義します。

trustedTypes.createPolicy("default", {
  createHTML(value) {
    console.warn("String passed instead of Trusted Type.");
    return DOMPurify.sanitize(value, { RETURN_TRUSTED_TYPE: false });
  },
});

Trusted Types をサポートする一般的なシンクには、次のようなものがあります。

  • HTML: element.innerHTML、element.outerHTML、document.write()、element.setHTMLUnsafe()、Document.parseHTMLUnsafe()
  • スクリプト: <script> テキスト コンテンツ、eval()
  • ScriptURL: <script src>、Worker()、SharedWorker()
// Assign the TrustedHTML object.
element.innerHTML = cleanHTML;

「安全な」HTML サニタイズ メソッド(element.setHTML() と Document.parseHTML()、信頼できない HTML のサニタイズで説明)では、潜在的な XSS 攻撃を常にネイティブにサニタイズするため、Trusted Types ポリシーは必要ありません。

4. CSP で Trusted Types を適用する

アプリケーションが破損しないように、Trusted Types は段階的にロールアウトする必要があります。完全な適用は最も強力な保護を提供しますが、すべてのシンク書き込みが移行されていない場合、サイトが破損するリスクがあります。

前提条件

  • フレームワークの連携: アプリケーションのフレームワーク(または DOM シンクに書き込むサードパーティ ライブラリ)が TrustedHTML 値または TrustedScript 値を生成しない場合、そのコードを破損せずに Trusted Types を適用することはできません。ロールアウトを開始する前に、監査フレームワークのサポートを確認します。
  • シンクのリファクタリング: 危険なシンクを特定してリファクタリングし、Trusted Types ポリシーを使用するようにします。

ロールアウト方法

  1. レポートのみ: まず、Content-Security-Policy-Report-Only ヘッダーを使用してロールアウトします。これにより、書き込みを実際にブロックすることなく、違反がレポート エンドポイントに記録されます。

    Content-Security-Policy-Report-Only: require-trusted-types-for 'script';
    
  2. 適用: ポリシーを統合し、違反レポートがゼロになったら、完全な適用に移行します。

    Content-Security-Policy: require-trusted-types-for 'script';
    

作成を許可するポリシーを制限することもできます(sanitize-html や escape-html などのポリシー名をリストします。ポリシーが DOMPurify.sanitize() に委任される場合は dompurify を含めます。これにより、内部で dompurify ポリシーが作成されます。移行中にデフォルト ポリシーを登録する場合は default を含めます)。

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;

この CSP を設定すると、Trusted Types を使用しないシンクへの書き込みはエラーをスローします。また、trusted-types に記載されていない名前で Trusted Type ポリシーを作成しようとすると、エラーが発生します。

この CSP をドキュメントの <head> の <meta> タグとして設定することもできますが、セキュリティが低下する可能性があります。

<!-- First child of `<head>`, as earlier tags will not enforce this CSP. -->
<meta
  http-equiv="Content-Security-Policy"
  content="require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;"
/>

代替戦略

信頼できるタイプをまだサポートしていないブラウザでは、最小限の「tinyfill」を使用して、サニタイズを提供しながらコードが破損しないようにすることができます。

if (!window.trustedTypes) {
  window.trustedTypes = {
    createPolicy: (name, rules) => rules,
  };
}

これにより、Trusted Types パターンを使用してコードを記述できるようになります。対応しているブラウザでは型が適用され、古いブラウザではモックポリシーを介してサニタイズ ロジックが実行されます。

ブラウザが Trusted Types をサポートしていない場合、TrustedHTML オブジェクトは単に文字列として扱われるか(tinyfill を使用している場合)、シンクレベルでの適用チェックが行われません。