JavacDoor, um malware do Maven que é executado durante a compilação.

JavacDoor: um artefato do Maven que era executado durante a compilação, não durante a instalação.

TL, DR

Um artefato do Maven Central publicado como io.github.davidtimur:c2-lab Foram lançadas nove versões, oito das quais executavam um payload de acesso remoto durante a compilação de qualquer projeto subsequente que incluísse o arquivo JAR em seu caminho de processamento de anotações. Nenhum código de aplicativo precisava importá-lo. Nenhuma linha de código-fonte precisava referenciá-lo. Não havia script de instalação, nenhum equivalente pós-instalação e nenhum gancho de ciclo de vida de qualquer tipo para que as ferramentas pudessem inspecionar.

O vetor de execução é um único arquivo de 46 bytes dentro do arquivo JAR: um registro de provedor de serviços Java que nomeia uma classe que implementa a interface. javax.annotation.processing.ProcessorO compilador Java descobre esses registros automaticamente. Uma vez encontrados, javac Instancia e executa a classe como parte normal da compilação. Isso significa que o ambiente de execução do payload é a máquina de compilação; no momento, a máquina de compilação está fazendo a única coisa para a qual existe.

Ao longo das nove versões, o canal de comando e controle foi reconstruído três vezes: primeiro, um URL de retorno de chamada fornecido pelo operador; depois, um shell reverso sobre um túnel TCP ngrok; e, por fim, um canal de polling HTTP cujos caminhos foram alterados mais duas vezes. A versão final instalou um gerenciador de confiança TLS sem operação como padrão da JVM. aceitar qualquer certificado durante o período de compilação.

O artefato continha as palavras "C2 Lab Payload" em seu próprio POM e uma licença MIT. Ele permaneceu ativo no Maven Central durante todo o período em que o observamos. já foi removido, juntamente com todo o conteúdo. io.github.davidtimur grupo.

Anatomia: o compilador como mecanismo de execução

O framework de processamento de anotações do Java existe para que as bibliotecas possam gerar código em tempo de compilação — o mecanismo por trás do Lombok, Dagger e uma longa lista de ferramentas de ORM e serialização. Um processador se anuncia com um arquivo de texto simples dentro do arquivo JAR:

META-INF/services/javax.annotation.processing.Processor

O conteúdo desse arquivo em todas as versões afetadas, na íntegra e textualmente:

io.github.davidtimur.c2lab.C2Processor

O arquivo é idêntico em todos os bytes nas versões 1.0.1 a 1.0.8, md5 7d2a08a5c8869a47eea9fa62487dfbe4. A versão 1.0.0 não o contém.

Ao Javac Em execução, ele examina o caminho do processador de anotações em busca dessas entradas de serviço e carrega o que encontra. Nada no projeto que está sendo compilado precisa mencionar o processador, anotar nada ou configurar nada. A presença no caminho é suficiente. Essa é a propriedade que diferencia o JavacDoor dos padrões de cadeia de suprimentos em que a maioria das ferramentas se baseia:

padrãoGatilhoVisível como
gancho de instalação do npmnpm installscripts.postinstall no manifesto
Carga útil de tempo de importação do Pythonprimeira importação do módulodeclaração de nível de módulo na fonte
JavacDoorjavac em qualquer projeto subsequenteum nome de arquivo de registro de serviço

Um gancho de instalação é uma declaração em um manifesto, e um manifesto é a primeira coisa que qualquer pessoa lê. Um payload de tempo de importação, pelo menos, reside em código-fonte legível. Um registro de serviço não é nenhum dos dois: é um nome de arquivo mais uma linha nomeando uma classe, e o comportamento reside em bytecode compilado em um diretório distante.

A própria classe de carga útil realiza o reconhecimento do host executando comandos. Os conjuntos de constantes das classes compiladas contêm / Bin / sh, whoami, uname -a, pwd no caminho Unix e tasklist no caminho do Windows, juntamente com redirecionarFluxoDeErro para mesclar a saída de erro do processo filho no fluxo capturado. Dois modelos JSON transportam os resultados para fora do host — um beacon de registro:

