Jogue com segurança em IFrames com sandbox

Construir uma experiência rica na web atual de forma quase inevitável envolve e conteúdo de incorporação sobre os quais você não tem controle real. Os widgets de terceiros podem impulsionar o engajamento e desempenhar um papel crucial experiência do usuário e conteúdo gerado por ele às vezes é ainda mais importante que o conteúdo nativo de um site. Abster-se de qualquer um deles não é uma opção, mas aumentam o risco de Algo BadTM acontecer no seu site. Cada que você incorpora (cada anúncio e widget de mídia social) é um potencial vetor de ataque para pessoas com intenção maliciosa:

Política de Segurança de Conteúdo (CSP) pode mitigar os riscos associados a ambos os tipos de conteúdo, fornecendo a capacidade de colocar na lista de permissões fontes de script especificamente confiáveis e outros conteúdo. Esse é um grande passo na direção certa, mas é importante notar que a proteção que a maioria das diretivas CSP oferece é binária: o recurso é é permitido ou não. Há momentos em que seria útil dizer: "Não estou certeza de que realmente confio nessa fonte de conteúdo, mas é tããããão linda! Incorporar por favor, Navegador, mas não deixe que ele prejudique meu site."

Privilégio mínimo

Em essência, estamos procurando um mecanismo que permita conceder conteúdo que incorporar apenas o nível mínimo de capacidade necessário para fazer seu trabalho. Se um widget não precisa abrir uma nova janela, e o acesso a window.open não é permitido. prejudicados. Se o Flash não for necessário, desativar o suporte ao plug-in não deve ser uma problema. Estamos o mais seguros possível se seguirmos o princípio do mínimo privilégio e bloquear todos os recursos que não sejam diretamente relevantes para a funcionalidade que gostaríamos usar. O resultado é que não precisamos mais confiar cegamente que alguma parte de conteúdo incorporado não aproveitarão os privilégios que não deveriam usar. Ela não terão acesso à funcionalidade em primeiro lugar.

Os elementos iframe são a primeira etapa em direção a um bom framework para essa solução. Carregar algum componente não confiável em um iframe fornece uma medida de separação. entre o aplicativo e o conteúdo que você quer carregar. O conteúdo emoldurado não terá acesso ao DOM da página, aos dados armazenados localmente nem ser capaz de desenhar em posições arbitrárias na página; o escopo dele é limitado à contorno do frame. No entanto, a separação não é realmente robusta. A página contida ainda tem várias opções para comportamentos irritantes ou maliciosos: a reprodução automática vídeo, plug-ins e pop-ups são a ponta do iceberg.

O atributo sandbox do elemento iframe nos dá exatamente o que precisamos para restringir as restrições ao conteúdo em frames. Podemos instruir o navegador a carregar o conteúdo de um frame específico em um privilégio baixo permitindo apenas o subconjunto de capacidades necessárias para fazer o que for as demandas do seu trabalho.

Vire, mas verifique

"Tweet" do Twitter é um ótimo exemplo de funcionalidade que pode ser mais incorporado com segurança no seu site por meio de uma sandbox. O Twitter permite que você incorpore a usando um iframe pelo seguinte código:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

Para descobrir o que podemos bloquear, vamos examinar cuidadosamente quais recursos exigido pelo botão. O HTML que é carregado no frame executa um pouco JavaScript dos servidores do Twitter e gera um pop-up preenchido com uma para enviar tweets quando clicado. Essa interface precisa de acesso à conta do Twitter cookies para vincular o tweet à conta correta e precisa da capacidade para enviar o formulário de tweet. É basicamente isso. o frame não precisa carregar plug-ins, ele não precisa navegar pela janela de nível superior, ou qualquer e o número de outros bits de funcionalidade. Como eles não são necessários, vamos removê-los colocando o conteúdo do frame no sandbox.

O sandbox funciona com base em uma lista de permissões. Começamos removendo permissões e reativar recursos individuais, adicionando sinalizações específicas para a configuração da sandbox. Para o widget do Twitter, decidiu ativar o JavaScript, pop-ups, envio de formulários e o site twitter.com cookies. Podemos fazer isso adicionando um atributo sandbox ao iframe com o seguinte valor:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

