
Untersuchungsbericht zu den Struts2-Schwachstellen S2-045, S2-055 und den Jackson-Schwachstellen CVE-2017-7525, CVE-2017-15095
Eine Zusammenfassung der wichtigsten Punkte in lesbarer Form wurde veröffentlicht. Empfohlen für alle, die zuerst einen Überblick benötigen oder wenig Zeit haben.
Am 1. Dezember 2017 wurde ein Sicherheitsupdate für Struts2 veröffentlicht. Bereits vor der Veröffentlichung kursierte in der Mailingliste das Gerücht, dass die Schwachstelle von Jackson (einer beliebten JSON-Bibliothek in Java) damit zusammenhänge. Auch der Autor, der Jackson in internen Systemen und Tools verwendet, war gespannt auf die genauen Details.
Die tatsächlich veröffentlichten Inhalte umfassten Korrekturen für die folgenden zwei Sicherheitsprobleme. Nur S2-055 ist von der Schwachstelle in jackson-databind, einer Komponente von Jackson, betroffen.
Im REST-Plugin waren bereits seit längerem sowohl ein Handler mit JSON-lib als auch ein Handler mit Jackson integriert, sodass der Benutzer wählen konnte. Die gesamte Korrektur scheint darin zu bestehen, dass in S2-054 der Standard-Handler auf Jackson umgestellt und in S2-055 die zuvor veraltete Jackson-Version aktualisiert wurde.
Was genau ist also die Schwachstelle CVE-2017-7525? Da der Autor selbst bei der JSON-Verarbeitung in Java üblicherweise Jackson verwendet, wurde dieses Problem am Wochenende des 2. und 3. Dezembers untersucht – dies ist der vorliegende Artikel.
Umgebung des Autors zur Überprüfung des Beispielcodes:
Im Blog von Adam Caudill wurde eine Erläuterung zu CVE-2017-7525 veröffentlicht.
Zusammengefasst in den eigenen Worten des Autors: jackson-databind bietet eine Funktion zum Mapping von JSON auf Java-Objekte (Klasse ObjectMapper).
Durch den Aufruf von ObjectMapper.enableDefaultTyping() wird es möglich, über einen in JSON eingebetteten Klassennamen zu mappen.
Viele werden bereits bei dem Punkt „Klassennamen direkt aus der JSON-Eingabe angeben zu können“ ein ungutes Gefühl bekommen – genau diese böse Vorahnung bestätigt sich bei CVE-2017-7525.
Bevor wir auf die Schwachstelle eingehen, erklären wir, warum diese Funktion überhaupt implementiert wurde.
Die grundlegende Verwendung der Deserialisierung mit jackson-databind ist im folgenden Beispielcode zu sehen. (In diesem Artikel wird für den Jackson-Beispielcode Groovy verwendet. Praktisch ist, dass man mit @Grab einfach die Version von jackson-databind wechseln kann.)
Im obigen Beispielcode kann der Schlüssel "animal" direkt auf die Klasse Animal gemappt werden. Wie verhält es sich jedoch in folgendem Fall?```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
Bei dieser Konfiguration gibt es zwei Fälle: Der Inhalt des Schlüssels "animal" zeigt entweder auf die Dog-Klasse oder auf die Cat-Klasse. Daher sind zusätzliche Informationen erforderlich, um zu bestimmen, welche Klasse für die Zuordnung verwendet werden soll.
Um dieses Problem zu lösen, hat jackson-databind eine eigene Verarbeitung integriert, die den Namen der zuzuordnenden Klasse in das JSON einbetten kann.
Zum Beispiel wie folgt, indem der Inhalt des Schlüssels "animal" in ein Array umgewandelt wird und das erste Element den Klassennamen angibt.```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
Dadurch erkennt ObjectMapper.readValue(), dass der Inhalt des Schlüssels "animal" die Klasse Dog ist, und führt die Zuordnung durch.
Natürlich kann man ohne weitere Maßnahmen nicht unterscheiden, ob der Inhalt des "animal"-Schlüssels ursprünglich ein Array war oder ob er die jackson-databind-eigenen Klassenname-Informationen enthält.
Der Wechsel zwischen diesen Modi erfolgt über die Methode ObjectMapper.enableDefaultTyping().
Es gibt auch die Möglichkeit, die Annotation @JsonTypeInfo in der Klasse zu definieren. Weitere Einzelheiten finden Sie in der folgenden Jackson-Dokumentation.
Im Folgenden finden Sie einen Beispielcode, der die Methode ObjectMapper.enableDefaultTyping() verwendet.
Wie wir oben gesehen haben, ist es möglich, durch Angabe eines Klassennamens und dessen Eigenschaften im JSON – wenn auch mit gewissen Einschränkungen – beliebige Klassen mit beliebigen Eigenschaften zu instanziieren. Diese Schwachstelle wurde durch CVE-2017-7525 ausgenutzt. Der Auslöser war vermutlich der folgende Bericht:
Für häufig in Java verwendete Serialisierungs-/Deserialisierungsbibliotheken wie Jackson wurde über die Gefahr berichtet, dass eine Manipulation von Klassennamen zu beliebiger Codeausführung führen kann. Es wurden konkrete gefährliche Klassennamen aufgelistet.
Ob dies der Auslöser war, ist nicht sicher, aber zeitlich kurz nach dem ersten Commit des obigen Repositories wurde das folgende Issue für jackson-databind erstellt und die Behebung begann.
Wie sehen eigentlich JSON-Daten und Java-Code aus, die diese Schwachstelle ausnutzen? Einen Hinweis gibt der Testcode von jackson-databind 2.8.9, der in diesem Issue behandelt wurde:
Basierend auf diesem Testcode wird im Folgenden ein angepasster Beispielcode gezeigt, mit dem Sie die Funktionsweise überprüfen können: