Solicitações de rede de origem cruzada

As páginas da Web normais 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 foram injetados e, 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 em execução em um service worker de extensão ou guia em primeiro plano pode se comunicar com servidores remotos fora de sua origem, desde que a extensão solicite permissões de host.

Origem da extensão

Cada extensão em execução existe na própria origem de segurança separada. Sem solicitar privilégios extras, a extensão pode chamar fetch() para receber recursos na instalação. Por exemplo, se uma extensão contém um arquivo de configuração JSON chamado config.json em uma pasta config_resources/, ela pode 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 solicitar conteúdo de uma origem de segurança diferente da própria, por exemplo, https://www.google.com, isso será tratado como uma solicitação entre origens, a menos que a extensão tenha permissões de host. As solicitações de origem cruzada são sempre tratadas como tal em scripts de conteúdo, mesmo que a extensão tenha permissões de host.

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 de manifesto.

{
  "name": "My extension",
  ...
  "host_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, os padrões de correspondência são semelhantes aos padrões de correspondência de script de conteúdo, mas qualquer informação de caminho após o host é ignorada.

O acesso também é 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:

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

Fetch() x XMLHttpRequest()

fetch() foi criado especificamente para service workers e segue uma tendência mais ampla da Web de operações síncronas. A API XMLHttpRequest() é compatível com extensões fora do service worker, e a chamada dela aciona o handler de busca do service worker da extensão. Novos trabalhos devem favorecer fetch() sempre que possível.

Considerações sobre segurança

Evitar vulnerabilidades de scripting em vários sites

Ao usar recursos recuperados via fetch(), o documento fora da tela, o painel lateral ou o pop-up precisam ter cuidado para não serem vítimas de scripting em vários sites. Especificamente, evite usar APIs perigosas, como innerHTML. Por 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, prefira APIs mais seguras que não executam 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 de scripts de conteúdo a solicitações de origem cruzada

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 possam 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 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 pedir à extensão para buscar qualquer URL a que ela tenha acesso. Uma página da Web maliciosa pode falsificar essas mensagens e enganar a extensão para conceder acesso a recursos de origem cruzada.

Em vez disso, crie handlers 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 => ...
);

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 da extensão adicionando um content_security_policy atributo ao manifesto, será necessário garantir que todos os hosts a que você quer se conectar sejam permitidos. 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.