É isso. Fornecemos à estrutura todos os recursos necessários e navegador negará acesso a qualquer um dos privilégios concedê-la explicitamente usando o valor do atributo sandbox.

Controle granular sobre as capacidades

Vimos algumas das possíveis sinalizações de sandbox no exemplo acima. Agora, analisar o funcionamento interno do atributo com um pouco mais de detalhes.

Dado um iframe com um atributo sandbox vazio, o documento enquadrado será totalmente colocado em sandbox, as seguintes restrições:

  • O JavaScript não será executado no documento com frame. Isso não inclui apenas JavaScript carregado explicitamente por meio de tags de script, mas também de manipuladores de eventos in-line e javascript: URLs. Isso também significa que o conteúdo em tags noscript será exibido, exatamente como se o usuário tivesse desativado o script.
  • O documento emoldurado é carregado em uma origem única, o que significa que todos verificações de mesma origem vão falhar. origens exclusivas não correspondem a outras origens, até eles mesmos. Entre outros impactos, isso significa que o documento não tem acesso aos dados armazenados nos cookies de qualquer origem ou em quaisquer outros mecanismos de armazenamento (armazenamento DOM, banco de dados indexado etc.).
  • O documento emoldurado não pode criar novas janelas ou caixas de diálogo (via window.open ou target="_blank", por exemplo).
  • Não é possível enviar os formulários.
  • Os plug-ins não serão carregados.
  • O documento com frame só pode navegar nele mesmo, não no arquivo pai de nível superior. Definir window.top.location vai gerar uma exceção, e clicar no link com target="_top" não terão efeito.
  • Recursos com acionamento automático (elementos de formulário com foco automático, reprodução automática vídeos etc.) estão bloqueados.
  • Não foi possível acessar o bloqueio do ponteiro.
  • O atributo seamless é ignorado na iframes que o documento com frame contém.

Isso é muito draconiano, e um documento carregado em um iframe totalmente no sandbox oferece muito pouco risco. Claro, ele também não pode ter muito valor: você usar um sandbox completo para alguns conteúdos estáticos, mas a maioria das vezes em que você vai querer relaxar um pouco as coisas.

Com exceção dos plug-ins, cada uma dessas restrições pode ser removida adicionar uma sinalização ao valor do atributo do sandbox. Os documentos no modo sandbox nunca podem executar plug-ins, pois eles são códigos nativos fora do sandbox, mas todo o resto é justo jogo:

  • allow-forms permite o envio de formulário.
  • allow-popups permite pop-ups (chocar!).
  • allow-pointer-lock permite (surpresa!) o bloqueio de ponteiro.
  • allow-same-origin permite que o documento mantenha a origem; páginas carregadas de https://example.com/ vai manter o acesso aos dados dessa origem.
  • O allow-scripts permite a execução de JavaScript e também permite que recursos para serão acionados automaticamente (porque seria simples implementar via JavaScript).
  • allow-top-navigation permite que o documento seja removido do frame ao ao navegar pela janela de nível superior.

Com isso em mente, podemos avaliar exatamente por que acabamos com as conjunto de sinalizações de sandbox no exemplo do Twitter acima:

  • allow-scripts é obrigatório, porque a página carregada no frame executa alguns JavaScript para lidar com a interação do usuário.
  • allow-popups é obrigatório, porque o botão abre um formulário de tweet em uma nova janela.
  • O allow-forms é obrigatório porque o formulário de envio de tweets precisa ser de envio.
  • allow-same-origin é necessário, assim como os cookies de twitter.com seriam estar inacessível e o usuário não conseguiu fazer login para postar o formulário.

Uma coisa importante a observar é que as sinalizações de sandbox aplicadas a um frame também se aplicam a quaisquer janelas ou frames criados na sandbox. Isso significa que temos para adicionar allow-forms ao sandbox do frame, mesmo que o formulário exista apenas na janela que o frame aparece.

