Trusted Types で DOM ベースのクロスサイト スクリプティングの脆弱性を防止

Krzysztof Kotowicz
Krzysztof Kotowicz

対応ブラウザ

  • Chrome: 83。
  • Edge: 83.
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

DOM ベースのクロスサイト スクリプティング(DOM XSS)は、ユーザーが制御するソース(ユーザー名や URL フラグメントから取得したリダイレクト URL など)のデータがシンクに到達すると発生します。シンクとは、任意の JavaScript コードを実行できる eval() などの関数や .innerHTML などのプロパティ セッターです。

DOM XSS は最も一般的なウェブ セキュリティの脆弱性の 1 つであり、デベロッパー チームがアプリに誤って導入することがよくあります。Trusted Types は、危険なウェブ API 関数をデフォルトで安全にすることで、アプリケーションの作成、セキュリティ審査、DOM XSS の脆弱性の防止を行うためのツールを提供します。Trusted Types は、まだサポートしていないブラウザ向けの ポリフィルとして使用できます。

背景

長年にわたり、DOM XSS は、最も一般的な危険なウェブ セキュリティの脆弱性の一つでした。

クロスサイト スクリプティングには 2 種類があります。一部の XSS の脆弱性は、ウェブサイトを構成する HTML コードを安全に作成しないサーバーサイド コードによって発生します。その他の問題の根本原因はクライアントにあり、JavaScript コードがユーザーが制御するコンテンツで危険な関数を呼び出しています。

サーバーサイド XSS を防ぐには、文字列を連結して HTML を生成しないでください。代わりに、安全なコンテキスト ベースの自動エスケープ テンプレート ライブラリと、ノンスベースのコンテンツ セキュリティ ポリシーを使用して、バグをさらに軽減します。

ブラウザは、Trusted Types を使用して、クライアントサイドの DOM ベースの XSS を防ぐこともできます。

API の概要

信頼できる型は、次のリスクの高いシンク関数をロックダウンすることで機能します。ブラウザ ベンダーとウェブ フレームワークは、セキュリティ上の理由から、すでにこれらの機能の使用を推奨していません。そのため、すでにご存じの機能もあるかもしれません。

信頼できる型では、これらのシンク関数に渡す前にデータを処理する必要があります。文字列のみを使用すると、ブラウザはデータが信頼できるかどうかを認識できないため、失敗します。

すべきでないこと
anElement.innerHTML  = location.href;
Trusted Types が有効になっている場合、ブラウザは TypeError をスローし、文字列で DOM XSS シンクを使用できなくなります。

データが安全に処理されたことを示すために、特別なオブジェクト(Trusted Type)を作成します。

すべきこと
anElement.innerHTML = aTrustedHTML;
  
信頼できる型が有効になっている場合、ブラウザは HTML スニペットを想定するシンクに対して TrustedHTML オブジェクトを受け入れます。他の機密性の高いシンクには、TrustedScript オブジェクトと TrustedScriptURL オブジェクトもあります。

Trusted Types を使用すると、アプリの DOM XSS 攻撃対象領域を大幅に減らすことができます。これにより、セキュリティ レビューが簡素化され、ランタイム時にブラウザでコードのコンパイル、リンティング、バンドル時に実行される型ベースのセキュリティ チェックを適用できます。

信頼できる型の使用方法

コンテンツ セキュリティ ポリシー違反レポートに備える

オープンソースの reporting-api-processorgo-csp-collector などのレポート コレクタをデプロイするか、同等の商用製品を使用できます。ReportingObserver を使用して、ブラウザにカスタム ロギングとデバッグ違反を追加することもできます。

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

または、イベント リスナーを追加します。

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

レポート専用の CSP ヘッダーを追加する

信頼できる型に移行するドキュメントに、次の HTTP レスポンス ヘッダーを追加します。

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 Type 違反を特定する

今後、信頼できる型が違反を検出するたびに、ブラウザは構成された 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 で、<img src=x で始まる文字列を使用して innerHTML が呼び出されたことを意味します。この情報は、DOM XSS を導入している可能性があり、変更が必要なコードの部分を絞り込むのに役立ちます。

違反を修正する

信頼できる型の違反を修正するには、いくつかの方法があります。問題のコードを削除するライブラリを使用するTrusted Types ポリシーを作成する、または最後の手段としてデフォルト ポリシーを作成することができます。

問題のコードを書換える

準拠していないコードが不要になったか、違反の原因となる関数なしで書き換えることができる可能性があります。

すべきこと
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
すべきでないこと
el.innerHTML = '<img src=xyz.jpg>';

ライブラリを使用する

一部のライブラリでは、シンク関数に渡すことができる Trusted Types がすでに生成されています。たとえば、DOMPurify を使用して HTML スニペットをサニタイズし、XSS ペイロードを削除できます。

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify は Trusted Types をサポートしており、ブラウザで違反が発生しないように、TrustedHTML オブジェクトにラップされたサニタイズされた HTML を返します。

信頼できるタイプのポリシーを作成する

違反の原因となっているコードを削除できない場合や、値をサニタイズして信頼できる型を作成するライブラリがない場合があります。このような場合は、信頼できるタイプのオブジェクトを自分で作成できます。

まず、ポリシーを作成します。ポリシーは、入力に特定のセキュリティ ルールを適用する Trusted Types のファクトリです。

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

このコードは、createHTML() 関数を使用して TrustedHTML オブジェクトを生成できる myEscapePolicy というポリシーを作成します。定義されたルールは、< 文字を HTML エスケープして、新しい HTML 要素の作成を防ぎます。

このポリシーは次のように使用します。

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;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 という名前のポリシーは、信頼できる型のみを受け入れることができるシンクで文字列が使用される場所で使用されます。

コンテンツ セキュリティ ポリシーの適用に切り替える

アプリで違反が生成されなくなったら、信頼できる型の適用を開始できます。

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

ウェブ アプリケーションがどれほど複雑であっても、DOM XSS の脆弱性を導入する可能性があるのは、ポリシーのコードのみです。ポリシーの作成を制限することで、さらにロックダウンできます。

関連情報