クロスオリジンの XMLHttpRequest

通常のウェブページでは、XMLHttpRequest オブジェクトを使用してリモート サーバーからデータを送受信できますが、同一生成元ポリシーによって制限されます。コンテンツ スクリプトは、コンテンツ スクリプトが挿入されたウェブオリジンに代わってリクエストを開始するため、コンテンツ スクリプトには同一生成元ポリシーも適用されます。(コンテンツ スクリプトは、Chrome 73 以降の CORB と Chrome 83 以降の CORS の対象となっています)。拡張機能のオリジンにはそれほど制限はありません。拡張機能のバックグラウンド ページまたはフォアグラウンド タブで実行しているスクリプトは、拡張機能がクロスオリジンの権限をリクエストしている限り、オリジンの外部にあるリモート サーバーと通信できます。

拡張機能の作成元

実行中の拡張機能はそれぞれ、個別のセキュリティ オリジン内に存在します。この拡張機能は、追加の権限をリクエストしなくても、XMLHttpRequest を使用してインストール内のリソースを取得できます。たとえば、拡張機能の config_resources フォルダに config.json という JSON 構成ファイルが含まれている場合、拡張機能は次のようにファイルの内容を取得できます。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

拡張機能がそれ自体以外のセキュリティ オリジン(https://www.google.com など)を使用しようとすると、拡張機能が適切なクロスオリジン権限をリクエストしない限り、ブラウザはセキュリティ オリジンを禁止します。

クロスオリジン権限のリクエスト

拡張機能は、マニフェスト ファイルの権限セクションにホストまたはホストの一致パターン(あるいはその両方)を追加することで、送信元のリモート サーバーへのアクセスをリクエストできます。

{
  "name": "My extension",
  ...
  "permissions": [
    "https://www.google.com/"
  ],
  ...
}

クロスオリジンの権限の値には、次のように完全修飾されたホスト名を指定できます。

  • "https://www.google.com/"
  • "https://www.gmail.com/"

または、次のような一致パターンにすることもできます。

  • "https://*.google.com/"
  • "https://*/"

一致パターンを「https://*/」にすると、すべての到達可能なドメインへの HTTPS アクセスが許可されます。ここで、一致パターンはコンテンツ スクリプトの一致パターンに似ていますが、ホストの後に続くパス情報は無視されます。

また、アクセス権はホストとスキームの両方で付与されています。特定のホストまたはホストのセットへの安全な HTTP アクセスとセキュアでない HTTP アクセスの両方を必要とする拡張機能では、権限を個別に宣言する必要があります。

"permissions": [
  "http://www.google.com/",
  "https://www.google.com/"
]

セキュリティ上の考慮事項

クロスサイト スクリプティングの脆弱性の回避

XMLHttpRequest で取得したリソースを使用する場合、バックグラウンド ページがクロスサイト スクリプティングの被害に遭わないように注意する必要があります。特に、以下のような危険な API は使用しないでください。

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

代わりに、スクリプトを実行しない、より安全な API の使用をおすすめします。

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText does not let the attacker inject HTML elements.
    document.getElementById("resp").innerText = xhr.responseText;
  }
}
xhr.send();

コンテンツ スクリプトによるクロスオリジン リクエストへのアクセスを制限する

コンテンツ スクリプトに代わってクロスオリジン リクエストを実行する場合は、コンテンツ スクリプトになりすますことを試みる悪意のあるウェブページに対する保護に注意してください。特に、コンテンツ スクリプトが任意の URL をリクエストできるようにしないでください。

拡張機能がクロスオリジン リクエストを実行して、コンテンツ スクリプトにアイテムの価格を検出させる例について考えてみましょう。(安全でない)アプローチの一つとして、バックグラウンド ページによって取得される正確なリソースをコンテンツ スクリプトに指定させるというものがあります。

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'fetchUrl') {
        // WARNING: SECURITY PROBLEM - a malicious web page may abuse
        // the message handler to get access to arbitrary cross-origin
        // resources.
        fetch(request.url)
            .then(response => response.text())
            .then(text => sendResponse(text))
            .catch(error => ...)
        return true;  // Will respond asynchronously.
      }
    });
chrome.runtime.sendMessage(
    {contentScriptQuery: 'fetchUrl',
     url: 'https://another-site.com/price-query?itemId=' +
              encodeURIComponent(request.itemId)},
    response => parsePrice(response.text()));

上述のアプローチでは、コンテンツ スクリプトは拡張機能に、拡張機能がアクセスできる URL を取得するよう要求できます。悪意のあるウェブページが、このようなメッセージを偽って拡張機能をだましてクロスオリジン リソースにアクセスさせる可能性があります。

代わりに、取得可能なリソースを制限するメッセージ ハンドラを設計します。以下の例では、完全な URL ではなく、コンテンツ スクリプトから itemId のみが提供されます。

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.contentScriptQuery == 'queryPrice') {
        var url = 'https://another-site.com/price-query?itemId=' +
            encodeURIComponent(request.itemId);
        fetch(url)
            .then(response => response.text())
            .then(text => parsePrice(text))
            .then(price => sendResponse(price))
            .catch(error => ...)
        return true;  // Will respond asynchronously.
      }
    });
chrome.runtime.sendMessage(
    {contentScriptQuery: 'queryPrice', itemId: 12345},
    price => ...);

HTTP より HTTPS を優先する

また、HTTP 経由で取得するリソースには特に注意してください。拡張機能が悪意のあるネットワークで使用されている場合、ネットワーク攻撃者(中間者"man-in-the-middle")がレスポンスを改ざんし、場合によっては拡張機能を攻撃するおそれがあります。可能であれば、HTTPS を使用することをおすすめします。

コンテンツ セキュリティ ポリシーの調整

マニフェストに content_security_policy 属性を追加して、アプリや拡張機能のデフォルトのコンテンツ セキュリティ ポリシーを変更する場合は、接続するホストがすべて許可されるようにする必要があります。デフォルト ポリシーではホストへの接続は制限されませんが、connect-src ディレクティブまたは default-src ディレクティブを明示的に追加する場合は注意が必要です。