JavacDoor, malware Maven che si esegue in fase di compilazione

JavacDoor: un artefatto Maven eseguito durante la compilazione, non durante l'installazione.

TL; DR

Un artefatto di Maven Central pubblicato come io.github.davidtimur:c2-lab Sono state rilasciate nove versioni, otto delle quali eseguivano un payload ad accesso remoto durante la compilazione di qualsiasi progetto a valle che avesse inserito il file jar nel percorso del processore di annotazioni. Nessun codice applicativo doveva importarlo. Nessuna riga di codice sorgente doveva farvi riferimento. Non era presente alcuno script di installazione, nessun equivalente post-installazione e nessun tipo di hook del ciclo di vita che gli strumenti potessero ispezionare.

Il vettore di esecuzione è un singolo file di 46 byte all'interno del jar: una registrazione del provider di servizi Java che nomina una classe che implementa javax.annotation.processing.Processor. Il compilatore Java rileva automaticamente tali registrazioni. Una volta trovate, javac Istanzia ed esegue la classe come parte normale del processo di compilazione. Ciò significa che l'ambiente di runtime del payload è la macchina di compilazione, e in quel momento la macchina di compilazione sta svolgendo l'unica funzione per cui è stata creata.

Nel corso delle nove release, il canale di comando e controllo è stato ricostruito tre volte: un URL di callback fornito dall'operatore, poi una reverse shell su un tunnel TCP ngrok, quindi un canale di polling HTTP i cui percorsi sono cambiati altre due volte. La release finale ha installato un gestore di fiducia TLS senza operazioni come predefinito della JVM, accettando qualsiasi certificato per la durata della compilazione.

L'artefatto riportava le parole "C2 Lab Payload" nel proprio POM e una licenza MIT. Era attivo su Maven Central per tutto il periodo in cui lo abbiamo osservato e è stato successivamente rimosso, insieme a tutto il suo io.github.davidtimur gruppo.

Anatomia: il compilatore come motore di esecuzione

Il framework di elaborazione delle annotazioni di Java esiste per consentire alle librerie di generare codice in fase di compilazione: è il meccanismo alla base di Lombok, Dagger e di una lunga lista di strumenti ORM e di serializzazione. Un processore si annuncia con un file di testo semplice all'interno del file JAR:

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

Il contenuto di quel file in ogni versione interessata, parola per parola e per intero:

io.github.davidtimur.c2lab.C2Processor

Il file è identico byte per byte nelle versioni dalla 1.0.1 alla 1.0.8, md5 7d2a08a5c8869a47eea9fa62487dfbe4. La versione 1.0.0 non lo contiene.

Quando javac Quando viene eseguito, analizza il percorso del processore di annotazioni alla ricerca di queste voci di servizio e carica ciò che trova. Nessun elemento del progetto in fase di compilazione deve menzionare il processore, annotare nulla o configurare nulla. La sua presenza nel percorso è sufficiente. Questa è la caratteristica che distingue JavacDoor dai modelli di supply chain su cui si basano la maggior parte degli strumenti:

CartamodellogrillettoVisibile come
npm install hooknpm installscripts.postinstall nel manifesto
Payload di importazione Pythonprima importazione del modulodichiarazione a livello di modulo nel codice sorgente
JavacDoorjavac su qualsiasi progetto a vallenome file di registrazione del servizio

Un hook di installazione è una dichiarazione in un manifest, e un manifest è la prima cosa che chiunque legge. Un payload in fase di importazione, almeno, si trova in un codice sorgente leggibile. La registrazione di un servizio non è né l'una né l'altra: è un nome di file più una riga che specifica il nome di una classe, e il comportamento risiede nel bytecode compilato in una directory diversa.

La classe payload stessa esegue la ricognizione dell'host tramite shell. I pool di costanti delle classi compilate contengono /bin/sh, whoami, uname -a, pwd sul percorso Unix e tasklist sul percorso di Windows, insieme a reindirizzaErrorStream per unire l'output di errore del processo figlio al flusso acquisito. Due modelli JSON trasportano i risultati fuori dall'host: un segnale di registrazione:

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

popolato dal hostname comando e il nome del sistema operativo, user.namee directory utente proprietà di sistema e una funzione di callback per il risultato:

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

