Nossos esforços de conectividade se concentram em expandir o acesso à Internet e a adoção em todo o mundo. Isso inclui nosso trabalho em tecnologias como o Terragraph, nossa colaboração com operadoras móveis nos esforços para expandir o acesso rural, nosso trabalho como parte do Projeto Telecom Infra, e programas como o Free Basics. Enquanto continuamos a trabalhar no Free Basics, ouvimos o feedback e as recomendações da sociedade civil e de outras partes interessadas. Desenvolvemos o Discover especificamente para abordar e incorporar essas recomendações em um novo produto que suporte a conectividade. Hoje, o Facebook Connectivity e nossos parceiros da Bitel, Claro, Entel e Movistar estão lançando uma versão experimental do Discover no Peru.

Fornecer este serviço enquanto mantém as pessoas a salvo de potenciais riscos de segurança foi um desafio técnico difícil. Nós queríamos desenvolver um modelo que nos permitisse apresentar com segurança páginas web de todos os domínios disponíveis, incluindo seus recursos (scripts, mídia, folhas de estilo, etc.). Abaixo, passamos pelo modelo que construímos, as escolhas de arquitetura únicas que fizemos ao longo do caminho e os passos que demos para mitigar os riscos.

Onde começamos

Para o Free Basics, nosso desafio era encontrar uma maneira de fornecer um serviço sem custos para as pessoas que usam a web móvel, mesmo em telefones sem suporte a aplicativos de terceiros. Os parceiros da operadora móvel podiam fornecer o serviço, mas as restrições de rede e de equipamentos de gateway significavam que apenas o tráfego para determinados destinos (geralmente intervalos de endereços IP ou uma lista de nomes de domínio) podia ser feito gratuitamente. Com mais de 100 parceiros globalmente e o tempo e a dificuldade envolvidos na mudança das configurações dos equipamentos de rede da operadora, percebemos que precisávamos criar uma nova abordagem.

Essa nova abordagem exigia que primeiro construíssemos um serviço proxy baseado na web onde a operadora pudesse disponibilizar o serviço gratuitamente para um único domínio: freebasics.com. A partir daí, iríamos buscar páginas web em nome do usuário e entregá-las ao seu dispositivo. Mesmo em navegadores modernos, há algumas preocupações com arquiteturas de proxy baseadas na web. Na web, os clientes são capazes de avaliar os cabeçalhos de segurança HTTP como o compartilhamento de recursos de origem cruzada (CORS) e a Política de Segurança de Conteúdo (CSP) e fazer uso de cookies diretamente do site. Mas na configuração de um servidor proxy, o cliente está interagindo com o proxy, e o proxy atua como um cliente para o site. O proxy de sites de terceiros através de um único namespace viola algumas suposições sobre como os cookies são armazenados, quanto scripts de acesso têm de ler ou editar conteúdo e como CORS e CSP são avaliados.

Para resolver essas preocupações, inicialmente impusemos algumas limitações simples, incluindo quais sites poderiam ser visitados com o Free Basics e a incapacidade de executar scripts. Esta última tornou-se mais um problema com o tempo, uma vez que muitos sites, incluindo sites móveis, começaram a depender do JavaScript para funcionalidades críticas, incluindo a renderização de conteúdo.

Arquitetura inicial

Desenho de domínio

Para acomodar a funcionalidade limitada de muitos gateways de operadoras móveis, consideramos arquiteturas alternativas, incluindo:

  1. Uma solução cooperativa onde sites podem alocar um subdomínio (por exemplo free.example.com>) e resolvê-lo para o nosso espaço IP para que as operadoras o tornem gratuito para o utilizador.

Esta solução tinha pros:

  • Permitia uma comunicação direta de ponta a ponta entre cliente e servidor.
  • Requeria uma intervenção mínima no lado do proxy.