{"host":"%s","os":"%s","user":"%s","dir":"%s"}

povoado a partir do hostname comando e o os.name, user.name e user.dir propriedades do sistema e um retorno de chamada de resultado:

{"version":"%s","host":"%s","time":"%s","output":"%s"}

Os indicadores de progresso deixados na própria saída do compilador são excepcionalmente francos: [C2] execução em tempo de compilação concluída, [C2] retorno de chamada enviado → HTTP , [C2] shell conectado a .

Nove lançamentos, três gerações C2

Os lançamentos não são nove cópias de uma mesma carga útil. Trata-se de um registro de iterações, e lê-los em ordem mostra o canal sendo reconstruído enquanto o vetor de entrega permanecia fixo.

SolteExecuta automaticamente durante a compilação.Canal
1.0.0não (nenhum arquivo de serviço)Somente URL de retorno de chamada fornecida pela operadora
1.0.1sim (vetor introduzido)URL de retorno de chamada fornecida pela operadora
1.0.2simShell reverso, túnel TCP ngrok
1.0.3simCanal HTTP, /register /cmd /out
1.0.4simCanal HTTP, /register /cmd /out
1.0.5simCanal HTTP, /register /poll /out
1.0.6simCanal HTTP, /register /poll /out
1.0.7simCanal HTTP, /register /cmd
1.0.8simCanal HTTP + Validação TLS desativada

Três detalhes dessa tabela merecem destaque, pois cada um deles altera a forma como o conjunto de artefatos deve ser interpretado.

O vetor chega a 1.0.1, e não a 1.0.2. A versão 1.0.0 mantém a mesma lógica de reconhecimento e retorno de chamada, mas não possui arquivo de serviço nem importações para processamento de anotações; ela só é executada se algo a chamar. A partir da versão 1.0.1, o arquivo de serviço está presente e as classes compiladas importam javax.annotation.processing.SupportedSourceVersionQualquer avaliação que compare as duas versões lançadas — 1.0.0 e 1.0.2 — conclui corretamente que algo mudou, mas identifica erroneamente onde.

A versão reversa do shell existe em exatamente uma versão. A versão 1.0.2 contém 0.tcp.ngrok[.]io, a linha de registro [C2] shell conectado a 0.tcp.ngrok[.]io:19823, um banner interativo concha c2e um terminador de quadro __FIM__As versões 1.0.3 em diante não contêm nenhuma dessas funcionalidades e, em vez disso, acessam um host HTTPS. Uma solicitação de remoção que mencionasse apenas as versões com problemas teria citado um endpoint TCP inativo, deixando o canal HTTP ativo, presente em seis versões posteriores, sem menção.

A versão final remove a verificação de transporte. A versão 1.0.8 adiciona uma classe que implementa javax.net.ssl.X509TrustManager cujos métodos de verificação de certificado não fazem nada, um verificador de nome de host sempre verdadeiro registrado através de definirVerificadorDeNomeDeHostPadrãoe um confie em todos rotina que instala ambos como padrões da JVM. O efeito é que, durante o restante da compilação, a JVM aceita qualquer certificado de qualquer host — não apenas para o tráfego do próprio payload, mas para qualquer outra coisa que a compilação faça via TLS posteriormente.

O canal HTTP, em todas as seis versões que o utilizam, é servido por um único host: tableful-fervor-crazed.ngrok-free[.]devCada solicitação carrega o cabeçalho. ngrok-skip-browser-warning, que suprime a página intersticial que os túneis ngrok gratuitos servem aos navegadores. Os conjuntos de caminhos mudam entre as versões — /fora desaparece na versão 1.0.7, e / cmd alterna com /votação — mas o apresentador nunca muda.

A autoexclusão diz

A partir da versão 1.0.1, todos os POMs definem um argumento do compilador para a própria compilação do artefato:

-proc:nenhum

Essa flag desativa o processamento de anotações. Seu efeito aqui é precise: quando o projeto que contém o processador é compilado, o processador não é executado.

