Análise do 0-day no WebP
(blog.isosceles.com)- A única correção de segurança incluída na atualização estável do Chrome, CVE-2023-4863, é um estouro de buffer no heap na biblioteca de imagens WebP, e o Google afirmou que já havia um exploit em uso no mundo real
- O relatório da Apple SEAR de 6 de setembro de 2023, o CVE-2023-41064 da Apple e o contexto do BLASTPASS do Citizen Lab indicam uma alta probabilidade de se tratar do mesmo bug
- A vulnerabilidade foi analisada como um problema no processo de construção da tabela de Huffman do VP8L, a compressão sem perdas do WebP, que permitia escritas além do tamanho de buffer pré-calculado
- O motivo pelo qual o fuzzing comum tinha dificuldade em encontrá-la era a existência de condições de disparo exigentes, que exigiam criar, em sequência, várias tabelas de Huffman de tamanho máximo e uma tabela inválida específica
- O patch upstream da libwebp parece suficiente, mas como a libwebp é amplamente usada em navegadores, sistemas operacionais e apps, a disseminação do patch e o sandboxing são importantes
Por que o patch do Chrome se conecta ao BLASTPASS
- No início de setembro de 2023, o Google corrigiu na atualização estável do Chrome o CVE-2023-4863, relatado pela Apple Security Engineering and Architecture (SEAR)
- A vulnerabilidade é um estouro de buffer no heap na biblioteca de imagens WebP
- O Google afirmou estar ciente de que “existe um exploit para o CVE-2023-4863 em uso no mundo real”
- Na mesma época, o Citizen Lab detectou comportamento suspeito no iPhone de uma pessoa ligada a uma organização da sociedade civil sediada em Washington, DC
- BLASTPASS está ligado a um caso de distribuição do spyware Pegasus, do NSO Group, por meio de um exploit zero-click e zero-day no iMessage
- Depois que o Citizen Lab enviou os resultados da análise técnica à Apple, a Apple divulgou dois CVEs em um aviso de segurança em 7 de setembro
- O primeiro CVE da Apple, CVE-2023-41061, está relacionado a indícios de que anexos do PassKit agrupavam um exploit de imagem para contornar o sandbox BlastDoor do iMessage
- A imagem maliciosa parece ter sido processada em outro processo sem sandbox
- O segundo CVE, CVE-2023-41064, é uma vulnerabilidade de estouro de buffer no Apple ImageIO
- ImageIO é o framework de parsing de imagens da Apple que identifica vários formatos de imagem e os encaminha ao decodificador adequado
- Como não há detalhes técnicos, não foi confirmado qual formato de imagem é afetado pelo CVE-2023-41064
- Como a Apple adicionou recentemente suporte a WebP no ImageIO, a equipe de segurança da Apple relatou uma vulnerabilidade de WebP ao Chrome em 6 de setembro, e o Google lançou um patch emergencial em 5 dias marcando-a como explorada na prática, há uma alta probabilidade de que o BLASTPASS e o CVE-2023-4863 sejam o mesmo bug
O local da vulnerabilidade apontado pelo patch da libwebp
- Ao comparar o ID do bug no aviso de segurança do Chrome com commits open source da libwebp, o patch Fix OOB write in BuildHuffmanTable corresponde ao CVE-2023-4863
- O patch foi criado em 7 de setembro de 2023, um dia depois do relatório da Apple
- A vulnerabilidade está no VP8L, o suporte a compressão sem perdas do WebP
- A compressão sem perdas do WebP usa Huffman coding para armazenar e restaurar pixels com 100% de precisão
- Implementações modernas otimizam a decodificação usando tabelas em vez de uma estrutura conceitual de árvore
- A versão vulnerável alocava memória com um tamanho de buffer pré-calculado obtido de uma tabela fixa e então construía diretamente a tabela de Huffman nesse espaço
- A nova versão calcula, no primeiro passe, o tamanho total necessário para a tabela de saída, mas sem realizar escritas
- Se o tamanho total for maior que o buffer pré-calculado, ela passa a criar uma alocação maior
- O fluxo principal envolve
huffman_tables, alocado emReadHuffmanCodesemsrc/dec/vp8l_dec.c, e as chamadas aVP8LBuildHuffmanTable/BuildHuffmanTabledentro deReadHuffmanCode- A tabela de Huffman é dividida em 5 segmentos com tamanhos de alfabetos diferentes
- Para causar o overflow, é preciso construir com precisão todas essas 5 tabelas
Processo de criação de um arquivo que dispara o bug
- A compressão sem perdas do WebP atribui sequências curtas de bits a valores frequentes e sequências longas a valores raros, com base na análise de frequência dos pixels de entrada
- Os códigos são construídos de modo que o decodificador consiga sempre distinguir o comprimento das sequências de bits
- A imagem comprimida precisa incluir informações estatísticas e de atribuição de códigos para reproduzir o mapeamento entre códigos e valores
- O WebP também comprime a própria tabela de Huffman com Huffman coding para reduzir o tamanho do arquivo
- Essa estrutura torna mais complexo analisar e disparar a vulnerabilidade
@mistymntncopforneceu um código de harness que cria WebPs em formato válido com dados arbitrários de Huffman coding, ou seja, code lengths- Com esse harness, foi possível passar um array arbitrário de
code_lengthspara a chamada-alvo deBuildHuffmanTable
- Com esse harness, foi possível passar um array arbitrário de
- Nos experimentos manuais, a interação dentro de
BuildHuffmanTableentre o histograma,num_open,num_nodese o valorkey, que acompanha a posição inicial deReplicateValue, era extremamente complexa- A root table de
count[0]acount[8]não afeta diretamentetotal_sizede forma significativa, mas pode alterar o estado interno posterior - As second level tables de
count[9]acount[15]afetam diretamente ototal_sizefinal
- A root table de
- enough.c, de Mark Adler, imprime o histograma do maior lookup table possível de árvore de Huffman para um tamanho de alfabeto, tamanho de root table e comprimento máximo de código
- Um comentário afirma que
kTableSizeda libwebp foi calculado com essa ferramenta - Com essa ferramenta, foi possível reproduzir o tamanho de buffer pré-calculado e também confirmar, com a ferramenta de
@mistymntncop, que os code lengths gerados pelo “enough” preenchiam 100% da alocação dehuffman_tables
- Um comentário afirma que
Condições em que o overflow realmente ocorre
- A ferramenta
enoughcalcula o valor máximo para códigos válidos e completos- Para uma das tabelas pequenas, com tamanho de símbolo 40, root table de 8 bits e comprimento máximo de código 15, o tamanho máximo é 410
- Não foi encontrado nenhum caso que produza um tamanho maior que 410 entre os códigos que
BuildHuffmanTableconsidera válidos
- O overflow ocorre ao inserir uma árvore de Huffman inválida na última etapa, em vez de ser criado apenas com árvores de Huffman válidas
- Primeiro são criadas tabelas de saída de tamanho máximo com 4 árvores de Huffman válidas
- Quando uma árvore de Huffman inválida é inserida na última tabela,
ReplicateValuepode escrever fora dos limites antes da verificação final de consistência
- A reprodução consiste em fazer checkout do commit vulnerável da libwebp
7ba44f80f3b94fc0138db159afea770ef06532a0, ativar o AddressSanitizer, gerarbad.webpcom o código PoC de@mistymntncope decodificá-lo comdwebp- O AddressSanitizer reporta
heap-buffer-overflowemBuildHuffmanTable - A escrita reportada ocorre no endereço logo após uma região de 11816 bytes
- O AddressSanitizer reporta
- Existem várias entradas que causam overflow em
huffman_tables- Entre os code lengths encontrados, há casos que escrevem até 400 bytes depois do fim da alocação de
huffman_tables - Mesmo com controle parcial sobre os valores escritos, a exploração parece possível
- Entre os code lengths encontrados, há casos que escrevem até 400 bytes depois do fim da alocação de
- A árvore de Huffman da entrada inválida é parcialmente desequilibrada e contém muitos nós internos sem filhos em um trecho do ramo desequilibrado
- Essa estrutura cria índices
keyque não poderiam ser alcançados com uma árvore válida
- Essa estrutura cria índices
Como o patch impede o overflow
- À primeira vista, o patch parecia impedir o estouro no heap aumentando dinamicamente o buffer conforme o tamanho necessário
- Na prática, o primeiro passe da versão corrigida executa
BuildHuffmanTablepara calcular o tamanho total necessário, mas não escreve na tabela- A entrada inválida que causava o overflow faz
BuildHuffmanTablefalhar nesse primeiro passe e retornar 0 - Como o primeiro passe não realiza escritas, nenhuma escrita fora dos limites ocorre mesmo que a árvore inválida seja processada parcialmente
- A entrada inválida que causava o overflow faz
- Como não foram encontrados casos de códigos válidos e completos que causassem o mesmo overflow, esse patch parece suficiente
Por que o fuzzing tinha dificuldade em encontrar o bug
- A libwebp vinha sendo fuzzada há muito tempo pelo Google OSS-Fuzz, e o suporte a WebP sem perdas também era fuzzado extensivamente
- A principal dificuldade é que tanto o formato quanto as condições de disparo são complexos
- Entre bilhões de possibilidades, é preciso primeiro construir 4 tabelas de Huffman de tamanho máximo para alfabetos de tamanho 280 e 256
- Em seguida, é preciso criar uma tabela de Huffman inválida com uma forma muito específica para um alfabeto de tamanho 40
- Se apenas 1 bit estiver errado em qualquer etapa, o decodificador de imagens retorna erro e encerra com segurança
- Depois de corrigir o 0-day do WebP, o Google lançou um novo fuzzer dedicado às rotinas de Huffman do WebP
- Mesmo executando esse fuzzer, o CVE-2023-4863 não foi encontrado
- Mutações padrão por bit flip, loops de feedback por cobertura de código e abordagens baseadas em entrada-estado como o CmpLog do AFL++ também têm dificuldade para atravessar as etapas intermediárias até esse estado extremo
- Técnicas de execução simbólica dinâmica como o TritonDSE, da Quarkslab, poderiam ter potencial, mas isso não foi confirmado
- A natureza do caso é diferente da vulnerabilidade Load_SBit_Png do FreeType
- Load_SBit_Png não foi descoberta porque o harness de fuzzing não refletia suficientemente o modo de uso da API
- O CVE-2023-4863 está mais próximo de um caso em que o fuzzing é difícil pelas restrições da própria vulnerabilidade do que por falta de harness
Escopo do impacto e estado da resposta
- O Citizen Lab capturou um exploit avançado usado no mundo real, e Apple e Chrome parecem ter distribuído atualizações para bilhões de usuários em poucos dias
- Na época, ainda havia a possibilidade de o Android ser afetado
- O BitmapFactory do Android processa decodificação de imagens como o ImageIO da Apple e oferece suporte à libwebp
- O boletim de segurança do Android daquela época não incluía a correção para o CVE-2023-4863, mas a correção já havia sido mesclada ao AOSP
- Se o Android fosse afetado, isso poderia levar a exploits remotos em apps como Signal ou WhatsApp
- A correção era esperada para o boletim de segurança de outubro
- O patch upstream da libwebp parece ter sido aplicado corretamente e está se disseminando para os locais necessários
- Como a libwebp é usada em muitos lugares, pode levar tempo até que o patch seja suficientemente distribuído
- Para código de parser complexo próximo da superfície de ataque de exploits remotos zero-click, como decodificação de imagens, é difícil garantir segurança apenas com fuzzing; é necessário investir em revisão proativa de código-fonte e sandboxing adequado
Assimetria na divulgação de informações técnicas
- Quando fornecedores não divulgam detalhes técnicos suficientes, fica mais difícil para defensores verificarem o impacto de uma vulnerabilidade
- Atacantes têm forte motivação para rastrear e explorar vulnerabilidades N-day, e a falta de divulgação de detalhes não os atrasa muito
- Defensores muitas vezes não têm recursos para realizar análises técnicas nesse nível
- Ocultar até informações básicas sobre o funcionamento do ataque cria uma assimetria em que atacantes têm mais conhecimento do que defensores sobre vulnerabilidades e exploits
1 comentários
Opiniões no Hacker News
Este bug parece mais parecido com o bug do Timsort de 2015 [1]
Timsort é um algoritmo de ordenação híbrido inteligente vindo do CPython, e várias implementações, incluindo o OpenJDK, o adotaram quase como uma tradução em nível de código-fonte. Ele mantém uma pilha de runs ordenados, e havia uma prova de que existia um limite superior finito suficientemente pequeno para o tamanho máximo de pilha possível pela estrutura, mas a implementação original do CPython não correspondia exatamente à prova, então, em casos raros, era possível ocorrer estouro de pilha. Por isso, no CPython era um bug de segurança sério, mas, no Java, esse caso lançava uma exceção, de modo que no OpenJDK não era um problema de segurança da mesma forma
Da mesma forma, este bug do WebP também surgiu porque o tamanho máximo da tabela foi provado formalmente, mas não batia com o valor que entrou no código-fonte real. Esse tipo de bug é difícil tanto de verificar quanto de revisar. Como há uma prova e o código-fonte também parece bater com ela, é fácil achar que está tudo bem. Parece um forte sinal da necessidade de verificação formal acessível, mais do que revisão humana, e do uso de linguagens com segurança de memória. O sistema de tipos também pode ser visto como uma forma fraca de verificação formal
[1] http://envisage-project.eu/wp-content/uploads/2015/02/sortin...
https://github.com/google/wuffs
Também é possível satisfazer esse requisito escrevendo explicitamente as mesmas verificações, mas, se você acreditava que elas não eram necessárias por se tratar de software de alto desempenho, é bem provável que descubra que estava errado no ponto em que a ferramenta do WUFFS não aceitar o código. É mais fraco do que uma verificação formal completa, mas é uma grande melhoria para o objetivo de segurança do programa e muito melhor do que uma pessoa dizendo “LGTM”
Além de “o que devo corrigir agora”, houve alguns pontos interessantes neste texto. Mesmo sabendo a localização da vulnerabilidade e o conteúdo da correção, reproduzir um PoC de exploit pode exigir bastante trabalho, e descompressores lossless dentro de decodificadores de imagem podem ser bastante resistentes a fuzzing. Para pessoas da área de segurança isso pode ser óbvio, mas, como não especialista, achei a leitura interessante
Há algumas perguntas para as quais não consigo encontrar uma resposta clara. 1) Outros navegadores baseados no Chrome, como o Brave, também são afetados? 2) O Chrome para desktop também é afetado, ou é um problema apenas mobile? 3) Por que nunca ouvi falar de WebP? Fico me perguntando se eu estava desconectado do mundo ou se é uma tecnologia mobile-first
O Chrome para desktop também foi afetado, tanto no Linux quanto no Windows. O Chrome inclui sua própria libwebp, então, mesmo que a distribuição Linux ainda não tenha aplicado o patch, se o Chrome estiver atualizado, ao menos do ponto de vista de ataques ao navegador, está tudo bem
Os principais navegadores e sistemas operacionais dão suporte a uma quantidade surpreendente de formatos de imagem pouco conhecidos. Por exemplo, no MacOS é possível carregar KTX2(Khronos Texture Container), e no Android, DNG(Adobe Digital Negative). Há muitas superfícies de ataque interessantes e bastante expostas para um invasor explorar
O bug está na biblioteca do codec, e o WebP é um ecossistema com implementação praticamente unificada, então todos usam a mesma biblioteca e todos precisam ser corrigidos
O Google impulsionou o WebP há 10 anos, mas por muito tempo ele era exclusivo do Chrome e não ganhou muita força. Também nunca foi propriamente padronizado, embora seja open source. Em imagens de baixa qualidade, comprime melhor que JPEG, mas em imagens de alta qualidade tende a borrar cores e deixar tudo meio desfocado. Ironicamente, quando o WebP começou a ter suporte amplo, ele já estava se tornando tecnicamente obsoleto por causa do AVIF e do JPEG XL
Se um dispositivo Android já estiver sem suporte, isso significa que, na prática, ele está exposto a um exploit de 0 clique que está circulando agora? Ou atualizar apenas o app de SMS, Chrome, WhatsApp, Signal etc. já bloqueia suficientemente as principais vias de entrada?
Em dispositivos sem suporte, atualizar o Chrome corrige o problema no Chrome, mas para corrigir apps como Signal ou WhatsApp é necessário um upgrade do Android OS. O Chrome inclui sua própria libwebp em bundle, mas apps de alta exposição, como mensageiros e o Gmail, usam as interfaces fornecidas pelo OS para exibir imagens. Em dispositivos Android ainda com suporte de segurança, espera-se que as atualizações comecem a sair no início de outubro.
A afirmação de que “há muitas entradas que realmente causam overflow em huffman_tables” parece um problema mais geral. É uma estrutura em que o software A cria uma tabela de consulta B, e essa tabela é usada para processar um fluxo de dados de entrada.
Agora, qualquer desenvolvedor que se preocupe minimamente com segurança ou correção precisa garantir uma de duas coisas: A) que o software foi escrito de modo que nenhum dado de entrada consiga usar indevidamente a tabela de consulta ou fazê-la falhar; ou B) que ele só seja usado em um ambiente controlado, por exemplo em uma comunicação ponto a ponto em que ambas as partes são confiáveis, com a garantia de que o fluxo nunca usará indevidamente a tabela de consulta nem causará anomalias.
B é praticamente impossível fora de grupos pequenos ou escritórios e, em escala de internet, é realmente impossível; então só sobra A. A prática recomendada generalizada para a engenharia de software daqui para frente é: se uma tabela de consulta for usada por qualquer motivo, o desenvolvedor deve garantir que ela funcione corretamente para todos os tipos de dados, ou que dados inválidos sejam detectados e tratados adequadamente antes de chegarem à tabela.
Se eu fosse um pesquisador de segurança sério e tivesse tempo, reuniria uma lista de todas as vulnerabilidades de segurança do passado que tiveram alguma relação com tabelas de consulta, leria uma por uma e compararia os pontos em comum. Provavelmente haveria um padrão. Depois disso, eu vasculharia todo software que usa tabelas de consulta em fluxos de dados e faria auditoria de vulnerabilidades, mas isso não é algo que uma pessoa consiga fazer em uma vida; é um esporte de equipe.
Até que ponto a abertura do código-fonte ajudou a NSO a encontrar esse bug? Fico curioso se, caso o código fosse apenas um blob binário, os decompiladores modernos ainda seriam suficientes para entender o código e encontrar um bug obscuro como esse.
É surpreendente que isso não seja uma parte “nova” do formato de imagem. Compressão de Huffman existe há mais de 70 anos, Huffman canônico só existe há algumas décadas a menos que isso, e até JPEG usa Huffman. É uma tecnologia de décadas, com incontáveis textos sobre como implementá-la; parece o tipo de tecnologia cujos bugs em várias implementações já deveriam ter sido resolvidos.
Como eu já tinha lido a especificação do JPEG e também escrito um decodificador antes, dei uma olhada na especificação do WebP: https://developers.google.com/speed/webp/docs/webp_lossless_...
O conteúdo importante está na seção 6. A primeira coisa que chama atenção é que, ao contrário da especificação do JPEG, não fica claro como os códigos são construídos. O JPEG tem muitos fluxogramas fáceis de ler sobre o processo. O WebP parece parecido com LZH/deflate(zlib), e está mais para uma coleção de alguns trechos de código-fonte comentados do que para uma “especificação”.
Depois de GIF, JPEG e PNG, até pensei em escrever um decodificador WebP, mas, olhando só para a “especificação” acima, a sensação é quase de que ela está dizendo para eu não fazer isso.
Implementações de árvores de Huffman envolvem muitos trade-offs diferentes, então não dá para dizer que todos os bugs já foram resolvidos só por ser uma tecnologia antiga. Charles Bloom disse que o artigo definitivo de 1997 sobre otimização de Huffman [1] ainda não era muito conhecido em 2010, e que muitas otimizações foram redescobertas e depois esquecidas. Por isso, é bem possível que haja muitas implementações ineficientes.
[1] https://cbloomrants.blogspot.com/2010/08/08-12-10-lost-huffm...
Parece que hoje em dia nem dá mais para ver imagens sem se preocupar com segurança.
Isso soa como as dores de crescimento típicas de usar C, uma linguagem insegura. Entendo o apelo por causa da velocidade nos navegadores, mas gostaria que a indústria se afastasse dessa forma de incentivar os mesmos erros que vem repetindo desde que começou a usar C.
É como construir uma ponte com parafusos autobrocantes em vez de rebites só porque é mais rápido. Quando a ponte começa a ceder, a solução vira simplesmente colocar mais parafusos.
“A boa notícia é que Apple e Chrome responderam muito bem à urgência deste problema” — isso é difícil de aceitar. Quem classificou este problema como exclusivo do Chrome foi o Google. Só nos últimos 7 dias, todas as principais distribuições Linux tiveram que empurrar atualizações, e a Red Hat deu nota 9,6. Imagens Docker de Python com mais de 1 bilhão de pulls, Puppeteer, WordPress, Node.js etc. também foram afetados, mas o CRBug ainda está privado
Sites como o BleepingComputer noticiaram o que viam sem investigar, e o mesmo vale para muitas empresas de segurança que trataram este problema como se fossem terceiros. Quando você percebe que a outra parte não fez a devida diligência, fica realmente difícil construir confiança
Adam Caudill escreveu um bom texto, “Whose CVE is it anyway?”[0], junto com o caso do 1Password, que aplicou o patch logo no início, e apontou bem o problema de que se fala aqui. O Citizen Lab se recusou a responder se os dois problemas estavam relacionados, mas há coisas que não exigem exatamente um gênio para perceber
[0]: https://adamcaudill.com/2023/09/14/whose-cve-is-it-anyway/
O autor também diz que muitos outros sistemas precisam ser corrigidos. Mas, dentre as imagens Docker de Python baixadas 1 bilhão de vezes, quantas renderizam imagens WebP não confiáveis? O mesmo vale para Node etc. Claro que é preciso corrigir rapidamente, mas não estão no mesmo patamar que iOS/Android/Chrome
Centenas de milhões de pessoas no mundo todo são afetadas por esta biblioteca, e o impacto se multiplica por vários fatores em cada dispositivo e app que elas usam
Eu gosto de C, mas acho que bibliotecas usadas por centenas de milhões de pessoas no mundo todo não deveriam usar C. A proporção de risco é alta demais em bibliotecas essenciais como essa. Mesmo um especialista em C vai cometer um erro em algum momento
Acho que esta é a correção: https://github.com/webmproject/libwebp/commit/dce8397fec159c...
“malloc fail” é frustrante. Slack, Discord, Teams e até todos os sistemas operacionais modernos são afetados
C/C++ e linguagens dinâmicas ampliam muito a superfície de comportamento indefinido e bugs sutis, além de serem difíceis e pesadas de analisar com lint até para os desenvolvedores mais inteligentes. Bibliotecas de base deveriam ser verificadas formalmente de uma forma parecida com o seL4
Outro problema é o amadorismo disseminado por todo o FOSS, e o desprezo por rigor, qualidade, correção e segurança. A forma atual de construir um império sobre areia é insensata