XMLHttpRequest de origem cruzada

Páginas da Web comuns podem usar o objeto XMLHttpRequest para enviar e receber dados de dispositivos remotos mas são limitados pela mesma política de origem. Scripts de conteúdo iniciam solicitações. em nome da origem da Web na qual o script de conteúdo foi injetado e, portanto, o conteúdo os scripts também estão sujeitos à mesma política de origem. (Os scripts de conteúdo estão sujeitos à CORB desde o Chrome 73 e o CORS desde o Chrome 83. As origens da extensão não são tão limitadas: um script a execução na página de segundo plano ou em primeiro plano de uma extensão pode se comunicar com servidores remotos fora da a origem, desde que a extensão solicite permissões de origem cruzada.

Origem da extensão

Cada extensão em execução existe na própria origem de segurança separada. Sem solicitar privilégios, a extensão pode usar XMLHttpRequest para obter recursos em sua instalação. Para exemplo, se uma extensão contiver um arquivo de configuração JSON chamado config.json, em um config_resources, a extensão pode recuperar o conteúdo do arquivo da seguinte forma:

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 que não seja ela mesma, por exemplo, https://www.google.com, o navegador não permitir a permissão, a menos que a extensão tenha solicitado a solicitação de URL permissões.

Como solicitar permissões de origem cruzada

Adicionando hosts ou padrões de correspondência de host (ou ambos) à seção de permissões da manifest, a extensão poderá solicitar acesso a servidores remotos fora de sua origem.

{
  "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 de "https://*/" permite o acesso HTTPS a todos os domínios acessíveis. Observe que, aqui, correspondem são semelhantes aos padrões de correspondência do script de conteúdo, mas todas as informações de caminho que seguem o host será ignorado.

Observe também que o acesso é concedido pelo host e pelo esquema. Se uma extensão quiser arquivos acesso HTTP não seguro a um determinado host ou conjunto de hosts, ele deve declarar as permissões separadamente:

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

Considerações sobre segurança

Evitar vulnerabilidades de scripting em vários sites

Ao usar recursos recuperados por XMLHttpRequest, sua página de segundo plano deve ter cuidado para não são vítimas do scripting em vários sites (em inglês). Especificamente, evite usar APIs perigosas, abaixo:

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, dê preferência a APIs mais seguras que não executem 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();

Limitar o acesso ao script de conteúdo a solicitações entre origens

Ao realizar solicitações de origem cruzada em nome de um script de conteúdo, tenha cuidado para se proteger contra páginas da Web maliciosas que podem tentar falsificar um script de conteúdo. Especificamente, não permita scripts de conteúdo para solicitar um URL arbitrário.

Considere um exemplo em que uma extensão realiza uma solicitação de origem cruzada para permitir que um script de conteúdo descobrir o preço de um item. Uma abordagem (insegura) seria fazer com que o script de conteúdo especifique 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 que a extensão tem acesso. Uma página da web maliciosa pode ser capaz de forjar essas mensagens e enganar a extensão para que dá acesso a recursos de várias origens.

Em vez disso, projete gerenciadores de mensagens que limitem os recursos que podem ser buscados. Abaixo, apenas as itemId é fornecido pelo script de conteúdo, e não pelo 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 recursos recuperados via HTTP. Se sua extensão for usada em um hostil, um invasor (também conhecido como "man-in-the-middle") poderia modificar a resposta e possivelmente atacar a extensão. Em vez disso, prefira HTTPS sempre que possível.

Ajuste da 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 content_security_policy ao seu manifesto, será preciso garantir que os hosts aos quais aos quais você quer se conectar tenham. Embora a política padrão não restrinja conexões a hosts, tenha cuidado ao adicionar explicitamente as diretivas connect-src ou default-src.