Isolamento de sites para desenvolvedores da Web

O Chrome 67 no computador tem um novo recurso chamado Isolamento de sites ativado por padrão. Este artigo explica o que é o isolamento de sites, por que é necessário e por que os desenvolvedores da Web precisam estar cientes desse recurso.

O que é o isolamento de sites?

A Internet é para assistir vídeos de gatos e gerenciar carteiras de criptomoedas, entre outras coisas. Mas você não quer que fluffycats.example tenha acesso às suas criptomoedas preciosas. Felizmente, os sites normalmente não podem acessar os dados uns dos outros dentro do navegador, graças à política de mesma origem. Ainda assim, sites maliciosos podem tentar burlar essa política para atacar outros sites e, ocasionalmente, bugs de segurança são encontrados no código do navegador que aplica a política de mesma origem. A equipe do Chrome pretende corrigir esses bugs o mais rápido possível.

O isolamento de sites é um recurso de segurança do Chrome que oferece uma linha adicional de defesa para diminuir a probabilidade de esses ataques terem êxito. Ele garante que páginas de sites diferentes sejam sempre colocadas em processos distintos, cada um executado em um sandbox que limita o que o processo pode fazer. Ele também impede que o processo receba determinados tipos de dados sensíveis de outros sites. Como resultado, com o isolamento de sites é muito mais difícil que um site malicioso use ataques especulativos de canal lateral, como o Spectre, para roubar dados de outros sites. À medida que a equipe do Chrome concluir outras restrições, o isolamento de sites também será útil mesmo quando a página de um invasor puder violar algumas das regras no próprio processo.

O isolamento de sites dificulta o acesso ou roubo de informações das suas contas em outros sites por sites não confiáveis. Ele oferece proteção adicional contra vários tipos de bugs de segurança, como os recentes ataques de canal lateral Meltdown e Spectre.

Para mais detalhes sobre o isolamento de sites, consulte nosso artigo no blog de segurança do Google.

Bloqueio de leitura de origem cruzada

Mesmo quando todas as páginas entre sites são colocadas em processos separados, as páginas ainda podem solicitar alguns sub-recursos entre sites, como imagens e JavaScript. Uma página da Web maliciosa pode usar um elemento <img> para carregar um arquivo JSON com dados sensíveis, como seu saldo bancário:

<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->

Sem o isolamento de sites, o conteúdo do arquivo JSON chega à memória do processo do renderizador. Nesse momento, o renderizador percebe que não é um formato de imagem válido e não renderiza uma imagem. No entanto, o invasor pode explorar uma vulnerabilidade como o Spectre para ler esse bloco de memória.

Em vez de usar <img>, o invasor também pode usar <script> para enviar os dados sensíveis à memória:

<script src="https://your-bank.example/balance.json"></script>

O bloqueio de leitura de origem cruzada, ou CORB, é um novo recurso de segurança que impede que o conteúdo de balance.json entre na memória da memória do processo do renderizador com base no tipo MIME.

Vamos entender como o CORB funciona. Um site pode solicitar dois tipos de recursos de um servidor:

  1. recursos de dados, como documentos HTML, XML ou JSON.
  2. recursos de mídia, como imagens, JavaScript, CSS ou fontes

Um site é capaz de receber recursos de dados da própria origem ou de outras origens com cabeçalhos CORS permissivos, como Access-Control-Allow-Origin: *. Por outro lado, recursos de mídia podem ser incluídos de qualquer origem, mesmo sem cabeçalhos CORS permissivos.

A CORB impedirá que o processo do renderizador receba um recurso de dados de origem cruzada (ou seja, HTML, XML ou JSON) se:

  • o recurso tiver um cabeçalho X-Content-Type-Options: nosniff;
  • O CORS não permite explicitamente o acesso ao recurso

Se o recurso de dados de origem cruzada não tiver o cabeçalho X-Content-Type-Options: nosniff definido, o CORB tentará detectar o corpo da resposta para determinar se ele é HTML, XML ou JSON. Isso é necessário porque alguns servidores da Web estão configurados incorretamente e exibem imagens como text/html, por exemplo.

Os recursos de dados bloqueados pela política CORB são apresentados ao processo como vazios, mesmo que a solicitação ainda ocorra em segundo plano. Como resultado, uma página da Web mal-intencionada tem dificuldade para extrair dados entre sites para o próprio processo para roubar.

Para garantir a segurança e aproveitar o CORB, recomendamos o seguinte:

  • Marque as respostas com o cabeçalho Content-Type correto. Por exemplo, os recursos HTML precisam ser exibidos como text/html, os recursos JSON com um tipo JSON MIME e os recursos XML com um tipo XML MIME.
  • Desative a detecção usando o cabeçalho X-Content-Type-Options: nosniff. Sem esse cabeçalho, o Chrome faz uma rápida análise de conteúdo para tentar confirmar se o tipo está correto, mas como isso erra ao permitir respostas para evitar o bloqueio de itens como arquivos JavaScript, é melhor você fazer a coisa certa por conta própria.

Para mais detalhes, consulte o artigo sobre CORB para desenvolvedores da Web ou a nossa explicação detalhada sobre o CORB (em inglês).

Por que os desenvolvedores da Web devem se preocupar com o isolamento de sites?

Em sua maioria, o isolamento de sites é um recurso de segundo plano do navegador que não é exposto diretamente aos desenvolvedores da Web. Não há uma nova API exposta pela Web para aprender, por exemplo. Em geral, as páginas da Web não conseguem distinguir quando são executadas com ou sem o isolamento de sites.