Com o atributo sandbox, o widget recebe apenas as permissões exige, e recursos como plug-ins, navegação superior e bloqueio de ponteiro permanecem bloqueado. Reduzimos o risco de incorporar o widget, sem efeitos indesejados. É uma vitória para todos os envolvidos.

Separação de privilégios

Colocar conteúdo de terceiros no sandbox para executar códigos não confiáveis em um de baixo privilégio é bastante benéfico. Mas e seu próprio código? Você confia em si mesmo, certo? Então, por que se preocupar com o sandbox?

Eu perguntaria: se seu código não precisa de plug-ins, por que dar o acesso a plug-ins? Na melhor das hipóteses, é um privilégio que você nunca usa; na pior, é uma vetor potencial para que invasores se adiantem. O código de todos tem bugs e praticamente todos os aplicativos são vulneráveis à exploração de uma forma entre outros. Colocar seu próprio código no sandbox significa que mesmo que um invasor consiga subverter seu aplicativo, ele não receberá acesso total ao origem do aplicativo; eles só poderão fazer coisas que o aplicativo poderia fazer. Ainda ruim, mas não tão ruim quanto poderia ser.

Você pode reduzir ainda mais o risco dividindo seu aplicativo em partes lógicas e colocando todas as partes no sandbox com o privilégio mínimo possível. Essa técnica é muito comum em código nativo: o Chrome, por exemplo, interrompe a si mesmo em um processo de navegador de alto privilégio que tenha acesso ao disco rígido local e pode fazer conexões de rede, e muitos processos de renderizador de baixo privilégio que fazem o trabalho pesado de analisar o conteúdo não confiável. Os renderizadores não precisam tocar disco, o navegador fornece todas as informações necessárias para para renderizar uma página. Mesmo que um hacker inteligente encontre uma maneira de corromper um renderizador, não foi muito longe, já que o renderizador não pode oferecer muito interesse por si só: todo acesso a privilégios elevados precisa ser roteado pelo processo do navegador. Os invasores terão que encontrar vários buracos em diferentes partes do sistema para causar qualquer dano, o que reduz drasticamente o risco de pwnage bem-sucedido.

Colocando eval() no sandbox com segurança

Com a sandbox e os API postMessage, a para o sucesso desse modelo é bem simples de aplicar na Web. Pedaços de seu aplicativo pode ficar em iframes no modo sandbox, e o documento pai pode a comunicação do agente entre eles postando mensagens e detectando de resposta. Esse tipo de estrutura garante que as explorações em qualquer parte da causar o mínimo de dano possível. Ela também tem a vantagem de forçar você a criar pontos de integração claros para que você saiba exatamente onde precisa estar cuidadosos ao validar entradas e saídas. Vamos usar um exemplo de brinquedo só para ver como isso poderia funcionar.

Evalbox é um aplicativo interessante que usa uma string e a avalia como JavaScript. Uau, não é? Apenas o que que você esperava há tantos anos. É um tipo de ataque aplicativo, é claro, pois permitir a execução de JavaScript arbitrário significa que qualquer e todos os dados de uma origem estão disponíveis. Nós reduziremos o risco de Bad ThingsTM acontece garantindo que o código seja executado dentro de uma sandbox, o que o torna um pouco mais seguro. Vamos analisar o código da de dentro para fora, começando pelo conteúdo do frame:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

Dentro do frame, temos um pequeno documento que simplesmente ouve as mensagens. do pai vinculando o evento message do objeto window. Sempre que o pai executa postMessage no conteúdo do iframe, esse evento será acionado, o que nos dá acesso à string que o pai quer a serem executados.

No gerenciador, capturamos o atributo source do evento, que é o pai janela. Usaremos isso para enviar o resultado de nosso trabalho árduo de volta assim que estivermos feito. Depois, faremos o trabalho pesado, transmitindo os dados que recebemos para eval(): Esta chamada foi encerrada em um bloco try, porque as operações banidas dentro de um iframe no modo sandbox geram exceções DOM com frequência. vamos pegar e enviar uma mensagem de erro. Por fim, publicamos o resultado de volta à janela principal. É uma coisa bem direta.

