生成元横断XMLHttpRequest

通常のウェブページは XMLHttpRequest オブジェクトを使ってリモートホストとの通信を行う。しかしそこには 同一生成元ポリシー という制限が存在する。拡張機能では、そのような制限は設けていない。拡張機能は最初に生成元横断のパーミッションを要求しておくことで、異なる生成元のサーバとも通信することが出来る。

拡張機能の生成元

実行されている各拡張機能は、それぞれ分割された生成元に存在している。特別に特権を要求しなくても、XMLHttpRequestを使って自身のインストールフォルダからのリソース取得を行うことが出来る。例えば、拡張機能が config.json というJSONで記述された設定ファイルを config_resources というフォルダに持っている場合、次のように取得することが出来る。

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

もし拡張機能が自分自身ではない生成元、例えば http://www.google.com などを使おうとすれば、事前に適切な生成元横断のパーミッションが要求されていない限り、ブラウザはそれを許可しない。

生成元横断パーミッションの要求

Manifest Fileの permissions セクションにホスト名やホスト名のマッチパターンを記述することで、拡張機能は生成元を超えてリモートサーバにアクセス要求を送ることが可能になる。

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

生成元横断パーミッションの値には、次のように正確なホスト名を指定することが出来る。

また、次のようにマッチパターンによる指定も可能。

例にある “http://*/” というパターンは、つまり全てのドメインへHTTPでのアクセスを許可するという意味になる。注意が必要なのは、マッチパターンはコンテント・スクリプトのマッチパターンとほぼ同じ記述であるが、ホストより下のパス部分は無視されるということである。

もうひとつの注意点として、アクセスはホストとスキームの両方で確認されるということ。もし拡張機能が http および https 両方でのアクセスを行いたければ、それぞれパーミッションを記述すなければならない。

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

セキュリティへの配慮

XMLHttpRequestにより取得したリソースを使う場合、バックグラウンド・ページが XSS の被害にあわないよう注意する必要がある。特に次に示すような危険なAPIは仕様を避けるようにすべき。

JSONのパースにevalを使う

/* background */
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.example.com/data.json", true);
xhr.send();
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // 危険! 悪意のあるスクリプトが実行されてしまうかもしれない
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}

evalはコードを実行してしまうので、JSONのパースは次のようにすべき。

/* background */
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.example.com/data.json", true);
xhr.send();
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse であれば、スクリプトは実行されない
    var resp = JSON.parse(xhr.responseText);
  }
}

innerHTMLにチェック無しに渡す

/* background */
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.example.com/data.json", true);
xhr.send();
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // 危険! 悪意あるスクリプトが実行されるかもしれない
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}

HTMLとしてではなく、テキストとして評価させれば安全。

/* background */
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.example.com/data.json", true);
xhr.send();
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText であれば、スクリプトが含まれていても実行されない
    document.getElementById("resp").innerText = xhr.responseText;
  }
}