- O backdoor do
xz, descoberto em março de 2024, poderia levar à execução remota de código em ambientessshdafetados, e o caso do NixOS mostra que builds reproduzíveis podem expor mecanicamente adulterações na cadeia de suprimentos - O componente malicioso não estava em todo o repositório Git do
xz, mas escondido nos tarballs de release fornecidos pelo mantenedor das versões5.6.0e5.6.1, gerando durante o build um script de shell e um arquivo-objeto a partir de falsos arquivos de teste.xz - O ataque usava o ifunc do
glibcpara executar código ao carregarliblzmae mirava ambientes das famílias Debian e Fedora em que osshddepende transitivamente deliblzmavialibsystemd - O NixOS normalmente prefere arquivos de código-fonte gerados automaticamente pelo GitHub, mas como o
xzestá na etapa de bootstrap donixpkgs, era preciso confiar no tarball do mantenedor para evitar um ciclo de dependência comautoconf - A defesa proposta é reconstruir o
xza partir do código-fonte do GitHub depois do bootstrap e comparar o resultado com o artefato do tarball existente; ao ativar a condição, surgem a diferença de tamanho emliblzma.soe a adição do símbolo_get_cpuid
Como o backdoor do xz foi escondido
xzé um software de compressão e descompressão frequentemente usado em caminhos centrais de distribuições Linux ao extrair tarballs de código-fonte- Em março de 2024, foi descoberto um backdoor no
xz, e o mantenedor malicioso Jia Tan ganhou confiança ao longo de cerca de 3 anos, obteve permissão de push no repositório e então inseriu gradualmente código ofuscado entre contribuições legítimas - Andres Freund, ao investigar uma degradação de desempenho de 500ms no
sshem várias máquinas Debian unstable, rastreou o problema atéliblzma, identificou o backdoor e o documentou - O objetivo do backdoor era sequestrar o
sshpara que, ao fazer login com uma chave RSA específica, o invasor pudesse executar comandos arbitrários na máquina da vítima
O tarball malicioso e os objetos gerados durante o build
- O componente malicioso não foi inserido diretamente em todo o código do repositório Git do
xz, mas incluído nos tarballs de release doxz5.6.0 e 5.6.1 que Jia Tan compilou, assinou e distribuiu - Alguns arquivos de teste
.xzdentro do repositório Git escondiam código de máquina, e os tarballs de release incluíam alterações disfarçadas para extrair esse conteúdo - Em especial, a mudança em
m4/build-to-host.m4parecia inofensiva à primeira vista e até tinha comentários, mas na prática escondia uma cadeia de comandos que decodificava e desofuscava vários falsos arquivos de teste.xz - O resultado desse processo eram duas coisas
- um script de shell executado durante o build do
xz - um arquivo-objeto binário malicioso
- um script de shell executado durante o build do
- O script de shell executado durante o build tinha dois papéis
- verificar as condições de execução do backdoor, como distribuição Linux específica, recursos do
glibce presença dossh - modificar o objeto legítimo
liblzma_la-crc64_fast.opara que ele usasse o símbolo_get_cpuiddo arquivo-objeto do backdoor
- verificar as condições de execução do backdoor, como distribuição Linux específica, recursos do
- A análise do script do
xzde Russ Cox detalha como os recursos maliciosos são gerados durante o build
Hijacking com ifunc até chegar ao sshd
- Em programas com linkedição dinâmica, no momento da execução o carregador dinâmico carrega bibliotecas compartilhadas na memória e preenche os endereços de símbolos na Global Offset Table (GOT)
- O ifunc do
glibcé um recurso para escolher, no carregamento dinâmico, uma entre várias implementações de uma mesma função, e o backdoor doxzo usava como caminho para execução de código malicioso - Em algumas distribuições como Debian e Fedora, o
sshé linkado comlibsystemdpara suporte a notificações dosystemd, elibsystemdpor sua vez é linkado comliblzma- nesse ambiente, o
sshdtem dependência transitiva deliblzma
- nesse ambiente, o
- Ao executar o
sshd, o carregador dinâmico carregalibsystemdeliblzma, e, se o backdoor estiver instalado, código malicioso é executado durante o carregamento deliblzma - O backdoor faz o resolvedor de ifunc chamar o símbolo malicioso
_get_cpuide manipula a GOT, ainda não marcada como somente leitura, para trocar o endereço deRSA_public_decryptpor uma função maliciosa - A análise detalhada da Securelist e este artigo de pesquisa que resume o vetor de ataque e as mitigaçãos podem ser consultados como referência
Fontes confiáveis e a restrição de bootstrap do NixOS
- Esse ataque pôde funcionar porque muitas distribuições compilavam o
xza partir de tarballs fornecidos pelo mantenedor, e não do código-fonte original do forge Git - O fluxo com tarball existe por razões históricas e práticas
- é o método das primeiras distribuições Linux, anterior ao
git - preserva, em um arquivo autocontido, o estado do código no momento da release
- pode incluir artefatos intermediários como manpages e scripts
configure, reduzindo o trabalho de build - ganha eficiência de armazenamento por meio da compressão
- é o método das primeiras distribuições Linux, anterior ao
- Do ponto de vista de segurança, quando tecnicamente possível, o ideal é compilar a partir de código-fonte autenticado pela entidade mais confiável disponível
- se o projeto é desenvolvido no GitHub, pode-se usar os arquivos gerados automaticamente pelo GitHub a cada release
- a mesma lógica vale para plataformas terceiras confiáveis como Codeberg, SourceHut e GitLab
- O NixOS usa um modelo funcional de gerenciamento de pacotes, em que as receitas de build são definidas em expressões Nix
- Entre mantenedores do NixOS há a preferência por usar
fetchFromGitHubcom arquivos de código-fonte gerados automaticamente pelo GitHub quando possível, mas o pacotexzestava buscando, comfetchurl, um tarball do mantenedor enviado manualmente - O motivo é que o
xzfaz parte da etapa de bootstrap donixpkgs- bootstrap é o processo que permite reconstruir todo o conjunto de pacotes do
nixpkgsa partir de um pequeno conjunto de binários-semente stdenvé o ambiente básico de compilação usado por outros pacotesstdenvdepende dexzem tempo de execução, e pacotes comocoreutilsprecisam dexzpara extrair arquivos-fonte.tar.xz
- bootstrap é o processo que permite reconstruir todo o conjunto de pacotes do
- Para compilar o
xza partir do código do GitHub, é precisoautoconfpara gerar o scriptconfigure, mas o próprioautoconftambém depende dexz- o tarball fornecido pelo mantenedor já inclui o script
configuregerado, quebrando esse ciclo de dependência - por isso, no ponto do grafo do
nixpkgsem que oxzé construído, era difícil usar o arquivo de código-fonte do GitHub e era necessário confiar no tarball do mantenedor
- o tarball fornecido pelo mantenedor já inclui o script
Comparar a convergência dos artefatos em vez do código-fonte
- Comparar se o tarball do mantenedor e o tarball de código-fonte do GitHub são idênticos parece natural, mas na prática isso não se encaixa bem
- Daniel Stenberg explica que o fato de o tarball de release ser diferente do código-fonte é uma funcionalidade
- ele pode incluir artefatos intermediários como manpages e scripts
configure - isso é especialmente útil quando a distribuição quer evitar dependências de
autoconf
- ele pode incluir artefatos intermediários como manpages e scripts
- Do ponto de vista de segurança da cadeia de suprimentos, essa flexibilidade se transforma na obrigação de confiar que o mantenedor foi honesto
- Builds reproduzíveis são a propriedade de produzir artefatos bit a bit idênticos ao compilar duas vezes nas mesmas condições
- O grupo reproducible-builds busca tornar reproduzível o maior número possível de pacotes, e Reproducible Builds: Increasing the Integrity of Software Supply Chains trata isso como uma propriedade que aumenta a confiança na distribuição de artefatos binários
- Uma abordagem para tornar a própria geração do tarball reproduzível foi adotada pelo projeto PostgreSQL
- isso permite que usuários verifiquem de forma independente se o tarball do mantenedor foi gerado honestamente a partir do código-fonte original
- ainda assim, como precisa ser implementado pelo mantenedor do projeto upstream, a adoção pode ser lenta na comunidade FOSS
Procedimento de detecção proposto no NixOS
- Se o
xzfor reproduzível bit a bit e o tarball do mantenedor não contiver mudanças que afetem o processo de build, então o build a partir do tarball do GitHub e o build a partir do tarball do mantenedor deveriam gerar o mesmo resultado - O método proposto é compilar o
xzmais uma vez depois do bootstrap, mas desta vez usandofetchFromGitHubcom o arquivo de código-fonte do GitHub, e comparar o resultado com o artefato dexzproduzido na etapa existente de bootstrap - Um exemplo de implementação coloca
after-bootstrap.nixempkgs/tools/compression/xze compila oxz5.6.1 a partir do código-fonte do GitHub- o tarball do mantenedor já contém o script
configure, mas no build a partir do código do GitHub é preciso executar./autogen.shusandoautoconf,libtool,automakeetc.
- o tarball do mantenedor já contém o script
- Como etapa de comparação, adiciona-se
compareArtifactspara compararliblzma.soao final do build- no exemplo, foi comparado apenas
liblzma.so, o arquivo modificado pelo backdoor, mas isso pode ser generalizado para todos os binários e bibliotecas gerados
- no exemplo, foi comparado apenas
- Em uma revisão normal da branch master, o build
xz-after-bootstrapfoi concluído com sucesso e os dois resultados eram idênticos - Na revisão com o backdoor
c53bbe3, a princípio tudo parecia igual- no NixOS, o backdoor não era ativado, e um dos motivos é que o script de instalação verificava se a variável
RPM_ARCHestava definida - ao configurar
env.RPM_ARCH = true;para ativar a condição, a diferença binária emliblzma.soapareceu
- no NixOS, o backdoor não era ativado, e um dos motivos é que o script de instalação verificava se a variável
- Ao salvar e comparar os dois
liblzma.so, o artefato com backdoor tinha 258KB, enquanto o artefato compilado depois do bootstrap a partir do código do GitHub tinha 210KB, uma diferença de cerca de 48KB - Ao comparar os símbolos com
nm, apareceram_cpuid,_get_cpuide__tls_get_addr@GLIBC_2.3, e_get_cpuidé um símbolo documentado em vários relatórios técnicos sobre o backdoor doxz
Aplicação prática e limitações
- A proteção proposta consiste em aplicá-la a todos os pacotes construídos na etapa de bootstrap que não usem um arquivo de código-fonte confiável
- Ao adicionar pacotes
*-after-bootstrapcomo bloqueadores de canal, uma falha de build pode se tornar um alerta importante que exige intervenção de mantenedores - Como prova de conceito para o caso do
xz, foi aberto um pull request no repositórionixpkgs - Esse método exige a suposição de que o pacote alvo seja reproduzível bit a bit
- de 2017 a 2023, foram amostradas 17 revisões de
nixpkgs-unstable, recompilando comnix-build --checkas derivações de saída não fixa destdenv - em todas as revisões,
xzera reproduzível bit a bit - em 12 das 17 revisões havia 1 ou 2 pacotes que compilavam, mas não eram reproduzíveis; por exemplo,
gccfoi consistentemente não reproduzível de 2017 a 2021, ebashaté 2019
- de 2017 a 2023, foram amostradas 17 revisões de
- Não é viável aplicar esse método a todos os pacotes de
stdenv, mas no longo prazo ele pode ser ativado seletivamente para pacotes com boa reprodutibilidade - O problema trusting trust de Ken Thompson continua sendo um limite teórico dessa abordagem
- é preciso assumir que um
xznão confiável da etapa de bootstrap não conseguiria contaminar indiretamente o build dexz-after-bootstrapa ponto de fazer os artefatos parecerem idênticos - esse tipo de ataque pode ser extremamente complexo, mas o método parte dessa suposição
- é preciso assumir que um
- Esse método de detecção só funciona quando a alteração no tarball afeta o artefato final
- como os executáveis do NixOS não continham o backdoor, este caso específico não teria sido detectado se a condição não tivesse sido ativada separadamente
1 comentários
Opiniões no Hacker News
Vale destacar que o NixOS e os builds reproduzíveis não detectaram o backdoor do xz. Na prática, o NixOS também distribuiu aos usuários builds maliciosos do xz; o código malicioso só não funcionou porque não mirava o NixOS
Um desenvolvedor do NixOS também disse: “quando o backdoor veio a público, fiquei surpreso ao saber que uma versão maliciosa do xz havia sido distribuída aos nossos usuários”. Como sempre, teoria e realidade são coisas diferentes, e o que tornou o xz possível não foi tanto uma vulnerabilidade técnica, mas sim vulnerabilidades humanas do mundo real. A comunidade tem dificuldade em admitir que nem sempre é possível corrigir esse tipo de problema apenas com software melhor
Se todos os processos puderem ser executados com permissões limitadas, sem acesso nem a
~/exceto aos diretórios necessários para a tarefa, seria possível impedir, por exemplo, que um pacotepipmalicioso roubasse chaves SSH. Ainda assim, acho que o motivo de o backdoor do xz não ter funcionado no NixOS foi a peculiar estrutura de sistema de arquivos não FHS do NixOSHoje em dia, a realidade concreta é frequentemente desdenhada quando não combina com os sentimentos ou valores pessoais de alguém sobre a realidade objetiva. Tenho meus valores pessoais, mas não acho que a realidade concreta seja menos importante do que meus sentimentos em relação a ela. Como eu gostava de dizer, a diferença entre teoria e prática é que, em teoria, as duas são iguais, mas na prática não são. Espero que a constatação de que os builds reproduzíveis do NixOS poderiam ter detectado o ataque ao xz, mas não detectaram, leve a avanços na análise desses builds para encontrar outros ataques mais rapidamente no futuro
Tenho a impressão de que o autor ficou com uma visão estreita demais por causa da forma como isso aconteceu por acaso desta vez. O caso Jia Tan é uma amostra única, então é míope achar que só aquele método é possível
Dá para imaginar muitos cenários em que a defesa proposta aqui não funcionaria. Mesmo como usuário do Nix, acho pouco provável que o NixOS tivesse detectado isso. Até porque, na prática, não detectou. Eu acabei de dizer que da próxima vez pode acontecer de outro jeito, mas também é tolice confiar no Nix sem evidências
Aqui o NixOS não é muito relevante. O backdoor do xz mirava especificamente Red Hat e Debian
Pela mesma lógica, seria igualmente relevante dizer que o backdoor do xz não afetou o Windows. Ironicamente, esse backdoor acabou sendo descoberto por um funcionário da Microsoft, um ponto que costuma ser ignorado
O texto diz que as distribuições deveriam obter o código-fonte diretamente de um sistema de controle de versão, por exemplo do Github, em vez de usar os tarballs tradicionais de instalação
Mas não entendo bem o que isso resolve. Um mantenedor mal-intencionado não poderia simplesmente adicionar um bloco binário diretamente ao repositório de código-fonte? O autor parece confiar no Github como se ele verificasse o código, mas é claro que o Github não faz essa verificação
Builds reproduzíveis verificados poderiam ter ajudado a evitar casos como o comprometimento do xz utils e a adulteração do SolarWinds Orion, e isso vale bastante o esforço
Se você baixa um binário de um release do Github e o executa, só resta confiar totalmente no mantenedor. O Nix só faz isso para pacotes de código fechado
O argumento se apoia no fato de que o mantenedor do XZ escondeu código malicioso em um tarball que não havia sido registrado no Git
O autor mostra que o Nix pode ser configurado para gerar um tarball a partir do Git e colocá-lo no build binário. Mas não entendo por que isso seria um recurso que exige Nix ou NixOS. Qualquer sistema de build, incluindo os usados em RPMs ou Debs, pode ser configurado para gerar um tarball como etapa intermediária. De fato, o Debian trabalha há muito tempo com builds reproduzíveis como uma meta importante. https://wiki.debian.org/ReproducibleBuilds
Primeiro, o processo de build do NixOS precisava do xz cedo demais no bootstrap para conseguir fazer um build completo a partir do código-fonte do xz. Segundo, ele propõe ajustes no nixpkgs para detectar automaticamente se dependências do nixpkgs necessárias no início do bootstrap foram comprometidas. Outros ecossistemas, claro, também podem tentar builds completos a partir do código-fonte e encontrar divergências. O ponto central do texto é que o nixpkgs atualmente não consegue fazer isso
Se for para focar em um caso que o NixOS poderia ter evitado, é melhor olhar para o incidente da CrowdStrike. Só o fato de poder inicializar com a configuração de ontem quando a de hoje não funciona já teria mitigado a maior parte dos danos
Se você usa um framework em que confia, ele é seguro até o momento em que esse framework for atacado. O backdoor do xz pode até ter sido descoberto, mas não foi projetado com o objetivo de funcionar no ecossistema Nix.
Algum dia, se um desenvolvedor central do Nix for um espião ou algo parecido acontecer, também surgirão ataques voltados ao ecossistema Nix. Eu preferiria que não respondessem como se o Nix fosse intrinsecamente seguro. Se o Nix for atacado com sucesso em um ou dois anos, vai dar vontade de ir atrás e fazer reconhecerem que estavam errados
A afirmação mais forte é: “o Nix é mais difícil e mais caro de atacar do que sistemas de build tradicionais”. Portanto, se você encontrar uma forma barata de atacar o Nix, aí sim venha falar. Até lá, no nível técnico, dizer que o Nix é mais difícil de atacar do que sistemas alternativos é no mínimo plausível e, na prática, muito provavelmente verdadeiro
O NixOS vai um pouco além, mas a maioria das outras distribuições também compila tudo a partir do código-fonte, verifica criptograficamente que o código-fonte usado não foi adulterado e tem dependências com versões especificadas entre pacotes. O Debian também tem builds reprodutíveis.
O problema é que o sistema de build não removia os arquivos-objeto pré-compilados antes de compilar a partir do código-fonte. Mesmo corrigindo isso, se ninguém inspecionar o código-fonte, é possível adicionar quantos backdoors quiser; nem o NixOS nem outras distribuições conseguem impedir isso
É uma excelente análise técnica, mas o título está errado e induz ao erro. Mesmo que se possa dizer que “tecnicamente está correto”, na melhor das hipóteses ele está mais próximo de significar que um backdoor foi inserido.
Isso mostra bem a necessidade de ferramentas de gerenciamento de build que vão além das camadas de filesystem em união. Por exemplo, testes devem ser rastreados e impedidos de contaminar os artefatos de build. É preciso criar um grafo de rastreamento causal de como arquivos afetam arquivos durante o processo de build, tornar esse grafo explícito e então aplicá-lo ou relatar diferenças em relação ao grafo de rastreamento anterior
Certo. Esconder o backdoor certamente teria ficado mais difícil. Mas está longe de ser impossível.
Se quiser, sempre dá para esconder um backdoor dentro do código-fonte. Apenas exige mais esforço para fazê-lo parecer um bug plausível, e a chance de ser detectado também fica maior