通常のウェブページでは、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 をリクエストすることを許可しないでください。
拡張機能がクロスオリジン リクエストを実行して、コンテンツ スクリプトがアイテムの価格を検出できるようにする例を考えてみましょう。安全ではない方法の 1 つは、コンテンツ スクリプトでバックグラウンド ページで取得するリソースを正確に指定することです。
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 を取得するよう拡張機能にリクエストできます。悪意のあるウェブページがこのようなメッセージを偽造し、拡張機能にクロスオリジン リソースへのアクセスを許可させる可能性があります。
代わりに、取得できるリソースを制限するメッセージ ハンドラを設計します。以下では、コンテンツ スクリプトによって itemId のみが提供され、完全な URL は提供されません。
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 ディレクティブを明示的に追加する場合は注意が必要です。