Essa flag tem usos completamente comuns. Um projeto que inclui um processador de anotações geralmente precisa evitar aplicar esse processador a si mesmo durante a inicialização, e a documentação da ferramenta de compilação recomenda exatamente isso. Isoladamente, ela não prova nada.

Considerando também o funcionamento do processador, isso descreve uma assimetria específica: o código é executado nas máquinas de todos que compilam o artefato, e não na máquina que o constrói. O indicador aparece na mesma versão que introduz o arquivo de serviço — 1.0.1 — e em todas as versões subsequentes. A correlação entre “a versão em que a execução em tempo de compilação começa” e “a versão em que a execução em tempo de compilação é desativada localmente” é o sinal analítico mais útil no conjunto de artefatos, e é visível em um POM em texto simples sem a necessidade de descompilar nada.

Observamos o efeito e paramos por aí. Nada nos artefatos explica por que o sinalizador foi ativado.

Mais um dado de metadados merece ser mencionado, principalmente para que possamos descartá-lo. O POM nomeia o projeto como “C2 Lab Payload”, descreve-o como um “artefato de payload de laboratório C2” e o licencia sob a licença MIT. Autodenominações desse tipo às vezes são apresentadas como evidência de que um pacote é um exercício de pesquisa.cise em vez de uma ameaça ativa, e ocasionalmente essa interpretação está correta — um canário declarado sem infraestrutura acessível é um objeto diferente deste. Não se aplica aqui. Um implante funcional publicado em um repositório público, acessível a qualquer consumidor, com infraestrutura de saída que foi reconstruída três vezes em nove versões, é uma capacidade ativa, independentemente de como seus metadados o denominam. O nome no POM não altera em nada o que acontece em uma máquina que compila com ele.

Indicadores para máquinas de construção

Se um host de compilação utilizou esse artefato, as evidências ficam registradas nos logs de compilação e na telemetria de rede, em vez de um implante persistente no disco — o payload é executado dentro do processo do compilador e finalizado juntamente com ele.

No cache do repositório local ou no arquivo JAR

  • META-INF/services/javax.annotation.processing.Processor nomeando io.github.davidtimur.c2lab.C2Processor
  • Arquivo de serviço md5 7d2a08a5c8869a47eea9fa62487dfbe4
  • Aulas sob io/github/davidtimur/c2lab/: C2Processor, C2Task, Taske na versão 1.0.8 a classe interna Task$1

Na saída de compilação

  • [C2] compile-time execution complete
  • [C2] callback sent → HTTP
  • [C2] callback failed:
  • [C2] shell connected to
  • Banner interativo c2-shell; terminador de quadro __END__

Telemetria em processo

  • javac como o pai de /bin/sh -c (Unix) ou um interpretador de comandos do Windows
  • Comandos da criança whoami, uname -a, pwd (Unix) ou tasklist (Windows) subordinado a uma etapa de compilação

Em telemetria de rede

  • TCP de saída para 0.tcp.ngrok[.]io:19823 (versão 1.0.2)
  • HTTPS para tableful-fervor-crazed.ngrok-free[.]dev, caminhos /register, /cmd, /poll, /out (versões 1.0.3 a 1.0.8)
  • Cabeçalho da solicitação ngrok-skip-browser-warning: true
  • Corpos de solicitação correspondentes {"host":...,"os":...,"user":...,"dir":...} or {"version":...,"host":...,"time":...,"output":...}

Em configuração

  • Variáveis ​​ambientais CALLBACK, CALLBACK_URL; propriedade do sistema callback.url

Metadados do editor

  • Grupo io.github.davidtimur; endereço da editora davudboi999@gmail[.]com; chave de assinatura E520C345EF94423D

Uma compilação que executou a versão 1.0.8 requer uma verificação adicional. Como essa versão instala um gerenciador de confiança permissivo como padrão em toda a JVM, qualquer conexão TLS feita posteriormente na mesma JVM — resolução de dependências, upload de artefatos, etapa de implantação — prosseguiu sem validação de certificado. O tráfego proveniente desse período não deve ser considerado autenticado.

Por que os artefatos compilados precisam de uma análise diferente?