O pai é descomplicado de forma semelhante. Vamos criar uma interface pequena com um textarea. para o código e um button para execução. Além disso, extrairemos frame.html por meio de uma iframe colocado no sandbox, permitindo apenas a execução de scripts:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

Agora, vamos conectar as coisas para execução. Primeiro, ouviremos as respostas o iframe e alert() para os usuários. Presumidamente um aplicativo real faria algo menos irritante:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

Em seguida, vamos configurar um manipulador de eventos para cliques no button. Quando o usuário cliques, vamos pegar o conteúdo atual da textarea e transmiti-lo à frame para execução:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

Fácil, não é? Criamos uma API de avaliação muito simples e podemos ter certeza de que o código avaliado não tem acesso a informações sensíveis, como cookies ou armazenamento DOM. Da mesma forma, o código avaliado não consegue carregar plug-ins, abrir novas janelas ou qualquer outra atividade irritante ou maliciosa.

Você pode fazer o mesmo para seu próprio código, dividindo aplicativos monolíticos em componentes de propósito único. Cada um pode ser encapsulado em uma simples API de mensagens, apenas como o que escrevemos acima. A janela mãe de privilégio alto pode atuar como uma controlador e agente, enviando mensagens em módulos específicos, cada um com o mínimo possível de privilégios para o trabalho, ouvindo os resultados garantindo que cada módulo seja bem abastecido apenas com as informações de que necessita.

No entanto, é preciso ter muito cuidado ao lidar com conteúdo enquadrado que venha da mesma origem que o pai. Se uma página em O https://example.com/ enquadra outra página na mesma origem com um sandbox que inclua as sinalizações allow-same-origin e allow-scripts, a página enquadrada pode alcançar o elemento pai e remover o atributo sandbox completamente.

Jogue no sandbox

O sandbox já está disponível para você em diversos navegadores: Firefox 17+, IE10+ e Chrome quando este artigo foi escrito (o caniuse, é claro, tem uma atualização tabela de suporte). Como aplicar a sandbox ao iframes incluído, permite que você conceda determinados privilégios ao conteúdo exibido, apenas os privilégios necessários para para funcionar corretamente. Isso permite reduzir o risco associadas à inclusão de conteúdo de terceiros, que vai além do que possível com a Segurança de conteúdo Política.

Além disso, sandbox é uma técnica poderosa para reduzir o risco de que uma equipe inteligente o invasor poderá explorar brechas em seu próprio código. Ao separar um aplicativo monolítico em um conjunto de serviços em sandbox, cada um responsável por uma um pequeno pedaço de funcionalidade independente, os atacantes serão forçados a não comprometem apenas frames específicos conteúdo, mas também seu controlador. Esse é um uma tarefa muito mais difícil, especialmente porque o controlador pode ser muito reduzido no escopo. Você poderá passar seu esforço relacionado à segurança auditando esse código se peça ajuda ao navegador com o restante.

Isso não quer dizer que a sandbox seja uma solução completa para o problema de a segurança na Internet. Ele oferece defesa em profundidade e, a menos que você controle sobre os usuários você ainda não pode contar com o suporte do navegador para todos seus usuários (se você controla os clientes, um ambiente empresarial, por exemplo: viva!). Um dia, mas, por enquanto, sandbox é outra camada de para reforçar as defesas, não é uma defesa completa na qual em que você só pode confiar. Ainda assim, as camadas são excelentes. Sugiro usar isso um.

Leitura adicional

  • Separação de privilégios em aplicativos HTML5 é um artigo interessante que funciona por meio do design de uma pequena estrutura, e sua aplicação a três aplicativos HTML5 existentes.

  • A sandbox pode ser ainda mais flexível quando combinada com dois outros novos iframe atributos: srcdoc, e seamless. O primeiro permite que você preencha um frame com conteúdo sem a sobrecarga de uma solicitação HTTP, e esta permite que o estilo flua para o conteúdo emoldurado. No momento, os dois têm um suporte bastante insatisfatório para navegadores (Chrome e WebKit todas as noites). mas será uma combinação interessante no futuro. Você poderia, Por exemplo, comentários no sandbox em um artigo por meio do seguinte código:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>