Solicitações de rede de origem cruzada

Páginas da Web comuns podem usar as APIs fetch() ou XMLHttpRequest para enviar e receber dados de servidores remotos, mas elas 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 o script de conteúdo foi injetado. Portanto, os scripts de conteúdo também estão sujeitos à política de mesma origem. As origens de extensão não são tão limitadas. Um script executado em um service worker de extensão ou em uma guia em primeiro plano pode se comunicar com servidores remotos fora da origem, desde que a extensão solicite permissões entre origens.

Origem da extensão

Cada extensão em execução existe na própria origem de segurança. Sem solicitar outros privilégios, a extensão pode chamar fetch() para receber recursos na instalação. Por exemplo, se uma extensão contiver um arquivo de configuração JSON chamado config.json, em uma pasta config_resources/, a extensão poderá recuperar o conteúdo do arquivo desta forma:

const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();

Se a extensão tentar usar uma origem de segurança diferente dela mesma, por exemplo, https://www.google.com, o navegador não permitirá, a menos que a extensão tenha solicitado as permissões apropriadas entre origens.

Solicitar permissões de origem cruzada

Para solicitar acesso a servidores remotos fora da origem de uma extensão, adicione hosts, padrões de correspondência ou ambos à seção host_permissions do arquivo manifest.

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

Os valores de permissão entre origens 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. Aqui, os padrões de correspondência são semelhantes aos padrões de correspondência do script de conteúdo, mas todas as informações de caminho após o host são ignoradas.

Observe também que o acesso é concedido pelo host e pelo 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:

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

Fetch() x XMLHttpRequest()

O fetch() foi criado especificamente para service workers e segue uma tendência mais ampla da Web, afastando-se das operações síncronas. A API XMLHttpRequest() tem suporte em extensões fora do service worker, e chamá-la aciona o gerenciador de busca do service worker de extensão. O novo trabalho deve favorecer o fetch() sempre que possível.

Considerações sobre segurança

Evitar vulnerabilidades de script entre sites

Ao usar recursos recuperados por fetch(), seu documento fora da tela, painel lateral ou pop-up precisa ter cuidado para não ser vítima de cross-site scripting. Específicamente, evite o uso de APIs perigosas, como innerHTML. Exemplo:

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
    ...

Em vez disso, dê preferência a APIs mais seguras que não executem scripts:

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);

const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;

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

Ao realizar 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 falsificar 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 realiza uma solicitação de origem cruzada para permitir que um script de conteúdo descubra o preço de um item. Uma abordagem não tão segura 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 solicitar que a extensão busque qualquer URL a que a extensão tenha acesso. Uma página da Web maliciosa pode forjar essas mensagens e enganar a extensão para conceder acesso a recursos de origem cruzada.

Em vez disso, projete manipuladores 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') {
      const 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 => ...
);

Dar preferência ao uso de HTTPS em vez de HTTP

Além disso, tenha cuidado com os recursos recuperados por 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, potencialmente, atacar a extensão. Em vez disso, prefira o 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 da sua extensão adicionando um atributo content_security_policy ao manifesto, será necessário garantir que todos os hosts aos quais você quer se conectar sejam 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.