No entanto, também tinha alguns inconvenientes:

  • Sites necessários para optar por este esquema, incorrendo em custos extra de engenharia para os proprietários do site.
  • Navegadores teriam de solicitar um domínio específico através do Server Name Indication (SNI), para que o proxy soubesse onde se conectar. No entanto, o suporte ao SNI não é universal, o que tornou esta solução menos viável.
  • Se os assinantes navegassem acidentalmente para example.com diretamente, e não para o subdomínio free.example.com, eles incorreriam em encargos – e não necessariamente seriam redirecionados para o subdomínio a menos que a operadora tivesse implementado alguma lógica extra.
  1. encapsulamento IPv4-em-IPv6, onde podemos encapsular todo o espaço IPv4 dentro de uma única sub-rede IPv6 de dados livre. Um resolvedor DNS personalizado resolve o IPv4 recursivamente e responde com respostas IPv6 encapsuladas.

Esta solução também tinha prós:

  • Não foi necessária a cooperação do proprietário do site.
  • Não houve necessidade de SNI para resolver o IP remoto.

E cons:

  • Os navegadores veriam o domínio www.example.com.freebasics.com, mas o certificado www.example.com resultaria num erro.
  • Apenas alguns gateways de operadoras suportavam IPv6 desta forma.
  • A menos onze dispositivos suportavam IPv6, especialmente versões mais antigas de SO.

Nem uma destas era uma solução viável. Finalmente, decidimos que a melhor arquitetura possível seria o colapso da origem, onde nosso proxy roda dentro de um único espaço de nomes de domínio com origem colapsada sob freebasics.com. Os operadores podem então permitir o tráfego de listas para este destino mais facilmente e manter as suas configurações simples. Cada origem de terceiros é codificada em um subdomínio, assim podemos garantir que a resolução do nome sempre direcionará o tráfego para um IP livre.

Por exemplo:

https://example.com/path/?query=value#anchor

É reescrita para:

https://https-example-com.0.freebasics.com/path/?query=value#anchor

Existe uma extensa lógica do lado do servidor para garantir que os links e hrefs sejam corretamente transformados. Esta mesma lógica ajuda a garantir que mesmo sites somente HTTP sejam entregues com segurança sobre HTTPS no Free Basics entre o cliente e o proxy. Este esquema de reescrita de URL permite-nos utilizar um único espaço de nomes e certificado TLS, em vez de exigir um certificado separado para cada subdomínio na Internet.

Todas as origens da Internet tornam-se irmãos sob 0.freebasics.com, o que levanta certas considerações de segurança. Não pudemos tirar vantagem de adicionar o domínio à Lista Pública de Sufixos, pois teríamos que emitir um cookie diferente para cada origem, o que eventualmente excederia os limites dos cookies do navegador.

Cookies

Confessionários da Web não semelhantes, que podem fazer uso de cookies diretamente do site, o serviço de proxy requer uma configuração diferente. Free Basics armazena cookies de usuários no lado do servidor por várias razões:

  1. Na maioria das vezes, os navegadores móveis de nível inferior têm suporte limitado a cookies. Se emitirmos apenas um cookie por site sob o nosso domínio proxy, podemos limitar-nos a definir apenas dezenas de cookies. Se o Free Basics definisse os cookies do lado do cliente para cada site em 0.freebasics.com, os navegadores mais antigos atingiriam rapidamente os limites locais de armazenamento de cookies – e até mesmo os navegadores modernos atingiriam um limite por domínio.
  2. As restrições de espaço de nomes de domínio que precisávamos implementar também impediam o uso de cookies irmãos e hierárquicos. Por exemplo, um conjunto de cookies em qualquer subdomínio em .example.com seria normalmente legível em qualquer outro subdomínio. Em outras palavras, se a.example.com coloca um cookie em .example.com, então b.example.com deve ser capaz de lê-lo. No caso do Free Basics, a-example-com.0.freebasics.com estaria definindo um cookie em example.com.0.freebasics.com, o que não é permitido pelo padrão. Como isso não funciona, outras origens, como b-example-com.0.freebasics.com, não seriam capazes de acessar os cookies definidos para seu domínio pai.