O JavacDoor é um caso de teste útil porque refuta duas suposições comuns de uma só vez, e nenhuma das falhas é específica das ferramentas de qualquer fornecedor.

A primeira suposição é que o código perigoso se anuncia em um manifesto. Grande parte das ferramentas da cadeia de suprimentos é organizada em torno do ciclo de vida. hooks, porque para npm e PyPI é onde a ação geralmente acontece. O JavacDoor não possui um gancho. Seu gatilho é um arquivo de registro de serviço cujo nome é uma interface Java e cujo conteúdo é um nome de classe. Para capturar isso estaticamente, você precisa tratar META-INF/services/javax.annotation.processing.Processor como um ponto de entrada de execução por si só, em pé de igualdade com um pós-instalação script — e então siga a classe nomeada para o bytecode. Os ecossistemas têm seus próprios mecanismos de autodescoberta com esse formato, e cada um é um ponto de entrada, independentemente de as ferramentas o enumerarem como tal.

A segunda premissa é que as strings residem nos arquivos de origem. Para um arquivo JAR, os endpoints, os comandos do shell, os modelos JSON e os marcadores de log estão todos nos pools constantes de .classe arquivos. Ferramentas que usam grep para buscar texto não encontram nada — não porque as strings estejam ofuscadas, mas porque estão em um contêiner binário estruturado que uma varredura de texto não consegue analisar. Todos os indicadores de rede neste post foram obtidos por meio da análise de pools de constantes. Um deles é um arquivo totalmente formado. https:// A URL está visível dentro de um arquivo de classe; uma leitura do conteúdo legível do arquivo JAR ainda não a revelaria. Não há codificação a ser burlada aqui, apenas um formato de contêiner a ser lido.

Ambas as lacunas têm o mesmo formato: um formato de artefato era tratado como um conjunto de arquivos em vez de uma estrutura com semântica definida. A solução é pouco glamorosa: analisar o contêiner, enumerar os pontos de entrada de autodescoberta do próprio ecossistema e segui-los até o código compilado. Especificamente para vetores de tempo de compilação, uma terceira verificação é barata e surpreendentemente diagnóstica: comparar o que um artefato faz aos consumidores com o que ele se isenta de fazer. Um artefato que registra um processador de tempo de compilação e, simultaneamente, desabilita o processamento de tempo de compilação para sua própria compilação, revela algo sobre si mesmo em duas linhas de texto simples no POM.

Para as equipes que utilizam artefatos Maven atualmente, seguem três medidas práticas:

  • Considere o caminho do processador de anotações como um limite de execução. As dependências que chegam lá executam código na sua compilação. Quando uma compilação não precisa de processamento de anotações, -proc:nenhum É tão útil defensivamente quanto aparentemente foi localmente aqui; onde for necessário, fixe o conjunto de processadores explicitamente em vez de herdá-lo do classpath de compilação.
  • Registrar subprocessos do compilador. Uma etapa de compilação que gera um shell é anômala na maioria dos projetos e pode ser facilmente detectada por meio de um alerta.
  • Não considere os metadados como depoimentos. Os termos “Laboratório”, “teste”, “carga útil” e “PoC” no nome ou na descrição de um pacote não são limites de escopo. Acessibilidade e comportamento, sim.

O artefato e todo o seu conjunto foram removidos de Maven Após nosso relatório, o Central informou que tanto o caminho do artefato quanto o caminho do grupo agora retornam 404, e o índice do Central não reporta coordenadas correspondentes. Isso encerra este artefato. No entanto, não encerra o vetor, que é um recurso documentado do compilador Java e disponível para qualquer pessoa que publique um arquivo JAR.

Referências

Esta postagem não cita fontes externas. Todas as conclusões são baseadas em análises estáticas diretas dos nove arquivos JAR publicados, obtidos do Maven Central antes da remoção. Nenhum código dos artefatos foi executado em nenhum momento.

sca-tools-software-composição-análise-ferramentas
Priorize, corrija e proteja seus riscos de software
Crie sua conta gratuita.
Nenhum cartão de crédito é necessário.

Proteja seu desenvolvimento e entrega de software

com o Suíte de Produtos da Xygeni