XMLHttpRequest de origem cruzada

As páginas da Web comuns podem usar o objeto XMLHttpRequest para enviar e receber dados de servidores remotos, mas são limitadas pela política de mesma origem. Os scripts de conteúdo iniciam solicitações em nome da origem da Web em que foram injetados. Portanto, eles também estão sujeitos à política de mesma origem. (Os scripts de conteúdo estão sujeitos ao CORB desde o Chrome 73 e ao CORS desde o Chrome 83.) As origens de extensão não são tão limitadas. Um script executado na página em segundo plano ou na guia em primeiro plano de uma extensão pode se comunicar com servidores remotos fora da origem, desde que a extensão solicite permissões de origem cruzada.

Origem da extensão

Cada extensão em execução existe em uma origem de segurança separada. Sem solicitar privilégios adicionais, a extensão pode usar XMLHttpRequest para receber recursos na instalação. Por exemplo, se uma extensão tiver um arquivo de configuração JSON chamado config.json em uma pasta config_resources, ela poderá recuperar o conteúdo do arquivo assim:

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

Se a extensão tentar usar uma origem de segurança diferente dela, como https://www.google.com, o navegador não vai permitir, a menos que a extensão tenha solicitado as permissões de origem cruzada adequadas.

Solicitar permissões entre origens

Ao adicionar hosts ou padrões de correspondência de host (ou ambos) à seção permissões do arquivo manifest, a extensão pode solicitar acesso a servidores remotos fora da origem dela.

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

Os valores de permissão de origem cruzada podem ser nomes de host totalmente qualificados, como estes:

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

Ou podem ser padrões de correspondência, como estes:

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

Um padrão de correspondência "https://*/" permite o acesso HTTPS a todos os domínios acessíveis. Observe que, aqui, os padrões de correspondência são semelhantes aos padrões de correspondência de script de conteúdo, mas as informações de caminho após o host são ignoradas.

O acesso é concedido por host e por esquema. Se uma extensão quiser acesso HTTP seguro e não seguro a um determinado host ou conjunto de hosts, ela precisará declarar as permissões separadamente:

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

Considerações sobre segurança

Como evitar vulnerabilidades de scripting em vários sites

Ao usar recursos recuperados via XMLHttpRequest, a página em segundo plano precisa ter cuidado para não ser vítima de scripting em vários sites. Especificamente, evite usar APIs perigosas, como as seguintes:

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

Em vez disso, prefira APIs mais seguras que não executam 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();

Como limitar o acesso do script de conteúdo a solicitações entre origens

Ao fazer solicitações de origem cruzada em nome de um script de conteúdo, tome cuidado para se proteger contra páginas da Web maliciosas que podem tentar se passar por um script de conteúdo. Em particular, não permita que scripts de conteúdo solicitem um URL arbitrário.

Considere um exemplo em que uma extensão faz uma solicitação entre origens para permitir que um script de conteúdo descubra o preço de um item. Uma abordagem (insegura) seria fazer com que o script de conteúdo especificasse o recurso exato a ser buscado pela página em segundo plano.

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

Na abordagem acima, o script de conteúdo pode pedir à extensão para buscar qualquer URL a que ela tenha acesso. Uma página da Web maliciosa pode forjar essas mensagens e enganar a extensão para dar acesso a recursos de origem cruzada.

Em vez disso, crie processadores de mensagens que limitem os recursos que podem ser buscados. Abaixo, apenas o itemId é fornecido pelo script de conteúdo, e não o URL completo.

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

Preferir HTTPS em vez de HTTP

Além disso, tenha cuidado especial com os recursos recuperados via HTTP. Se a extensão for usada em uma rede hostil, um invasor de rede (também conhecido como "man-in-the-middle") poderá modificar a resposta e, possivelmente, atacar a extensão. Em vez disso, prefira HTTPS sempre que possível.

Ajustar a Política de Segurança de Conteúdo

Se você modificar a Política de Segurança de Conteúdo padrão para apps ou extensões adicionando um atributo content_security_policy ao manifesto, verifique se os hosts a que você quer se conectar estão permitidos. Embora a política padrão não restrinja as conexões a hosts, tenha cuidado ao adicionar explicitamente as diretivas connect-src ou default-src.