Para permitir que o serviço de proxy acesse esse pote de cookies do lado do servidor, o Free Basics utiliza dois cookies do lado do cliente:

  1. O cookie datr, um identificador do navegador utilizado para fins de integridade do site.
  2. O ick (chave de cookie da internet), que contém uma chave criptográfica usada para encriptar o jarro de cookies do lado do servidor. Como esta chave é armazenada apenas no lado do cliente, o pote de cookies do lado do servidor não pode ser descriptografado pelo Free Basics quando o usuário não está utilizando o serviço.

Para ajudar a proteger a privacidade e segurança do usuário ao armazenar seus cookies em um pote de cookies do lado do servidor, nós nos certificamos de que:

  1. Os cookies do lado do servidor são criptografados com um ick que é mantido apenas no cliente.
  2. Quando o cliente fornece o ick, ele é esquecido pelo servidor em cada solicitação sem nunca ser logado.
  3. Marcamos ambos os cookies do lado do cliente como Secure e HttpOnly.
  4. >

  5. Marcamos o índice de um cookie usando a chave do lado do cliente para que o cookie não seja rastreável de volta ao usuário quando a chave não estiver presente.

Permitir que os scripts sejam executados arrisca a fixação de cookies do lado do servidor. Para evitar isso, excluímos o uso de JavaScript do Free Basics. Adicionalmente, mesmo que qualquer site possa fazer parte do Free Basics, nós revisamos cada site individualmente para potenciais vetores de abuso, independentemente do conteúdo.

Melhorando o que tínhamos construído

Para suportar um modelo servindo qualquer site, com a habilidade de rodar scripts com mais segurança, nós precisávamos repensar significativamente nossa arquitetura para prevenir ameaças, como scripts sendo capazes de ler ou corrigir os cookies do usuário. O JavaScript é extremamente difícil de analisar e impedir que código não intencional seja executado.

Como exemplo, aqui estão algumas formas de um atacante poder injetar código que precisaríamos ser capazes de filtrar:

setTimeout();location = ' javascript:alert(1) <!--';location = 'javascript\n:alert(1) <!--';location = '\x01javascript:alert(1) <!--';var location = 'javascript:alert(1)';for(location in {'javascript:alert(1)':0}); = 'javascript:alert(1)';location.totally_not_assign=location.assign;location.totally_not_assign('javascript:alert(1)');location] = 'javascript:alert(1)';Reflect.set(location, 'href', 'javascript:alert(1)')new Proxy(location, {}).href = 'javascript:alert(1)'Object.assign(window, {location: 'javascript:alert(1)'});Object.assign(location, {href: 'javascript:alert(1)'});location.hash = '#%0a alert(1)';location.protocol = 'javascript:';

O modelo que criamos ampliou o projeto Free Basics, mas também protege o cookie que está armazenando a chave de criptografia de ser sobregravada por scripts. Nós usamos um frame externo que confiamos para atestar que o frame interno, que apresenta conteúdo de terceiros, não está sendo adulterado. A secção seguinte mostra em detalhe como mitigamos a fixação da sessão e outros ataques, tais como phishing e clickjacking. Nós estabelecemos um método para servir conteúdo de terceiros de forma segura enquanto habilitamos a execução JavaScript.

Arquitetura de melhorias em Discover

Referências ao domínio neste ponto mudarão para o nosso novo domínio, uma origem-colapsada de forma semelhante discoverapp.com.

JavaScript e fixação de cookies

Ao permitir JavaScript de sites de terceiros, tivemos que reconhecer que isso permite que certos vetores para os quais precisávamos nos preparar, pois os scripts podem modificar e reescrever links, acessar qualquer parte do DOM e, no pior dos casos, fixar cookies do lado do cliente.