No entanto, esta regra tem algumas exceções. Ativar o isolamento de sites traz alguns efeitos colaterais sutis que podem afetar seu site. Mantemos uma lista de problemas conhecidos de isolamento de sites e detalhamos os mais importantes abaixo.

O layout de página inteira não é mais síncrono

Com o isolamento de sites, não há mais garantia de que o layout de página inteira será síncrono, já que os frames de uma página agora podem ser distribuídos em vários processos. Isso pode afetar as páginas se elas presumirem que uma mudança de layout é propagada imediatamente para todos os frames da página.

Por exemplo, vamos considerar um site chamado fluffykittens.example que se comunica com um widget social hospedado em social-widget.example:

<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.width = 456;
  iframe.contentWindow.postMessage(
    // The message to send:
    'Meow!',
    // The target origin:
    'https://social-widget.example'
  );
</script>

No início, a largura de <iframe> do widget de rede social é de 123 pixels. No entanto, a página do FluffyKittens muda a largura para 456 pixels (acionando o layout) e envia uma mensagem para o widget de redes sociais, que tem o seguinte código:

<!-- https://social-widget.example/ -->
<script>
  self.onmessage = () => {
    console.log(document.documentElement.clientWidth);
  };
</script>

Sempre que o widget de rede social recebe uma mensagem pela API postMessage, ele registra a largura do elemento raiz <html>.

Qual valor de largura é registrado? Antes de o Chrome ativar o isolamento de sites, a resposta era 456. O acesso a document.documentElement.clientWidth força o layout, que antes era síncrono antes do isolamento de sites do Chrome ativado. No entanto, com o isolamento de sites ativado, o novo layout do widget de redes sociais de origem cruzada agora acontece de forma assíncrona em um processo separado. Assim, a resposta agora também pode ser 123, ou seja, o antigo valor width.

Se uma página mudar o tamanho de um <iframe> de origem cruzada e enviar um postMessage para ela, com o isolamento de sites, é possível que o frame de destino ainda não saiba o novo tamanho ao receber a mensagem. Em geral, isso pode corromper as páginas se presumir que uma mudança de layout se propaga imediatamente para todos os frames da página.

Neste exemplo específico, uma solução mais robusta define o width no frame pai e detecta essa mudança no <iframe> ouvindo um evento resize.

Os gerenciadores de descarregamento podem atingir o tempo limite com mais frequência

Quando um frame navega ou é fechado, o documento antigo e todos os documentos de subframe incorporados nele executam o gerenciador unload. Se a nova navegação acontecer no mesmo processo do renderizador (por exemplo, para uma navegação de mesma origem), os gerenciadores unload do documento antigo e dos subframes dele poderão ser executados por um longo tempo arbitrariamente antes de permitir que a nova navegação seja confirmada.

addEventListener('unload', () => {
  doSomethingThatMightTakeALongTime();
});

Nessa situação, os gerenciadores unload em todos os frames são muito confiáveis.

No entanto, mesmo sem o isolamento de sites, algumas navegações de frames principais são entre processos, o que afeta o comportamento do gerenciador de descarregamento. Por exemplo, se você navegar de old.example para new.example digitando o URL na barra de endereço, a navegação por new.example vai acontecer em um novo processo. Os gerenciadores de descarregamento de old.example e os respectivos subframes são executados no processo do old.example em segundo plano, depois que a página new.example é mostrada, e os gerenciadores de descarregamento antigos são encerrados caso não terminem dentro de um determinado tempo limite. Como os gerenciadores de descarregamento podem não terminar antes do tempo limite, o comportamento de descarregamento é menos confiável.

Com o isolamento de sites, todas as navegações entre sites se tornam processos cruzados, de modo que documentos de sites diferentes não compartilhem um processo entre si. Como resultado, a situação acima se aplica a mais casos, e os gerenciadores de descarregamento em <iframe>s geralmente têm os comportamentos de segundo plano e tempo limite descritos acima.

Outra diferença resultante do isolamento de sites é a nova ordem paralela de gerenciadores de descarregamento: sem o isolamento de sites, eles são executados em uma ordem estrita de cima para baixo nos frames. No entanto, com o isolamento de sites, os gerenciadores de descarregamento são executados em paralelo em diferentes processos.

Essas são consequências fundamentais da ativação do isolamento de sites. A equipe do Chrome está trabalhando para melhorar a confiabilidade dos gerenciadores de descarregamento para casos de uso comuns, quando possível. Também estamos cientes de bugs em que os gerenciadores de descarregamento de subframes ainda não conseguem utilizar determinados recursos e estão trabalhando para resolvê-los.

Um caso importante para gerenciadores de descarregamento é enviar pings de fim de sessão. Isso geralmente é feito da seguinte maneira:

addEventListener('pagehide', () => {
  const image = new Image();
  img.src = '/end-of-session';
});

Uma abordagem melhor e mais robusta diante dessa mudança é usar navigator.sendBeacon:

addEventListener('pagehide', () => {
  navigator.sendBeacon('/end-of-session');
});

Se você precisar de mais controle sobre a solicitação, use a opção keepalive da API Fetch:

addEventListener('pagehide', () => {
  fetch('/end-of-session', {keepalive: true});
});

Conclusão

Com o isolamento de sites, fica mais difícil para sites não confiáveis acessarem ou roubarem informações das suas contas em outros sites. Como parte disso, o CORB tenta manter recursos de dados sensíveis fora do processo do renderizador. Nossas recomendações acima garantem que você aproveite ao máximo esses novos recursos de segurança.

Agradecemos a Alex Moshchuk, Charlie Reis, Jason Miller, Nasko Oskov, Philip Walton, Shubhie Panicker e Thomas Steiner por lerem uma versão de rascunho deste artigo e fornecerem seus comentários.