1 pontos por GN⁺ 2024-04-02 | 1 comentários | Compartilhar no WhatsApp
  • xzbot é um repositório para explorar o backdoor do xz (CVE-2024-3094), oferecendo junto um honeypot, patch de chave pública ED448, formato do payload do backdoor e uma demo de execução remota de código
  • O honeypot aplica um patch simples ao OpenSSH para registrar no log do sshd as tentativas de conexão que chegam com um valor N de chave pública compatível com o formato do backdoor
  • O patch de ED448 substitui a chave pública hardcoded usada pelo backdoor para verificação de assinatura e descriptografia do payload por uma chave gerada pelo usuário, permitindo acionar o backdoor
  • O payload do backdoor fica no valor N da chave de assinatura CA de um certificado SSH, e cria o tipo de requisição com a * b + c; o Type 2 executa um comando terminado em null com system()
  • A CLI de demonstração mostra o fluxo de conexão a um servidor SSH vulnerável para executar comandos, e uma exploração bem-sucedida não gera logs de nível INFO ou superior

Escopo e composição do xzbot

  • xzbot é um projeto para explorar o backdoor do xz, CVE-2024-3094
  • Ele inclui quatro componentes
    • honeypot: servidor vulnerável falso para detectar tentativas de exploit
    • ed448 patch: aplica patch em liblzma.so para usar sua própria chave pública ED448
    • backdoor format: documentação do formato do payload do backdoor
    • backdoor demo: CLI que aciona RCE assumindo que se conhece a chave privada ED448

Honeypot: detecção de tentativas com patch no OpenSSH

  • openssh.patch é um patch simples aplicado ao OpenSSH para registrar tentativas de conexão que chegam com um valor N de chave pública compatível com o formato do backdoor
  • O fluxo consiste em clonar openssh-portable, aplicar o patch e executar autoreconf, configure e make
  • As tentativas de conexão aparecem no log do sshd como xzbot: magic 1 [preauth] e uma sequência de bytes do payload

Patch da chave pública ED448

  • O backdoor usa uma chave pública ED448 hardcoded para verificação de assinatura e descriptografia do payload
  • Se essa chave pública for trocada por uma chave controlada pelo usuário, é possível acionar o backdoor
  • O repositório apresenta tanto a chave pública ED448 do atacante quanto um valor de chave pública substituta gerado com seed=0
  • A etapa inicial é preparar o objeto compartilhado liblzma.so.5.6.1 que contém o backdoor
    • Como fonte de exemplo, é indicado o snapshot Debian de xz-utils 5.6.1-1
    • Após instalar pwntools, executar patch.py liblzma.so.5.6.1 gera liblzma.so.5.6.1.patch
    • Depois, executa-se o sshd usando o objeto compartilhado modificado liblzma.so.5.6.1.patch
  • Há exemplos em assets

Formato do payload do backdoor

  • O backdoor pode ser acionado ao conectar com um certificado SSH colocando o payload dentro do valor N da chave de assinatura CA
  • Esse payload precisa ser criptografado e assinado com a chave ED448 do atacante
  • A estrutura externa é composta pelos seguintes campos
    • a: 32 bits
    • b: 32 bits
    • c: 64 bits
    • ciphertext: 240 bytes
  • O tipo de requisição é derivado de a * b + c
    • Se o valor for maior que 3, o backdoor ignora o processamento
    • Type 1: finalidade desconhecida e espera 0 bytes
    • Type 2: executa com system() um payload terminado em null
    • Type 3: finalidade desconhecida e espera dados de assinatura de 48 bytes
  • ciphertext é criptografado com chacha20, usando os primeiros 32 bytes da chave pública ED448 como chave simétrica
    • Por isso, tentativas de exploit podem ser descriptografadas com a chave de 32 bytes apresentada
  • Dentro do ciphertext descriptografado há uma assinatura de 114 bytes, campos de 1 bit x e y, comprimento, string de comando e outros dados
    • Definir x ou y leva a caminhos de código ligeiramente diferentes
  • A assinatura é uma assinatura ED448 conforme a RFC-8032
    • A entrada inclui um valor magic de 32 bits, um campo de 5 bytes antes do comando, opcionalmente length bytes do comando e os primeiros 32 bytes do hash SHA-256 da hostkey do servidor