A solução que encontrámos é necessária para endereçar a fixação de cookies, por isso, em vez de tentar analisar e bloquear certas chamadas de scripts, decidimos detectá-la à medida que acontecia e torná-la inútil. Isto é conseguido pelo seguinte:

  1. Na inscrição, nós geramos um novo e seguro cookie aleatório ick.
  2. Enviamos ick para o navegador como um cookie HttpOnly.
  3. >

  4. Enviamos então para o HMAC um valor chamado ickt de um digest de ambos ick e datr (para evitar fixação para ambos) e armazenamos uma cópia de ickt no cliente, num local em localStorage para o qual um potencial atacante não pode escrever. O local que usamos é https://www.0.discoverapp.com, que nunca serve para conteúdo de terceiros. Uma vez que esta origem é irmão de todas as origens de terceiros, não pode ocorrer redução de domínio ou qualquer outro tipo de modificação de domínio, e a origem é considerada confiável.
  5. Incluímos ickt, derivado do cookie ick visto na solicitação, dentro do HTML em cada resposta de proxy de terceiros.
  6. Quando a página é carregada, comparamos o embedded ickt com o trust ickt usando window.postMessage(), e invalidamos a sessão se houver um descasamento apagando os cookies datr e ick.
  7. Impedimos a interacção do utilizador com a página até este processo estar concluído.

Como protecção adicional, definimos um novo datr cookie se detectarmos vários cookies no mesmo local, incorporando um carimbo de data/hora para que possamos usar sempre o mais recente.

Solução de dois quadros

Para validação, precisamos de uma forma de uma página de terceiros para consultar o valor ickt e validá-la. Fazemos isso incorporando o site de terceiros dentro de um valor <iframe> em uma página na origem segura e injetando um pedaço de JavaScript no site de terceiros. Construímos um frame externo seguro e um frame interno de terceiros.

Inner frame

Com o frame interno, injetamos um script em cada página proxied que servimos. Também injetamos o valor ickt calculado a partir do valor ick visto no pedido junto com ele. O comportamento do frame interno é o seguinte:

  1. Verifique com o frame externo:
    • postMessage para cima com ickt embutido na página.
    • Espere.
    • Se o script receber um reconhecimento da origem segura, deixamos o usuário interagir com a página.
    • Se o script espera muito tempo ou recebe uma resposta de uma origem inesperada, nós navegamos o frame para uma tela de erro sem conteúdo de terceiros (nossa página “Oops”), porque é possível que o frame externo não esteja lá ou seja diferente do que o frame interno espera.
  2. Verifique com parent:
    • postMessage a parent.
    • Espere.
    • Se o script receber uma resposta com source===parent e origem sob .0.discoverapp.com, ele irá prosseguir.
    • Se o script esperar muito tempo, ou receber uma resposta de uma origem inesperada, nós iremos navegar para a página “Oops”.

Algumas notas no frame interno:

  1. Even se for contornado, os potenciais atacantes seriam capazes de fixar somente em uma origem na qual eles podem obter a execução do código, tornando redundantes os vetores de fixação de cookies.
  2. Presumimos que uma origem benigna não contornará deliberadamente o protocolo de mensagens interno-outer.

Faixa-fora

Aaixa-fora está lá para atestar que a armação interna é consistente:

  1. Aaixa-fora é sempre a armação superior com JavaScript e X-Frame-Options: DENY.
  2. Espera por postMessage.
  3. Se a moldura externa receber uma mensagem:
    • É de uma origem de moldura interna?
    • Se sim, informa o valor correcto ickt>
      • Se sim, envia uma mensagem de confirmação.
      • Se não, apague a sessão, apague todos os cookies e navegue para uma origem segura.
  4. Se o frame externo não receber uma mensagem durante alguns segundos ou se o subframe não for o frame interno mais alto, removemos a localização da barra de endereço do frame seguro.

Interacção da página

Para evitar condições de corrida em que uma pessoa possa introduzir uma palavra-passe sob um cookie corrigido antes do frame interno ter concluído a verificação, é importante evitar que as pessoas interajam com a página antes da sequência de verificação do frame interno estar concluída.