Gli indicatori di avanzamento lasciati nell'output del compilatore sono insolitamente espliciti: [C2] esecuzione in fase di compilazione completata, [C2] callback inviato → HTTP , [C2] shell connessa a .

Nove versioni, tre generazioni C2

Le release non sono nove copie di un singolo payload. Sono un registro delle iterazioni e, leggendole in ordine, si può osservare la ricostruzione del canale mentre il vettore di consegna rimane invariato.

RilasciareEsegue automaticamente in fase di compilazionecanale
1.0.0no (nessun file di servizio)URL di callback fornito solo dall'operatore
1.0.1sì (vettore introdotto)URL di callback fornito dall'operatore
1.0.2sìShell inversa, tunnel TCP ngrok
1.0.3sìCanale HTTP, /registrazione /comando /uscita
1.0.4sìCanale HTTP, /registrazione /comando /uscita
1.0.5sìCanale HTTP, /registrazione /sondaggio /uscita
1.0.6sìCanale HTTP, /registrazione /sondaggio /uscita
1.0.7sìCanale HTTP, /registrazione /comando
1.0.8sìCanale HTTP + convalida TLS disabilitata

Tre dettagli presenti in quella tabella meritano di essere evidenziati, poiché ognuno di essi modifica il modo in cui l'insieme degli artefatti dovrebbe essere interpretato.

Il vettore arriva a 1.0.1, non a 1.0.2. La versione 1.0.0 contiene la stessa logica di ricognizione e di callback ma non ha file di servizio né importazioni per l'elaborazione delle annotazioni; viene eseguita solo se qualcosa la chiama. Dalla versione 1.0.1 il file di servizio è presente e le classi compilate importano javax.annotation.processing.SupportedSourceVersionQualsiasi valutazione che confronti le due versioni a pagamento — 1.0.0 e 1.0.2 — conclude correttamente che qualcosa è cambiato, ma non identifica dove.

La shell inversa esiste in una sola versione. La versione 1.0.2 contiene 0.tcp.ngrok[.]io, la riga di registro [C2] shell connessa a 0.tcp.ngrok[.]io:19823, un banner interattivo c2-shelle un terminatore di frame __FINE__Le versioni dalla 1.0.3 in poi non contengono nessuno di questi elementi e si connettono invece a un host HTTPS. Una richiesta di rimozione che menzionasse solo le versioni con ticket avrebbe citato un endpoint TCP non funzionante, tralasciando il canale HTTP attivo, presente in sei versioni successive.

La versione finale rimuove la verifica del trasporto. La versione 1.0.8 aggiunge una classe che implementa javax.net.ssl.X509TrustManager i cui metodi di verifica dei certificati non fanno nulla, un verificatore di nome host sempre vero registrato tramite setDefaultHostnameVerifier, e un trustAll routine che installa entrambi come predefiniti della JVM. L'effetto è che per il resto della compilazione, la JVM accetta qualsiasi certificato da qualsiasi host, non solo per il traffico del payload stesso, ma anche per qualsiasi altra operazione che la build esegue successivamente tramite TLS.

Il canale HTTP, in tutte e sei le versioni che lo utilizzano, è gestito da un singolo host: tableful-fervor-crazed.ngrok-free[.]devOgni richiesta contiene l'intestazione ngrok-skip-browser-warning, che sopprime la pagina interstiziale che i tunnel ngrok gratuiti forniscono ai browser. I set di percorsi cambiano tra le versioni — /fuori scompare alla versione 1.0.7 e /cmd alterna con /sondaggio — ma l'ospite non cambia mai.

L'autoesclusione racconta

Ogni file POM dalla versione 1.0.1 in poi imposta un argomento del compilatore per la compilazione dell'artefatto stesso:

-proc: nessuno

Quel flag disabilita l'elaborazione delle annotazioni. Il suo effetto qui è precise: quando il progetto contenente il processore viene compilato, il processore non viene eseguito.

Questo flag ha usi del tutto ordinari. Un progetto che include un processore di annotazioni spesso deve evitare di applicarlo a se stesso durante il bootstrap, e la documentazione degli strumenti di compilazione raccomanda proprio questo. Di per sé non dimostra nulla.

