
Tiefgehende technische Analyse von CVE-2022-22965 (Spring4Shell) mit Umgebungseinrichtung, Debug-Durchlauf und Aufschlüsselung der Exploit-Kette für die Übung im Bildungslabor.
Spring4Shell ist der Name einer CVE, die im Spring Core des Spring Framework existiert.
Mit einem CVSS 3.x Score von 9.8 wird die Schwachstelle als kritisches Risiko eingestuft. Diese Schwachstelle ermöglicht es einem Angreifer, Code aus der Ferne auszuführen und den anfälligen Server zu kontrollieren.
Aufgrund der Verbreitung von Spring Core im Internet und der schwerwiegenden Auswirkungen von Spring4Shell wird diese Schwachstelle von Experten als ebenso einflussreich bewertet wie Log4shell.
Spring4Shell betrifft nicht alle Webanwendungen, die Spring Framework im Internet verwenden, sondern erfordert, dass die Webanwendung die folgenden Bedingungen erfüllt:
Die von mir eingerichtete Umgebung hat folgende Parameter:
Installation von Apache Tomcat
Wie oben erwähnt, verwende ich Kali 2021.4a und Apache Tomcat 9.0.45. Wenn Sie nicht wissen, wie man Apache Tomcat installiert, und es unter Kali Linux installieren möchten, können Sie sich diesen Link ansehen.
Hinweis: Ersetzen Sie den Link https://mirror.kiu.ac.ug/apache/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz durch https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz.
Auswahl der IDE
Wir benötigen eine IDE, um das Projekt zu codieren, als .war-Datei zu packen und – sehr wichtig – zu debuggen. Ich verwende IntelliJ, Sie können aber auch Eclipse oder Netbeans verwenden, solange die IDE Java unterstützt.
Erstellen eines einfachen Projekts mit der Schwachstelle
Mein Projekt ist sehr einfach und besteht aus:
einem Modell HelloWorld.java

einem Controller HelloWorldController.java

einer Ansicht hello.jsp

Erstellen der .war-Datei
Um das Projekt zu packen, gehen Sie wie folgt vor: Build -> Build Artifacts -> helloworld:war -> Build.
Warten Sie, bis der Build erfolgreich abgeschlossen ist. Dann erscheint ein weiterer Ordner out im Projekt. Gehen Sie zu ./out/artifacts/your_war_name/ und Sie sehen eine Datei your_war_name.war. Diese .war-Datei ist das kompilierte und gepackte Webprojekt und kann in Java-Servern wie Apache Tomcat bereitgestellt werden.
Falls Build Artifacts ausgegraut ist (nicht möglich), wurde die Build-Artifact-Konfiguration für dieses Projekt noch nicht eingerichtet. Gehen Sie zu: File -> Project Structure -> Artifacts -> Löschen aller vorhandenen Artifacts -> Add (+ ) -> Web Application: Exploded -> From Modules... -> OK (Ende der Erstellung von Exploded) > Add (+ ) -> Web Application: Archive -> For ‘helloworld:war exploded’ -> OK. Führen Sie dann Build Artifacts erneut aus.
Bereitstellung und Einrichtung des Debuggings
Bereitstellung
Um eine .war-Datei in Apache Tomcat bereitzustellen, kopieren Sie die .war-Datei einfach in das Verzeichnis /webapps im Apache-Tomcat-Ordner (z.B.: Ich kopiere die Datei helloworld.war (ich habe sie umbenannt, um sie leichter zu nennen) in das Verzeichnis /opt/tomcat/apache-tomcat-9.0.45/webapps/). Starten Sie dann den Tomcat-Server auf zwei Arten (für Linux):
Nach der Bereitstellung rufen Sie http://localhost:8080/helloworld auf.
Einrichtung des Debuggings
Um das Remote-Debugging von Tomcat einzurichten, gehen Sie wie folgt vor:
Serverseite:
Öffnen Sie die Datei catalina.sh und ersetzen Sie den Wert localhost durch die IP der VM im Parameter JPDA_ADDRESS.