Para evitar isto, o servidor adiciona style="display:none" ao elemento <html> de cada página. O frame interno irá removê-lo quando receber a confirmação do frame externo.

O código JavaScript ainda pode ser executado, e os recursos ainda são buscados. Mas enquanto a pessoa não tiver inserido nenhuma entrada na página, o navegador não faz nada que um potencial atacante não pudesse ter feito simplesmente visitando o site – a menos que o site já esteja vulnerável a falsificações de pedidos entre sites (CSRF).

Ao optar por esta solução, tivemos de resolver para outros resultados possíveis, especificamente:

  1. Fixação assíncrona de cookies.
  2. Clickjacking due to framing.
  3. Phishing impersonating the Discover domain.

Asynchronous cookie fixation

Até agora, as proteções que implementamos contabilizaram as fixações síncronas, mas elas também podem ocorrer de forma assíncrona. Para prevenir isto, usamos um método clássico de prevenção do CSRF. Exigimos que os POSTs tenham um parâmetro de consulta com o parâmetro datr visto quando a página é carregada. Comparamos então o parâmetro de consulta com o cookie datr visto no pedido. Se não corresponderem, não cumprimos o pedido.

Para evitar datr fugas, embutimos uma versão encriptada do datr dentro do frame interior e asseguramos que este parâmetro de consulta é adicionado a cada objecto <form> e XHR. Como a página não pode derivar o token datr por si só, o datr adicionado é o que se vê na altura.

Para pedidos anónimos, exigimos que tenham também o parâmetro de consulta datr. O anonimato é preservado porque não o divulgamos para o site de terceiros – falta o cookie ick, por isso não podemos usar o cookie jarro. No entanto, neste caso, não somos capazes de validar contra o cookie datr, por isso os POSTs anónimos podem ser feitos sob sessões fixas. Mas como eles são anônimos e sem o ick, nenhuma informação sensível pode vazar.

Clickjacking

Quando um site envia X-Frame-Options: DENY, ele não será carregado em um frame interno. Este cabeçalho é usado por sites para prevenir a exposição a certos tipos de ataques, tais como clickjacking. Nós removemos esse cabeçalho da resposta HTTP mas pedimos ao frame interno para verificar se parent é o frame da janela top usando postMessage. Se a validação falhar, nós navegamos o usuário para a página “Oops”.

Phishing

A “barra de endereço” que fornecemos no frame seguro é usada para expor a origem do frame interno mais alto ao usuário. No entanto, ela pode ser copiada por sites de phishing que imitam o Discover. Impedimos que links maliciosos naveguem para longe do Discover, impedindo a navegação pelo topo usando <iframe sandbox>. O frame externo só pode ser escapado navegando diretamente para outro site.

Biscoitos do lado do cliente

O document.cookie permite que o JavaScript leia e modifique os cookies que não estão marcados com HttpOnly. Suportar isto com segurança é um desafio em um sistema que mantém os cookies no servidor.

Acessando cookies: Quando uma solicitação é recebida, o proxy irá enumerar todos os cookies que são visíveis para essa origem. Ele então anexará uma carga útil JSON à página de resposta. O código do lado do cliente é injectado no shim document.cookie e torna estes cookies visíveis para outros scripts, como se fossem cookies reais do lado do cliente.

Modificar cookies: Se for permitido aos scripts definir arbitrariamente cookies que o servidor então aceita, isto poderia levar à fixação, onde a origem evil.com poderia definir um cookie sensível em example.com.

Confiar nas capacidades CORS do navegador não seria suficiente neste caso – origem a.example.com tentar definir um cookie em example.com será bloqueado pelo navegador, uma vez que estas origens são irmãos e não hierárquicas.

Even, portanto, quando o servidor recebe um novo cookie definido pelo cliente, não pode impor com segurança se o domínio alvo é permitido; a origem do escritor é conhecida apenas no cliente e nem sempre é enviada para o servidor de uma forma que possamos confiar.