Considerato insieme a ciò che fa il processore, questo descrive una specifica asimmetria: il codice viene eseguito sulle macchine di tutti coloro che compilano utilizzando l'artefatto, e non sulla macchina che crea l'artefatto stesso. Il flag compare nella stessa release che introduce il file di servizio — la 1.0.1 — e in ogni release successiva. La correlazione tra "la release in cui inizia l'esecuzione in fase di compilazione" e "la release in cui l'esecuzione in fase di compilazione viene disattivata localmente" è il segnale analitico più utile dell'intero set di artefatti ed è visibile in un file POM in formato testo semplice senza bisogno di decompilare nulla.

Osserviamo l'effetto e ci fermiamo lì. Nessun reperto spiega il motivo per cui la bandiera è stata issata.

Un ulteriore elemento di metadati merita di essere menzionato, principalmente per poi eliminarlo. Il file POM chiama il progetto "C2 Lab Payload", lo descrive come un "artefatto payload del laboratorio C2" e lo rilascia con licenza MIT. L'auto-etichettatura di questo tipo viene talvolta presentata come prova che un pacchetto è un esercizio di ricerca.cise piuttosto che una minaccia reale, e a volte questa interpretazione è corretta: un canary dichiarato senza infrastruttura raggiungibile è un oggetto diverso da questo. Non si applica qui. Un impianto funzionante pubblicato in un repository pubblico, raggiungibile da qualsiasi utente, con un'infrastruttura in uscita che è stata ricostruita tre volte in nove release, è una capacità attiva indipendentemente da come viene chiamata nei suoi metadati. Il nome nel POM non cambia nulla di ciò che accade su una macchina che lo compila.

Indicatori per macchine di costruzione

Se un host di compilazione ha utilizzato questo artefatto, le prove risiedono nei log di compilazione e nella telemetria di rete, anziché in un impianto persistente su disco: il payload viene eseguito all'interno del processo di compilazione e termina con esso.

Nel file jar o nella cache del repository locale

  • META-INF/services/javax.annotation.processing.Processor di denominazione io.github.davidtimur.c2lab.C2Processor
  • md5 del file di servizio 7d2a08a5c8869a47eea9fa62487dfbe4
  • Classi sotto io/github/davidtimur/c2lab/: C2Processor, C2Task, Taske nella versione 1.0.8 la classe interna Task$1

Nell'output di compilazione

  • [C2] compile-time execution complete
  • [C2] callback sent → HTTP
  • [C2] callback failed:
  • [C2] shell connected to
  • Banner interattivo c2-shell; terminatore di frame __END__

Telemetria di processo

  • javac come genitore di /bin/sh -c (Unix) o un interprete di comandi Windows
  • Comandi per bambini whoami, uname -a, pwd (Unix) o tasklist (Windows) figlio di una fase di compilazione

Nella telemetria di rete

  • TCP in uscita verso 0.tcp.ngrok[.]io:19823 (versione 1.0.2)
  • HTTPS a tableful-fervor-crazed.ngrok-free[.]dev, percorsi /register, /cmd, /poll, /out (versioni dalla 1.0.3 alla 1.0.8)
  • Intestazione della richiesta ngrok-skip-browser-warning: true
  • Richiedi corpi corrispondenti {"host":...,"os":...,"user":...,"dir":...} or {"version":...,"host":...,"time":...,"output":...}

In configurazione

  • Variabili ambientali CALLBACK, CALLBACK_URLproprietà di sistema callback.url

Metadati dell'editore

  • Gruppo io.github.davidtimurindirizzo dell'editore davudboi999@gmail[.]comchiave di firma E520C345EF94423D

Una build che utilizza la release 1.0.8 richiede un controllo aggiuntivo. Poiché tale release installa un trust manager permissivo come impostazione predefinita a livello di JVM, qualsiasi connessione TLS stabilita successivamente nella stessa JVM (risoluzione delle dipendenze, caricamento di artefatti, fase di distribuzione) procede senza convalida del certificato. Il traffico proveniente da tale finestra temporale non deve essere considerato autenticato.

Perché gli artefatti compilati necessitano di una scansione diversa

JavacDoor è un caso di test utile perché confuta contemporaneamente due presupposti comuni, e nessuno dei due errori è specifico degli strumenti di un singolo fornitore.

