XMLHttpRequest multi-origine

Les pages Web standards peuvent utiliser l'objet XMLHttpRequest pour envoyer et recevoir des données à partir de données serveurs, mais sont limités par la même règle d'origine. Les scripts de contenu lancent des requêtes pour le compte de l'origine Web dans laquelle le script de contenu a été injecté. Le contenu sont également soumis aux mêmes règles relatives à l'origine. (Les scripts de contenu sont soumis aux règles CORB depuis Chrome 73 et CORS depuis Chrome 83). Les origines des extensions ne sont pas si limitées : un script s'exécutant sur la page d'arrière-plan ou l'onglet de premier plan d'une extension peut communiquer avec des serveurs distants son origine, tant que l'extension demande des autorisations multi-origines.

Origine de l'extension

Chaque extension en cours d'exécution existe dans sa propre origine de sécurité. Sans demander de ressources supplémentaires, l'extension peut utiliser XMLHttpRequest pour obtenir des ressources dans son installation. Pour Par exemple, si une extension contient un fichier de configuration JSON appelé config.json, dans une config_resources, l'extension peut récupérer le contenu du fichier comme suit:

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

Si l'extension tente d'utiliser une origine de sécurité autre que la sienne, par exemple https://www.google.com, le navigateur l'interdit, sauf si l'extension a demandé l'origine multi-origine appropriée autorisations.

Demander des autorisations multi-origines

En ajoutant des hôtes ou des modèles de correspondance d'hôte (ou les deux) à la section autorisations de la manifest, l'extension peut demander l'accès à des serveurs distants en dehors de son origine.

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

Les valeurs d'autorisation multi-origine peuvent être des noms d'hôte complets, comme ceux-ci:

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

Ou il peut s'agir de modèles de correspondance, comme ceux-ci:

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

Le format de correspondance "https://*/" autorise l'accès HTTPS à tous les domaines accessibles. Notez qu'ici, les valeurs sont semblables aux modèles de correspondance du script de contenu, à la différence près que toute information de chemin d'accès située après le est ignoré.

Notez également que l'accès est accordé à la fois par l'hôte et par schéma. Si une extension souhaite à la fois l'accès HTTP non sécurisé à un hôte donné ou à un ensemble d'hôtes donné, il doit déclarer les autorisations séparément:

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

Considérations de sécurité

Éviter les failles de script intersites

Lorsque vous utilisez des ressources récupérées via XMLHttpRequest, votre page en arrière-plan doit veiller à ne pas sont victimes des scripts intersites. Plus précisément, évitez d'utiliser des API dangereuses telles que ci-dessous:

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();

Optez plutôt pour des API plus sûres qui n'exécutent pas de scripts:

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();

Limiter l'accès au script de contenu aux requêtes d'origine croisée

Lorsque vous exécutez des requêtes d'origines multiples au nom d'un script de contenu, veillez à vous prémunir contre pages Web malveillantes susceptibles d'essayer d'usurper l'identité d'un script de contenu. Vous ne devez pas, en particulier, scripts de contenu pour demander une URL arbitraire.

Prenons l'exemple d'une extension qui effectue une requête multi-origine pour permettre à un script de contenu découvrir le prix d'un article. Une approche (non sécurisée) consisterait à faire en sorte que le script de contenu spécifie la ressource exacte à extraire par la page d'arrière-plan.

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()));

Dans l'approche ci-dessus, le script de contenu peut demander à l'extension d'extraire n'importe quelle URL qu'elle a accès. Une page Web malveillante peut falsifier de tels messages et tromper l'extension pour qu'elle en donnant accès à des ressources multi-origines.

Concevez plutôt des gestionnaires de messages qui limitent les ressources pouvant être récupérées. Ci-dessous, seuls les itemId est fourni par le script de contenu, et non par l'URL complète.

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 => ...);

Préférer HTTPS au HTTP

En outre, faites particulièrement attention aux ressources récupérées via HTTP. Si votre extension est utilisée sur un réseau hostile, un pirate informatique (également appelé "man-in-the-middle") pourrait modifier la réponse et potentiellement attaquer votre extension. Dans la mesure du possible, privilégiez plutôt HTTPS.

Ajuster la stratégie Content Security Policy

Si vous modifiez le Content Security Policy par défaut pour les applications ou les extensions en ajoutant un content_security_policy à votre fichier manifeste, vous devez vous assurer que tous les hôtes auxquels que vous souhaitez connecter sont autorisés. Bien que la stratégie par défaut ne limite pas les connexions aux hôtes, soyez prudent lorsque vous ajoutez explicitement les directives connect-src ou default-src.