TL; DR
En Maven Central-artefakt publicerad som io.github.davidtimur:c2-lab levererade nio utgåvor, varav åtta exekverade en fjärråtkomstnyttolast under kompileringen av något nedströmsprojekt som placerade burken på sin annoteringsprocessor-sökväg. Ingen applikationskod behövde importera den. Ingen källkodsrad behövde referera till den. Det fanns inget installationsskript, ingen motsvarighet efter installationen och ingen livscykelkrok av något slag för verktyg att inspektera.
Exekveringsvektorn är en enda 46-byte-fil inuti jar-filen: en Java-tjänsteleverantörsregistrering som namnger en klass som implementerar javax.annotation.processing.ProcessorJava-kompilatorn upptäcker sådana registreringar automatiskt. När de väl hittats, javac instansierar och kör klassen som en normal del av kompileringen. Det betyder att nyttolastens körtidsmiljö är byggmaskinen, just nu när byggmaskinen gör det enda den existerar för att göra.
Över de nio utgåvorna byggdes kommando- och kontrollkanalen om tre gånger: en operatörslevererad callback-URL, sedan ett omvänt skal över en ngrok TCP-tunnel, och sedan en HTTP-pollingkanal vars sökvägar ändrades ytterligare två gånger. Den slutliga utgåvan installerade en no-op TLS-förtroendehanterare som JVM-standard, accepterar alla certifikat under kompileringens gång.
Artefakten bar orden "C2 Lab Payload" i sin egen POM och en MIT-licens. Den var aktiv på Maven Central under hela den period vi observerade den och har sedan dess tagits bort, tillsammans med hela dess io.github.davidtimur grupp.
Anatomi: kompilatorn som exekveringsmotor
Javas ramverk för annoteringsbehandling finns så att bibliotek kan generera kod vid kompileringstid – mekanismen bakom Lombok, Dagger och en lång lista med ORM- och serialiseringsverktyg. En processor tillkännager sig själv med en vanlig textfil inuti burken:
META-INF/tjänster/javax.annotation.processing.Processor
Innehållet i den filen i varje berörd utgåva, ordagrant och fullständigt:
io.github.davidtimur.c2lab.C2Processor
Filen är byte-identisk mellan versionerna 1.0.1 till 1.0.8, md5 7d2a08a5c8869a47eea9fa62487dfbe4. Version 1.0.0 innehåller den inte.
När javanesiska När den körs skannar den annoteringsprocessorns sökväg efter dessa tjänstposter och laddar vad den hittar. Ingenting i projektet som kompileras behöver nämna processorn, annotera något eller konfigurera något. Närvaro på sökvägen är tillräckligt. Detta är egenskapen som skiljer JavacDoor från de leveranskedjemönster som de flesta verktyg är byggda kring:
| Mönster | Trigger | Synlig som |
|---|---|---|
| npm installationskrok | npm install | scripts.postinstall i manifestet |
| Python-nyttolast vid importtid | första importen av modulen | modulnivåsats i källkod |
| JavacDoor | javac på alla nedströmsprojekt | ett filnamn för tjänstregistrering |
En installationshook är en deklaration i ett manifest, och ett manifest är det första någon läser. En nyttolast vid importtid finns åtminstone i en läsbar källkod. En tjänstregistrering är ingetdera: det är ett filnamn plus en rad som namnger en klass, och beteendet finns i kompilerad bytekod en katalog bort.
Nyttolastklassen utför själv värdspaning genom att skicka ut signaler. De kompilerade klassernas konstantpooler innehåller / Bin / sh, whoami, uname -a, pwd på Unix-vägen och tasklist på Windows-sökvägen, tillsammans med omdirigeringsfelström för att sammanfoga underprocessens felutdata med den infångade strömmen. Två JSON-mallar överför resultat från värden — en registreringsbeacon:
{"host":"%s","os":"%s","user":"%s","dir":"%s"}
befolkad från hostname kommandot och os.namn, user.nameoch användar.dir systemegenskaper och ett resultatåteranrop:
{"version":"%s","host":"%s","time":"%s","output":"%s"}
Förloppsmarkörerna som lämnas kvar i kompilatorns egen utdata är ovanligt tydliga: [C2] kompileringstidskörning klar, [C2] återuppringning skickad → HTTP , [C2] skal anslutet till .
Nio utgåvor, tre C2-generationer
Utgåvorna är inte nio kopior av en nyttolast. De är en iterationslogg, och om man läser dem i ordning visas att kanalen byggs om medan leveransvektorn förblev fixerad.
| Släpp | Autokörs vid kompilering | Kanal |
|---|---|---|
1.0.0 | nej (ingen servicefil) | Endast återuppringnings-URL som tillhandahålls av operatören |
1.0.1 | ja (vektor introducerad) | Operatörslevererad återuppringnings-URL |
1.0.2 | ja | Omvänt skal, ngrok TCP-tunnel |
1.0.3 | ja | HTTP-kanal, /register /cmd /out |
1.0.4 | ja | HTTP-kanal, /register /cmd /out |
1.0.5 | ja | HTTP-kanal, /register /poll /out |
1.0.6 | ja | HTTP-kanal, /register /poll /out |
1.0.7 | ja | HTTP-kanal, /register /cmd |
1.0.8 | ja | HTTP-kanal + TLS-validering inaktiverad |
Tre detaljer i den tabellen är värda att lyfta fram, eftersom var och en förändrar hur artefaktmängden ska läsas.
Vektorn anländer till 1.0.1, inte 1.0.2. Version 1.0.0 har samma logik för rekognoscering och återanrop men har ingen servicefil och inga importer för annoteringsbehandling; den körs bara om något anropar den. Från och med 1.0.1 finns servicefilen och de kompilerade klasserna importeras. javax.annotation.processing.StöddKällversionVarje bedömning som jämför de två biljetterade utgåvorna – 1.0.0 och 1.0.2 – drar korrekt slutsats att något har ändrats, men identifierar felaktigt var.
Det omvända skalet finns i exakt en utgåva. Version 1.0.2 innehåller 0.tcp.ngrok[.]io, logglinjen [C2] skal anslutet till 0.tcp.ngrok[.]io:19823, en interaktiv banner c2-skaloch en ramterminator __AVSLUTA__Version 1.0.3 och senare innehåller inget av detta och når istället en HTTPS-värd. En borttagningsbegäran som endast namnger de biljetterade versionerna skulle ha citerat en död TCP-slutpunkt samtidigt som den aktiva HTTP-kanalen, som finns i sex senare versioner, lämnats oomnämnd.
Den slutliga utgåvan tar bort transportverifiering. Version 1.0.8 lägger till en klass som implementerar javax.net.ssl.X509TrustManager vars certifikatkontrollmetoder inte gör någonting, en alltid-sant värdnamnsverifierare registrerad via setDefaultHostnameVerifierOch en lita på allt rutin som installerar båda som JVM:s standardvärden. Effekten blir att JVM:n för resten av den kompileringen accepterar vilket certifikat som helst från vilken värd som helst – inte bara för nyttolastens egen trafik, utan för allt annat som bygget gör över TLS efteråt.
HTTP-kanalen, i alla sex utgåvor som använder den, leds av en enda värd: tableful-fervor-crazed.ngrok-free[.]devVarje begäran har rubriken ngrok-skip-browser-varning, vilket undertrycker den mellanliggande sidan som gratis ngrok-tunnlar visar webbläsare. Sökvägsuppsättningar ändras mellan utgåvor — /ut försvinner vid 1.0.7, och /cmd växlar med /röstning — men värden ändras aldrig.
Självuteslutningen berättar
Varje POM från och med 1.0.1 anger ett kompilatorargument för artefaktens egen build:
-proc:ingen
Den flaggan inaktiverar annoteringsbehandling. Dess effekt här är förinställd.cise: när projektet som innehåller processorn i sig kompileras, körs inte processorn.
Denna flagga har helt vanliga användningsområden. Ett projekt som levererar en annoteringsprocessor behöver ofta undvika att tillämpa den processorn på sig själv under bootstrap, och dokumentationen för byggverktyget rekommenderar just detta. Ensamt bevisar det ingenting.
Tillsammans med vad processorn gör beskriver det dock en specifik asymmetri: koden exekveras på maskinerna för alla som kompilerar mot artefakten, och inte på maskinen som bygger artefakten. Flaggan visas i samma utgåva som introducerar servicefilen – 1.0.1 – och i varje utgåva efter den. Korrelationen mellan "utgåvan där kompileringstidskörningen börjar" och "utgåvan där kompileringstidskörningen är avstängd lokalt" är den enskilt mest användbara analytiska signalen i artefaktuppsättningen, och den är synlig i en vanlig text-POM utan att dekompilera någonting.
Vi noterar effekten och stannar där. Ingenting i artefakterna talar för varför flaggan sattes.
Ytterligare en metadatabit förtjänar att nämnas, främst för att bli av med den. POM döper projektet till "C2 Lab Payload", beskriver det som en "C2 lab payload artefakt" och licensierar det till MIT. Självmärkning av detta slag erbjuds ibland som bevis på att ett paket är en forskningsövning.cissnarare än ett levande hot, och ibland är den läsningen korrekt – en deklarerad kanariefågel utan nåbar infrastruktur är ett annat objekt än detta. Det gäller inte här. Ett funktionellt implantat som publiceras till ett offentligt arkiv, nåbart för alla konsumenter, med utgående infrastruktur som har byggts om tre gånger under nio utgåvor, är en levande funktion oavsett vad dess metadata kallar den. Namnet i POM ändrar ingenting om vad som händer på en maskin som kompilerar mot den.
Indikatorer för byggmaskiner
Om en byggvärd kompilerade mot denna artefakt, finns bevisen i byggloggar och nätverkstelemetri snarare än i ett beständigt implantat på disk – nyttolasten körs inuti kompileringsprocessen och avslutas med den.
I burken eller den lokala databascachen
META-INF/services/javax.annotation.processing.Processornamngivningio.github.davidtimur.c2lab.C2Processor- Servicefil md5
7d2a08a5c8869a47eea9fa62487dfbe4 - Klasser under
io/github/davidtimur/c2lab/:C2Processor,C2Task,Task, och i 1.0.8 den inre klassenTask$1
Utdata i byggprocessen
[C2] compile-time execution complete[C2] callback sent → HTTP[C2] callback failed:[C2] shell connected to- Interaktiv banner
c2-shell; ramterminator__END__
Telemetri under processen
javacsom förälder till/bin/sh -c(Unix) eller en Windows-kommandotolk- Barnkommandon
whoami,uname -a,pwd(Unix) ellertasklist(Windows) överordnat av ett kompileringssteg
I nätverkstelemetri
- Utgående TCP till
0.tcp.ngrok[.]io:19823(version 1.0.2) - HTTPS till
tableful-fervor-crazed.ngrok-free[.]dev, stigar/register,/cmd,/poll,/out(versionerna 1.0.3 till 1.0.8) - Begäransrubrik
ngrok-skip-browser-warning: true - Begär matchning av organ
{"host":...,"os":...,"user":...,"dir":...}or{"version":...,"host":...,"time":...,"output":...}
I konfigurationen
- Miljövariabler
CALLBACK,CALLBACK_URLsystemegenskapcallback.url
Utgivarens metadata
- Grupp
io.github.davidtimurutgivarens adressdavudboi999@gmail[.]comsigneringsnyckelE520C345EF94423D
En version som körde version 1.0.8 kräver en extra kontroll. Eftersom den versionen installerar en tillåtande förtroendehanterare som standard för hela JVM, fortsatte alla TLS-anslutningar som gjordes senare i samma JVM – beroendelösning, artefaktuppladdning, ett distributionssteg – utan certifikatvalidering. Trafik från det fönstret bör inte antas vara autentiserad.
Varför kompilerade artefakter behöver olika skanningar
JavacDoor är ett användbart testfall eftersom det motbevisar två vanliga antaganden samtidigt, och inget av felen är specifikt för någon enskild leverantörs verktyg.
Det första antagandet är att farlig kod tillkännager sig själv i ett manifest. En stor del av leveranskedjans verktyg är organiserade kring livscykeln hooks, eftersom det för npm och PyPI är där åtgärden vanligtvis är. JavacDoor har ingen hook. Dess trigger är en tjänstregistreringsfil vars namn är ett Java-gränssnitt och vars innehåll är ett klassnamn. För att fånga det statiskt måste du behandla META-INF/tjänster/javax.annotation.processing.Processor som en egen ingångspunkt för exekvering, i nivå med en efterinstallation skript — och följ sedan den namngivna klassen in i bytekoden. Ekosystem har sina egna automatiska upptäcktsmekanismer av denna form, och var och en är en ingångspunkt oavsett om verktyg räknar upp den som en eller inte.
Det andra antagandet är att strängar finns i källfiler. För en jar finns slutpunkterna, shell-kommandona, JSON-mallarna och loggmarkörerna alla i de konstanta poolerna av .klass filer. Verktyg som använder greps för text hittar ingenting – inte för att strängarna är obfuskerade, utan för att de finns i en strukturerad binär behållare som en textskanning inte analyserar. Varje nätverksindikator i det här inlägget kom från constant-pool-parsing. En av dem är en fullständigt formaterad [fil/etc.]. https:// URL:en visas tydligt i en klassfil; en textsökning av burkens läsbara innehåll skulle fortfarande inte visa den. Det finns ingen kodning att besegra här, bara ett containerformat att läsa.
Båda luckorna har samma form: ett artefaktformat behandlades som en påse med filer snarare än som en struktur med definierad semantik. Korrigeringen är oglamorös – analysera behållaren, räkna upp ekosystemets egna ingångspunkter för automatisk upptäckt och följ dem in i kompilerad kod. Specifikt för byggtidsvektorer är en tredje kontroll billig och förvånansvärt diagnostisk: jämför vad en artefakt gör mot konsumenter mot vad den undantar sig själv från. En artefakt som registrerar en kompileringsprocessor och samtidigt inaktiverar kompileringstidsbearbetning för sin egen build har berättat något om sig själv i två rader POM i klartext.
För team som konsumerar Maven-artefakter idag följer tre praktiska åtgärder:
- Behandla annoteringsprocessorns sökväg som en exekveringsgräns. Beroenden som hamnar där kör kod i din build. Där en build inte behöver annoteringsbehandling, -proc:ingen är lika användbart defensivt som det tydligen var lokalt här; där det gör det, fäster det processoruppsättningen explicit istället för att ärva den från kompileringsklassvägen.
- Delprocesser för loggkompilatorn. Ett kompileringssteg som skapar ett skal är avvikande i de flesta projekt och är trivialt varningsbart.
- Behandla inte metadata som vittnesmål. ”Lab”, ”test”, ”nyttolast” och ”PoC” i ett paketnamn eller en beskrivning är inte omfattningsgränser. Nåbarhet och beteende är det.
Artefakten och hela dess grupp togs bort från Maven Central följer vår rapport; både artefaktsökvägen och gruppsökvägen returnerar nu 404, och Central-indexet rapporterar inga matchande koordinater. Det stänger denna artefakt. Det stänger inte vektorn, vilket är en dokumenterad funktion i Java-kompilatorn och tillgänglig för alla som publicerar en jar-fil.
Referensprojekt
Det här inlägget citerar inga externa källor. Alla fynd är förstahandsanalys av de nio publicerade burkarna, hämtade från Maven Central innan de togs bort. Ingen kod från artefakterna kördes vid någon tidpunkt.