CLI de demonstração do exploit

  • A CLI é instalada com Go
    • go install github.com/amlweems/xzbot@latest
  • xzbot -h mostra três opções principais
    • -addr: endereço do servidor SSH, padrão 127.0.0.1:2222
    • -seed: seed ED448 que deve corresponder à chave do backdoor do xz, padrão 0
    • -cmd: comando a ser executado por system(), padrão id > /tmp/.xz
  • O exemplo se conecta a um servidor SSH vulnerável em 127.0.0.1:2222 e executa o comando id > /tmp/.xz
  • Ao definir um watchpoint no local da chamada de system() no servidor vulnerável, é possível ver o processo sshd executando id > /tmp/.xz
  • Após a execução, é mostrado um exemplo em que /tmp/.xz contém a saída uid=0(root) gid=0(root) groups=0(root)

Árvore de processos e características dos logs

  • A árvore de processos de uma conexão SSH normal segue para a sessão do usuário e o shell sob o sshd
  • No exemplo de execução do backdoor, após xzbot -cmd 'sleep 60', aparecem sshd: root [priv], sshd: root [net], sh -c sleep 60 e sleep 60
  • Uma exploração bem-sucedida não gera entradas de log de nível INFO ou superior

Materiais de referência

1 comentários

 
GN⁺ 2024-04-02
Opiniões no Hacker News
  • É bastante interessante o fato de terem feito com que fosse necessária a chave privada do atacante, em vez de permitir execução remota de código que qualquer um pudesse abusar.
    Ironicamente, parece uma vulnerabilidade com uma consciência de segurança muito alta.

    • A intenção original provavelmente era prolongar a vida útil do backdoor.
      Se tivessem aberto um buraco grande o bastante para qualquer um entrar, ele teria sido descoberto e fechado rapidamente; e, se não houvesse degradação de desempenho, talvez tivesse sobrevivido silenciosamente por muito tempo, justamente por não ser amplamente explorável.
    • Visto como um ataque patrocinado por um Estado, faz bastante sentido implantar uma vulnerabilidade segura em sistemas que até cidadãos do próprio país poderiam usar.
      Todas as contribuições ao xz acabam parecendo trabalho para inserir esse backdoor; por exemplo, até criaram um framework de testes capaz de esconder o payload malicioso.
      Antes de trabalhar no xz, também contribuíram para a libarchive do BSD, onde surgiu uma vulnerabilidade.
    • Realmente parece um exploit patrocinado por Estado.
      O projeto e a execução foram extremamente sofisticados, e dá a impressão de que o problema de desempenho ter levado à descoberta foi quase pura sorte.
    • Isso se chama NOBUS: https://en.wikipedia.org/wiki/NOBUS
    • Pelo que li, a assinatura do payload inclui a chave de host SSH do alvo; fico me perguntando se é isso mesmo.
      Se for, não dá para sair espalhando isso por servidores indiscriminadamente, e o próprio envio para um host é uma estrutura com custo computacional considerável.
  • Esse caso não saiu da minha cabeça o fim de semana inteiro.
    O mecanismo é interessante e um ótimo conjunto de ofuscações, e a parte de engenharia social é uma história vergonhosamente familiar para mantenedores de open source.
    O mais interessante é terem escolhido dados de teste ruins como vetor de ataque: se você prepara um bom arquivo, manipula sua estrutura e depois o usa como dados ruins para testes de fuzzing, o restante das etapas fica muito mais fácil.
    Para referência futura, acho que esse tipo de manipulação deveria aparecer em um grafo de padrões binários.
    O restante das técnicas, depois de definido o payload, era em grande parte ofuscação relativamente comum, mas a grande jogada foi que o mesmo padrão poderia ter adicionado “patches” ou até backdoors novos a outros arquivos de teste sem que ninguém percebesse.
    O fato de o GitHub ter ocultado e removido o repositório não ajudou em nada e atrapalha a análise desse caso.

    • Fiquei desconfiado das dependências em tempo de build das ferramentas open source que usamos.
      Descobri que essa dependência tem uma preferência curiosa por xz e, se ele não estiver instalado, chega a instalar o xz na máquina host como se fosse um recurso de conveniência.
      Ela não faz isso com outras dependências, então é meio estranho.
      Esse tipo de jogo de longo prazo é assustador, e até o “mal” ser de fato executado não dá para saber o que é malicioso e o que é apenas estranho.
    • Uma das causas centrais parece ter sido adicionar arquivos binários ao repositório para uso como entradas de teste.
      Especialmente se forem arquivos de “lixo binário” para demonstrar falhas de teste, é um lugar bom demais para esconder conteúdo malicioso.
    • Ao removerem o repositório, no fim só o atacante ficou com acesso ao código e ao know-how.
  • É muito impressionante a rapidez com que a comunidade, especialmente amlweems, implementou e documentou esse código de prova de conceito.
    Se não houver vulnerabilidades adicionais nas funções criptográficas ou no carregamento do payload, talvez isso nem tenha aberto a falha de segurança para todos os outros atacantes até que a chave seja quebrada.
    O próximo passo é encontrar uma forma de detectar distribuições vulneráveis, mas isso não parece fácil; também poderia ser incluído no upstream um modo de monitorar se alguém está varrendo ativamente servidores SSH com a chave hardcoded.

    • Isso não é o exploit original, e sim uma prova de conceito de uma versão com a chave alterada.
      Uma prova de conceito da versão original exigiria a chave privada não divulgada do atacante.
    • Sem a chave privada do atacante, detectar pela rede uma distribuição vulnerável parece não apenas difícil, mas impossível.
      O melhor que dá para fazer é algum benchmarking mais refinado, mas não há como saber se um host qualquer na internet é lento porque está vulnerável, porque está longe ou porque o próprio computador é lento.
      Também não há acesso a quanto demoravam tentativas anteriores de conexão com aquele host, e ainda existem variações de roteamento.
  • Fico curioso para saber se alguém rodou essa prova de conceito em ferramentas de detecção de comportamento anômalo de processos.
    Produtos como Carbon Black, AWS GuardDuty e Sysdig se encaixam nisso, e este parece ser um caso perfeito para essa categoria de produto, para testar se alguém teria percebido com relativa rapidez quando isso fosse implantado de verdade.

    • Acho que depende de quão bem o exploit imita ou se esconde dentro de uma operação normal de compactação.
      O GuardDuty não inspeciona processos como um EDR do tipo CrowdStrike ou Carbon Black, então acho que seria difícil pegar; e o Sysdig observa contêineres e infraestrutura de nuvem, então parece difícil detectar o exploit em si.
      Ainda assim, há possibilidade de detectar anomalias nas ações subsequentes do agente de ameaça após a escalada de privilégios.
      No fim, quem teria maior chance de detectar o próprio exploit seria um EDR que monitora processos no endpoint, ou uma avaliação de cadeia de suprimentos de software que monitora problemas de segurança em software livre e open source upstream.
      Curiosamente, isso leva a um tema de segurança maior.
      Equipes de desenvolvimento podem não gostar de colocar EDR em servidores por causa da queda de desempenho e dos problemas de experiência do usuário em medidas de isolamento, e também podem não gostar de políticas que restringem o uso de software livre e open source.
      Esse exploit atinge em cheio uma “vulnerabilidade” organizacional e, dependendo da posição, cria tanto argumentos para manter a exposição quanto para corrigi-la.
    • A Sysdig publicou um blog na sexta-feira.
      Eles disseram: “Uma forma de detecção em runtime é monitorar se o SSHD carrega uma biblioteca maliciosa. Essas bibliotecas compartilhadas muitas vezes incluem a versão no nome do arquivo”.
      O blog também traz conteúdo de regras de detecção reais que não vi em outros fornecedores de segurança.
      https://sysdig.com/blog/cve-2024-3094-detecting-the-sshd-bac...
    • Vejo essa categoria inteira de produtos, em sua maior parte, como produtos de segurança de fachada.
  • Li incorretamente o link abaixo, e mantenho o conteúdo original para fins de registro
    Olhando mais abaixo no mesmo thread de e-mail, foi dito que a pessoa que fez o commit do backdoor também parecia ter feito recentemente uma contribuição ao kernel, mas a análise original em si é realmente excelente, então vale a pena ler esse texto
    https://www.openwall.com/lists/oss-security/2024/03/29/10

    • Essa série de patches foi enviada pelo Lasse, não pelo Jia Tan
      O próprio Lasse também disse que não era nada urgente e que não entraria nesta janela de merge; uma pessoa razoável não está acusando o Lasse de ser um agente malicioso
    • Até prova em contrário, Lasse Collin não é Jia Tan
    • A série de patches citada ainda não entrou no kernel
    • Pode ser coincidência, mas JiaT75 se parece bastante com o código de transponder 7500, que na aviação significa sequestro
  • Este caso tem uma quantidade absurdamente grande de semelhanças com o caso do Audacity de alguns anos atrás
    O Cookie guy afirmou que foi esfaqueado e que a polícia federal se envolveu no caso, o que sugere a possibilidade de o caso estar ligado a um ator muito maior que o 4chan
    Na época, muita gente achava que apenas o Muse Group estava envolvido, mas talvez pudesse ter sido um ator estatal russo
    Antes disso, ele havia afirmado que o Audacity tinha muita telemetria e backdoors e que os removeu no primeiro commit depois de fazer o fork
    Talvez o Audacity também tenha mesmo um backdoor, então será preciso verificar o código-fonte

    • É preciso tomar cuidado, o APT28 é bastante perigoso
      Hoje em dia ele está combinando operações com o APT29, e eu não acordaria o cozy bear
    • Quem é o Cookie guy?
  • Fico curioso para saber como o exploit real fazia isso em tempo de execução, sem o openssh.patch do honeypot em tempo de compilação https://github.com/amlweems/xzbot/blob/main/openssh.patch
    A cadeia era opensshd -> notificação do systemd -> xz incluído como dependência transitiva, e quero saber como, depois que liblzma.so.5.6.1 era carregado na memória, ele voltava até openssh_RSA_verify para instalar um hook ou aplicar um patch

    • Ao carregar a liblzma, ele faz patch na GOT do ELF com o endereço do código malicioso
      Se ela for carregada antes da libcrypto, ele registra um manipulador de auditoria de símbolos, que provavelmente parece ser um recurso específico da glibc, permitindo receber uma notificação quando os símbolos da libcrypto forem resolvidos e adiar o patch da GOT
    • É ifunc
  • Fico curioso para saber se esse exploit só funcionava quando entrava uma conexão SSH
    A lista de strings no GitHub inclui DISPLAY e WAYLAND_DISPLAY
    https://gist.github.com/q3k/af3d93b6a1f399de28fe194add452d01
    Como elas não têm uma relação clara com SSH, é possível que ele fizesse algo mesmo sem uma conexão
    Isso pode ser importante para pessoas que executaram o código, mas presumem estar seguras porque não expuseram o servidor SSH à internet

    • Provavelmente é um kill switch para impedir que o exploit funcione se houver um terminal aberto ou se ele estiver rodando em uma sessão GUI
      Ou seja, é um mecanismo para evitar situações em que alguém esteja tentando detectar, reproduzir ou depurar
    • Também pode estar relacionado ao encaminhamento de sessão X11
      Se você não desativar isso ao se conectar a uma máquina não confiável, vira uma brecha de segurança comum do lado de quem se conecta
  • Fico curioso se a frase “a exploração bem-sucedida não gera entradas de log” significa que, se esse exploit não tivesse sido descoberto, o atacante poderia executar comandos arbitrários como root no host comprometido sem nenhum log do sshd para aquela “conexão”

    • Sim
      A execução remota de código ocorre na etapa de conexão, antes que os logs sejam gravados
    • Ainda assim, teria havido tráfego SSH sem um login correspondente
      Fico curioso sobre que tipo de detecção de anomalias poderia ter capturado isso
  • Fico curioso se teria sido possível dificultar esse ataque separando os arquivos de teste em um repositório à parte, de modo que não pudessem ser usados no momento do build
    A lógica é que tudo o que pode participar do build deve ser legível por humanos

    • “Tudo o que pode participar do build deve ser legível por humanos” é um bom princípio para adotar de forma geral
      Devemos tratar esse ataque como um acidente aéreo e introduzir novas regras que reduzam a chance de ele ter sucesso novamente
      Mesmo que não seja possível verificar todos os contribuidores individualmente, deveria ser fácil separar dados de teste ruidosos