Il primo presupposto è che il codice pericoloso si manifesti tramite un file manifest. Gran parte degli strumenti della catena di approvvigionamento sono organizzati attorno al ciclo di vita hooks, perché per npm e PyPI è lì che di solito si svolge l'azione. JavacDoor non ha un hook. Il suo trigger è un file di registrazione del servizio il cui nome è un'interfaccia Java e il cui contenuto è un nome di classe. Per catturarlo staticamente devi trattarlo META-INF/services/javax.annotation.processing.Processor come punto di ingresso dell'esecuzione a pieno titolo, alla pari con un post-installazione script — e poi seguire la classe denominata nel bytecode. Gli ecosistemi hanno i propri meccanismi di rilevamento automatico di questa forma, e ognuno è un punto di ingresso indipendentemente dal fatto che gli strumenti lo enumerano come tale.

Il secondo presupposto è che le stringhe risiedano nei file sorgente. Per un jar, gli endpoint, i comandi della shell, i modelli JSON e i marcatori di log sono tutti nei pool costanti di .classe file. Gli strumenti che analizzano il testo non trovano nulla, non perché le stringhe siano offuscate, ma perché si trovano in un contenitore binario strutturato che una scansione del testo non analizza. Ogni indicatore di rete in questo post è stato ottenuto tramite analisi del pool costante. Uno di essi è completamente formato https:// L'URL è ben visibile all'interno di un file di classe; una scansione testuale del contenuto leggibile del file JAR non lo rivelerebbe comunque. Non c'è alcuna codifica da aggirare, solo un formato contenitore da leggere.

Entrambe le lacune hanno la stessa forma: un formato di artefatto è stato trattato come un insieme di file anziché come una struttura con una semantica definita. La soluzione è poco appariscente: analizzare il contenitore, enumerare i punti di ingresso per l'individuazione automatica dell'ecosistema e seguirli nel codice compilato. Per quanto riguarda specificamente i vettori di compilazione, un terzo controllo è economico e sorprendentemente diagnostico: confrontare ciò che un artefatto fa ai consumatori con ciò da cui si esenta. Un artefatto che registra un processore in fase di compilazione e allo stesso tempo disabilita l'elaborazione in fase di compilazione per la propria build ha già comunicato qualcosa di sé in due righe di POM in testo semplice.

Per i team che oggi utilizzano artefatti Maven, ecco tre misure pratiche da adottare:

  • Considera il percorso del processore di annotazioni come un confine di esecuzione. Le dipendenze che finiscono lì eseguono codice nella tua build. Dove una build non necessita di elaborazione delle annotazioni, -proc: nessuno è utile a livello difensivo tanto quanto lo era apparentemente in locale; laddove necessario, è opportuno impostare esplicitamente il processore anziché ereditarlo dal classpath di compilazione.
  • Sottoprocessi del compilatore di log. Nella maggior parte dei progetti, una fase di compilazione che avvia una shell è anomala ed è facilmente individuabile tramite un avviso.
  • Non trattare i metadati come testimonianze. I termini "Lab", "test", "payload" e "PoC" nel nome o nella descrizione di un pacchetto non rappresentano limiti di ambito. Lo sono invece la raggiungibilità e il comportamento.

Il manufatto e tutto il suo gruppo sono stati rimossi da Maven A seguito della nostra segnalazione, sia il percorso dell'artefatto che il percorso del gruppo ora restituiscono 404 e l'indice centrale non segnala coordinate corrispondenti. Questo chiude l'artefatto. Non chiude il vettore, che è una funzionalità documentata del compilatore Java e disponibile a chiunque pubblichi un file JAR.

Referenze

Questo post non cita fonti esterne. Tutti i risultati sono frutto di un'analisi statica diretta dei nove file jar pubblicati, recuperati da Maven Central prima della loro rimozione. Nessun codice presente negli artefatti è stato eseguito in alcun momento.

sca-tools-software-strumenti-di-analisi-della-composizione
Dai priorità, risolvi e proteggi i rischi del tuo software
Crea il tuo account gratuito.
Nessuna carta di credito richiesta.

Proteggi lo sviluppo e la distribuzione del tuo software

con la suite di prodotti Xygeni