Para forçar o cliente a provar que é elegível para definir cookies num domínio específico, o servidor enviará, além da carga útil do JSON, uma lista de tokens criptográficos para cada uma das origens em que a origem do pedido é permitida para definir cookies. Estes tokens são salgados com o valor ick, pelo que não podem ser transferidos entre utilizadores.

O calço do lado do cliente para document.cookie encarrega-se de resolver e incorporar o token no texto do cookie real que é enviado para o proxy. O proxy pode então verificar se a origem de escrita realmente possuía o token para escrever no domínio alvo do cookie, e armazená-lo no pote de cookies do lado do servidor, enviando-o novamente para o cliente na próxima vez que a página for solicitada.

Protocolo bootstrap

O modelo contém três tipos de origem: origem do portal (Discover portal, etc.), origem segura (frame externo), e origem de reescrita (frame interno). Cada um tem uma necessidade diferente:

  1. A origem do portal requer datr.
  2. A origem segura requer ickt.
  3. Reescrever origem requer datr e ick.

Com o protocolo localStorage

Existe uma representação do processo bootstrap para a maioria dos navegadores móveis modernos:

É importante notar que para evitar reflexão, o ponto final bootstrap na origem segura sempre emite um novo ick e ickt; ick nunca depende da entrada do usuário. Note que como definimos domain=.discoverapp.com em ambos ick e datr, eles estão disponíveis em todos os tipos de origem, e ickt está disponível apenas na origem segura.

Sem protocolo localStorage

Porque certos navegadores, como o Opera Mini (popular em muitos países onde o Discover opera), não suportam localStorage, não podemos armazenar os valores ick e ickt. Isto significa que temos de usar um protocolo diferente:

Decidimos separar a origem reescrita da origem segura para que eles não partilhem o mesmo sufixo de anfitrião de acordo com a Lista Pública de Sufixos. Nós usamos www.0.discoverapp.com para armazenar a cópia segura de ickt (como um cookie), e movemos todas as origens de terceiros sob 0.i.org. Em um navegador bem-comportado, a definição de um cookie na origem segura o tornará inacessível a todas as origens reescritas.

Desde que as origens estão agora separadas, nosso processo de bootstrap torna-se um processo de dois passos. Antes, podíamos definir ick no mesmo pedido que fornecemos localStorage com ickt. Agora, precisamos de bootstrap duas origens, em pedidos separados, sem abrir ick vectores de fixação.

Resolvemos isto inicializando a origem segura com o cookie ickt primeiro e dando ao utilizador uma versão encriptada de ick, com uma chave conhecida apenas pelo proxy. O ick ciphertext é acompanhado por um nonce que pode ser usado para descriptografar aquele particular ick na origem reescrita e definir um cookie, mas apenas uma vez.

Um atacante poderia escolher entre:

  1. Utilizar o nonce para revelar o cookie ick.
  2. Passar ao usuário para fixar seu valor.

Em ambos os casos, o atacante não pode simultaneamente conhecer e forçar um determinado valor ick em um usuário. O processo também sincroniza datr entre as origens.

Esta arquitetura tem sido através de testes de segurança internos e externos substanciais. Acreditamos ter desenvolvido um design que é robusto o suficiente para resistir aos tipos de ataques de aplicações web que vemos na natureza e fornecer com segurança conectividade que é sustentável para as operadoras móveis. Após o lançamento da Discover no Peru, estamos planejando implementar testes adicionais Discover com operadoras parceiras em vários outros países onde temos testado características de produtos beta, incluindo Tailândia, Filipinas e Iraque. Prevemos que o Discover estará ao vivo nesses países adicionais nas próximas semanas, e exploraremos testes adicionais onde operadoras parceiras quiserem participar.

Gostaríamos de agradecer a Berk Demir por sua ajuda neste trabalho.

Num esforço para sermos mais inclusivos em nosso idioma, editamos este post para substituir “whitelist” por “allowlist”.

Deixe uma resposta

O seu endereço de email não será publicado.