Starten Sie den Tomcat-Server neu mit: /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh jpda start. Zusätzlich zum Öffnen von Port 8080 für den HTTP-Server öffnet Tomcat dann Port 8000, um eine Verbindung zum Debuggen herzustellen.
Hinweis: Im Debugging-Teil führe ich IntelliJ unter Windows 10 aus und den Tomcat auf einer Kali-VM. Daher muss JPDA_ADDRESS geändert werden. Wenn Sie IntelliJ und Tomcat auf demselben Rechner einrichten, ist keine Änderung erforderlich.
IntelliJ-Seite:
Gehen Sie zu Run -> Edit Configurations... -> Add (+ ) -> Remote JVM Debug
Geben Sie einen Namen ein -> Ändern Sie Host und Port auf die IP und den Port, die Sie in der Datei catalina.sh geändert haben -> OK -> Shift + F9 (Debugging starten)

Zunächst analysiere ich das Projekt, das ich zum Debuggen verwende. Wie oben erwähnt, besteht dieses Projekt nur aus:
Ich habe ein Beispiel wie folgt:

Die Anwendung holt die Informationen aus den Parametern der POST-Anfrage und erstellt ein helloWorld-Objekt {“person”:”Leo”, “message”:”Hi there”}. Dieses helloWorld-Objekt ist die Eingabe für die Methode helloPost. Die Anwendung führt die oben genannten Schritte aus, um die entsprechende Antwort an den Benutzer zurückzugeben.
Der Prozess der Umwandlung von den Parametern im Body der POST-Anfrage in das helloWorld-Objekt wird vollständig automatisch von Spring durchgeführt. Wie macht Spring das und validiert es die eingegebenen Parameter?
Dieses Bild wurde während des Debuggings aufgenommen, das Beispiel führte eine Anfrage mit dem Body „class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT“ durch. (ich nenne den linken Teil (Stack Trace) (1) und den rechten Teil (2)):

In (1) habe ich die wichtigen Punkte hervorgehoben (von unten nach oben betrachtet). Spring führt applyPropertyValue für das helloWorld-Objekt aus den Parametern der POST-Anfrage aus. Wenn die Parameter nur einfach person=Leo&message=Hi%20there sind, kann Spring mit dem Schlüsselwort „person“ helloWorld.person und „message“ helloWorld.message zuordnen.
Aber Spring erlaubt es uns auch, Objekte über HTTP-Anfragen zu senden (es ist übertrieben zu sagen, dass man Objekte über HTTP sendet, aber so kann man es sich vereinfacht vorstellen). Angenommen, das Attribut person ist kein String mehr, sondern ein Objekt Person, das zwei Unterattribute name (String) und age (int) hat. Um Informationen über dieses Person-Objekt an den Server zu senden, würde dies so aussehen: “person.name=Leo&person.age=23”.
Das Format der Parameter ist also A.B.C.D… = X und nicht mehr nur A=X. Um das Format A.B.C.D… = X zu verarbeiten, z.B. einen Parameter wie A.B.C= X, vereinfacht gesagt: Spring wandelt dies in getA.getB.setC(X) um.
Ich werde nicht erklären, was getA ist, sondern ein Beispiel mit dem Anfrage-Body „person.name=Leo&person.age=23“ geben: Spring sucht im helloWorld-Objekt nach dem Attribut person und der Methode getPerson. Wenn vorhanden, ruft Spring helloWorld.getPerson() auf. Zu diesem Zeitpunkt erhält Spring ein Objekt vom Typ Person, nennen wir es person1. Dann sucht Spring in person1 nach einem Attribut namens „name“ und nach setName (da nach „name“ ein „=“ kommt). Wenn vorhanden, ruft Spring setName(Leo) für person1 auf.
Bei person.age ruft Spring nicht erneut von vorne nach person und dann nach age, sondern verwendet die zuvor gefundenen Objekte wieder, in diesem Fall helloWorld und person1.
Nach diesen Schritten existiert auf dem Server ein helloWorld-Objekt {person:{name:”Leo”, age:23}} (das Attribut message lassen wir vorerst weg).
Wie kann Spring die Attribute jedes Objekts finden, z.B. das Attribut „person“ des helloWorld-Objekts?
Sie sehen im oberen Teil von (1) die Funktion CachedIntrospectionResults(beanClass). Diese Funktion listet die Attribute von beanClass auf. In (2) sehen Sie, dass beanClass = model.HelloWorld drei Attribute hat. Das von mir erstellte Modell HelloWorld hat jedoch nur zwei Attribute: „person“ und „message“. Die obige Funktion hat also ein weiteres Attribut „class“ zurückgegeben. Wenn Sie die Zeile „class“ erweitern, sehen Sie, dass propertyType „java.lang.class“ ist.
Wir können also auf ein class-Objekt vom Typ java.lang.classclass einwirken – dies ist die Quelle (source) von Spring4Shell.
In Bezug auf die obige Quelle gab es bereits eine CVE-2010-1622, die sich auf diese Quelle bezieht. Der Autor von CVE-2010-1622 nutzte diese Quelle mit dem Payload class.classLoader.URLs[0] = X aus.
Weil die Klasse java.lang.class eine Methode getClassLoader() enthält, die ein ClassLoader-Objekt zurückgibt, und dieser ClassLoader kann auf das URLs-Array von Tomcat (zum Laden von Ressourcen) einwirken. Durch die Beeinflussung von URLs kann ein Angreifer den Wert von URLs[0] auf eine URL ändern, um eine Remote-Verbindung zu einer bösartigen Jar-Datei (die der Angreifer kontrolliert) herzustellen.
Um diesen Fehler zu beheben, hat Spring einen Filter (Blacklist) in der Funktion CachedIntrospectionResults(beanClass) implementiert:

Wenn „beanClass“ == Class.class (java.lang.class) ist, muss pd sich von „classLoader“ und „protectionDomain“ unterscheiden. Dies zeigt sich darin, dass nach dem Laden aller Attribute von java.lang.class durch CachedIntrospectionResults die beiden Attribute „classLoader“ und „protectionDomain“ nicht vorhanden sind:

Aber stattdessen hat Class.class ab JDK 9 ein zusätzliches Attribut „module“ erhalten, und Class.module hat ein Attribut classLoader:

→ Mit JDK 9 oder höher kann man also die Blacklist von Spring umgehen!!!
Basierend auf dem öffentlichen PoC des Spring4Shell-Fehlers sieht das verwendete Payload so aus:
class.module.classloader.resources.context.parent.pipeline.first ⇔ Class.getModule().getClassLoader().getResources().getContext().getParent().getPipeline().getFirst()
Basierend auf dem Debugging sehen wir folgende Gadget-Chain:
java.lang.class.getModule() -> java.lang.module.getClassLoader() -> org.apache.catalina.loader.ParallelWebappClassLoader.getResources() -> org.apache.catalina.webresources.StandardRoot.getContext() -> org.apache.catalina.core.StandardContext.getParent() -> org.apache.catalina.core.StandardHost.getPipeline() -> org.apache.catalina.core.StandardPipeline.getFirst() -> org.apache.catalina.valves.AccessLogValue.
Und die Klasse AccessLogValue hat die folgenden Attribute:

Wir können ein AccessLogValue-Objekt aufrufen, und dieses AccessLogValue beeinflusst die Log-Erstellung von Tomcat.
→ Wir können auf dem Server Dateien erstellen, indem wir die Attribute des AccessLogValue-Objekts im Tomcat-Server setzen. Dazu setzen sie im PoC die Attribute Prefix, Suffix, Pattern, Directory und fileDateFormat. Die Payload-Anfrage sieht wie folgt aus:
“class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=”
Liste der Breakpoints
Um das Debuggen zu erleichtern, können Sie an den folgenden Stellen Haltepunkte setzen:

Diese Analyse hat eigentlich kein Fazit, dieser Abschnitt dient nur der Vollständigkeit!!!
Wenn Sie nach einer Lösung suchen, finden Sie sie hier.