Incidente · Sênior
Cloudbleed 2017: parser vaza memória pela borda da CDN
Incidente real. Pontos centrais: bug de memória em parser e expurgo de caches de terceiros.
Resposta curta
Incidente real. Pontos centrais: bug de memória em parser e expurgo de caches de terceiros. Em 18 de fevereiro de 2017, a Cloudflare recebeu do Google os detalhes de um problema: um pesquisador do Project Zero havia visto páginas corrompidas devolvidas por servidores de borda. O post-mortem público da empresa é a fonte deste caso.
01 · CONTEXTO
Uma borda que reescreve o HTML dos outros
Em 18 de fevereiro de 2017, a Cloudflare recebeu do Google os detalhes de um problema: um pesquisador do Project Zero havia visto páginas corrompidas devolvidas por servidores de borda. O post-mortem público da empresa é a fonte deste caso.
Na borda da rede, a Cloudflare lê e modifica o HTML das respostas em trânsito, por exemplo para reescrever links, esconder partes da página de robôs ou ofuscar endereços de e-mail. Para isso, precisa analisar o HTML e achar os elementos que mudam.
O analisador original, escrito com a ferramenta Ragel, servia a todas essas modificações. A empresa passou a escrever um novo, o cf-html, e migrava funcionalidades aos poucos do antigo para ele. Os dois são módulos do NGINX que analisam buffers com HTML, fazem as alterações e passam os buffers adiante.
No processo HTTP da borda, os dois analisadores são módulos de filtro: cada um recebe os buffers com HTML, modifica o que for preciso e passa adiante.
O analisador antigo era o original; o novo foi escrito para substituí-lo, e as funcionalidades migravam aos poucos de um para o outro.
A terminação SSL ficava numa instância isolada de NGINX, que o bug não afetou.
02 · O BUG LATENTE
Um erro de ponteiro que dormiu por anos
A causa raiz foi um erro de ponteiro: o fim do buffer era verificado com um teste de igualdade, e o ponteiro podia pular o fim e seguir adiante, o que se chama estouro de buffer. Uma verificação com maior ou igual teria pego o salto. Esse teste era gerado automaticamente pelo Ragel, o que indicou que a ferramenta não estava sendo usada corretamente.
O código escrito pela equipe tinha um tratador de erro sem o fhold, que recua o ponteiro em uma posição. Esse recuo ausente foi o que causou o vazamento. Se a página terminava com uma tag HTML quebrada, o erro ocorria no fim do buffer e o ponteiro ficava depois do fim do documento. Acrescentar o fhold ao tratador corrige o problema.
O defeito existia havia muitos anos no analisador antigo, mas nenhuma memória vazava por causa da forma como os buffers internos do NGINX eram usados. Ao introduzir o cf-html, o uso dos buffers mudou sutilmente e permitiu o vazamento, embora o próprio cf-html não tivesse problema.
A diferença estava no que o analisador sabia sobre o último buffer. Com os dois analisadores presentes, o último buffer com dados chegava marcado como final, e o tratamento de erro rodava.
| Situação | Só o analisador antigo | Os dois analisadores |
|---|---|---|
| Marcação do último buffer com dados | Marcado como não sendo o último | Marcado como o último |
| Fim de arquivo (eof) | Nulo | Aponta para o fim do buffer |
| Bloco de erro | Não é executado | É executado |
Para a memória vazar, o último buffer com dados tinha de terminar com uma tag de script ou de imagem malformada e ter menos de 4k. Além disso, o cliente precisava ter ativo um recurso que combinasse os dois analisadores, como o Email Obfuscation. Essas condições explicam por que o estouro ocorria tão raramente.
03 · O IMPACTO
Memória de um cliente na página de outro
A memória vazada vinha de um processo baseado em NGINX que trata HTTP e podia conter cabeçalhos, trechos de corpos de POST (talvez com senhas), JSON de chamadas de API, parâmetros de URI, cookies e credenciais como chaves de API e tokens OAuth.
Como a infraestrutura é compartilhada, uma requisição a um site vulnerável podia revelar informação de outro site da Cloudflare.
Em certas condições, o processo HTTP da borda lia além do fim de um buffer e devolvia memória com dados privados, como cookies, tokens e corpos de POST.
Por ser uma infraestrutura compartilhada, a memória devolvida podia trazer informação de outro site da Cloudflare.
As chaves privadas SSL dos clientes não vazaram: o processo de SSL tem heap separado do processo que trata HTTP.
Buscadores guardaram parte da memória vazada em cache, por seus processos normais de rastreamento.
A empresa, com os buscadores, identificou as URIs em cache e pediu o expurgo antes de anunciar o problema.
Mesmo com as chaves SSL dos clientes protegidas, vazou uma chave privada usada nas conexões entre as máquinas da própria Cloudflare, e havia na memória alguns segredos internos de autenticação.
No pior período, de 13 a 18 de fevereiro, cerca de 1 em cada 3.300.000 requisições HTTP podia vazar memória. A empresa não encontrou evidências de exploração maliciosa.
04 · RESPOSTA E EXPURGO
Desligar por recurso, depois limpar os caches
Uma equipe multifuncional se formou em São Francisco e Londres, e o trabalho passava entre os escritórios a cada 12 horas para que a equipe atuasse 24 horas por dia.
Três recursos usavam a mesma cadeia de parser HTML causadora do vazamento. Todo recurso da Cloudflare tem uma feature flag, chamada de global kill, e desligá-los fez com que a memória não pudesse mais ser devolvida em respostas HTTP.
Três recursos usavam a mesma cadeia de parser HTML que causava o vazamento.
A chave de desligamento global do Email Obfuscation foi acionada 47 minutos após a empresa receber os detalhes do problema.
A do Automatic HTTPS Rewrites foi acionada 3h05m depois. Desligar um recurso levou poucos segundos em todo o mundo.
O Server-Side Excludes também era vulnerável e não tinha chave global, por ser anterior a esse mecanismo. A empresa criou a chave e implantou um patch na frota.
Com os recursos desligados, a memória já não podia ser devolvida em respostas HTTP.
| Recurso | Como entrava no vazamento | Alcance |
|---|---|---|
| Email Obfuscation | Usava os dois analisadores | Alterado em 13 de fevereiro; principal causa da memória vazada |
| Automatic HTTPS Rewrites | Usa o analisador novo; só vazava em combinação com outro recurso do analisador antigo | Pouco usado |
| Server-Side Excludes | Usa o analisador novo; só vazava em combinação com outro recurso do analisador antigo | Executa só para IPs de má reputação |
A empresa cita que o padrão da indústria para corrigir um bug assim costuma ser de três meses, e que terminou globalmente em menos de 7 horas, com mitigação inicial em 47 minutos.
Como buscadores tinham guardado parte da memória vazada, a equipe de segurança identificou as URIs em cache e pediu o expurgo antes do anúncio público, por entender que tinha o dever de cuidado. Com a ajuda dos buscadores, foram achadas 770 URIs únicas em 161 domínios.
05 · LIÇÕES
Caçar o resto do bug e olhar para o código antigo
Depois de achar um estouro de ponteiro no código gerado, a equipe foi atrás de outros. Parte do time de segurança fez fuzzing do código gerado, outra equipe montou testes com páginas malformadas encontradas na internet, e engenheiros inspecionaram o código manualmente.
Decidiu-se também adicionar checagens explícitas a todo acesso por ponteiro no código gerado e registrar os erros. Com esse registro, cada linha de log indicava uma requisição que poderia ter vazado memória.
Perguntas frequentes
O que é Cloudbleed 2017: parser vaza memória pela borda da CDN?
Incidente real. Pontos centrais: bug de memória em parser e expurgo de caches de terceiros.
O que o design de Cloudbleed 2017: parser vaza memória pela borda da CDN cobre?
Ele está organizado nestas seções: Uma borda que reescreve o HTML dos outros, Um erro de ponteiro que dormiu por anos, Memória de um cliente na página de outro, Desligar por recurso, depois limpar os caches e Caçar o resto do bug e olhar para o código antigo.
Quanto tempo leva Cloudbleed 2017: parser vaza memória pela borda da CDN e qual é o nível?
Leva cerca de 14 minutos para ler e tem nível Sênior.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.