Hier geht es um eine ganz besonders trickreiche Schwachstelle, der unsicheren Deserialisierung. 2017 hat das zu katastrophalen Angriffen geführt, wo in den USA mit dem Equifax-Breach sämtliche Credit-Ratings Daten gestohlen wurden.

Platz 8 der OWASP Top-10 nennt sich „Insecure Deserialization“ – was sich harmlos anhört. Dahinter steckt ein Angriff, der Remote Code Execution zulässt, die Königsklasse der Angreifer. Ich werde in diesem Artikel zeigen was dahinter steckt, und wie man sich davor schützt.

Was genau ist Serialisierung und Deserialisierung?

Serialisierung braucht man, wenn ein Objekt bzw. ein Datensatz eine Anwendung verlässt, z.B. weil es übers Netz geschickt wird, oder weil es in der Datenbank gespeichert wird. Dabei wird es in ein anderes Format umgewandelt, entweder in ein Binärformat, oder auch in ein Textformat wie XML oder Json. Ein Textformat hat den Vorteil, dass wenn man Anwendungen mit unterschiedlichen Technologien verwendet, es einfacher mit dem Austausch ist. Da ist es normalerweise egal, ob ein iOS Client Daten von einem Rest-Service lädt, der auf Java oder .net läuft.

Serialisieren einer Klasse, um sie übers Netz zu übertragen, oder zu persistieren.

Sterilisierung eines Objekts

Deserialisieren ist der umgekehrte Vorgang, so dass das serialisierte Objekt wieder eingelesen wird, und ein Objekt der entsprechenden Klasse damit erzeugt wird, damit die Anwendung damit arbeiten kann.

Deserialisierung eines Objekts

Die Schwachstelle dabei ist, dass ein serialisiertes Objekt manipuliert werden kann. Gelingt es einem Angreifer Schadcode in das Objekt einzuschleusen, der von der Anwendung später ausgeführt wird, dann kann man darüber beliebigen Code im Kontext und mit den Rechten der Anwendung ausführen.

Deserialisierung von Schadende

Anwendungen und APIs sind anfällig, wenn sie Deserialisierung von Objekten zulassen, die aus nicht vertrauenswürdigen Quellen stammen. 

Dabei muss das serialisierte Objekt nicht mal in Binärform vorliegen. Aber damit ist es am einfachsten, denn bei Java gibt es sogar Tools, die einem bei solchen Angriffen helfen und ein serialisiertes Java-Objekt generieren, das bei verwundbaren Anwendungen Code ausführen lässt. 

Eine andere Form von serialisierten Objekten sind String-Repräsentationen wie XML oder Json. Json wird von den momentan sehr populären Rest-Services viel verwendet. Backendseitig werden Objekte automatisch in Json umgewandelt (serialisiert) und an den Browser geschickt, wo sie als JavaScript-Objekte weiterverarbeitet werden. Doch auch auf Serverseite kann ein Json Objekt deserialisiert werden. Weit verbreitet ist hierfür die Bibliothek Jackson, die dabei eifrig hilft. Allerdings tut sie das im aktivierten Modus „Default Typing“ so eifrig, dass darüber ebenfalls Remote Code Execution möglich ist.

Beispiel

In der Bibliothek Apache Commons Collection gibt es das Interface Transformer, mit dem Daten und Code dynamisch aufgenommen und ausgeführt werden kann. Hier ein Beispiel das zeigt, wie man den Gnome Calculator, eine Taschenrechner-Anwendung für Unix, über eine solche Klasse und den eingeschleusten Befehl ausführen kann:

Ausgeführt wird der Transformer über den InvocationHandler aus dem sun-Namespace, so dass dadurch eine Remote Code Execution möglich ist.

Prävention

Sicherheitslücken also treten auf, wenn eine Anwendung über eine Schnittstelle serialisierte Daten von Benutzern akzeptiert. Sowohl im Binärformat, als auch in String-Formaten.

Wie kann man für mehr Sicherheit sorgen und solche Schwachstellen vermeiden? Hier einige Maßnahmen, die bereits viel Angriffsfläche absichern.

Zur Prävention muss man die bekannten Mechanismen abschalten, die als Voraussetzung für diese Schwachstelle gelten. Je nach dem welche Libraries man sich in seiner Anwendung an Bord holt, muss man einige Maßnahmen treffen.

Allgemein

Prinzipiell sollte man folgende Dinge beachten bei der Serialisierung.

  • Objekte die man serialisiert überträgt oder speichert, sollte man schlank halten, möglichst primitive Datentypen in dessen Feldern verwenden und keinen ausführbaren Code einfügen.
  • Wenn primitive Datentypen nicht ausreichen, sollte man beim Deserialisieren eine Typenbeschränkung machen.
    • Dazu kann die Klasse ObjectInputStream verwendet werden, da sie verantwortlich ist für Serialisierung und Deserialisierung.
      • Härten der Klasse java.io.ObjectInputStream
      • resolveClass()überschreiben und prüfen, was deserialisiert werden soll
      • Voraussetzungen: 
        • Deserialisierung im eigenen Code
        • deserialisierbare Klassen müssen bekannt sein
  • Alternative: Hardening java.io.ObjectInputStream unter Verwendung eines Agents
    • Wenn Klasse zum Deserialisieren nicht verändert werden kann (z.B. fremder Code)
    • Agent wird beim Start der JVM angegeben 
    • verhindert über Blacklist bekannte Angriffsvektoren
    • Einbindung in JVM mit Option
      • -javaagent:name-of-agent.jar
    • Produkte:
      • Invoker Defendervon Go-CD
      • rO0von Contrast Security
      • Contrast Enterprise von Contrast Security (kommerzielles Produkt)

Jackson-Databind

Eines von vielen Java Serialisierungs-Frameworks ist Jackson-Databind. Es ist darauf spezialisiert, Json Strings zu deserialisieren. Die Serialisierung ist naturgemäß recht einfach, denn man muss nur eine Datenstruktur in eine String-Repräsentation a la Json bringen. Die Deserialisierung ist einiges komplizierter als Serialisierung, denn den Json String in reale Objekte zu mappen ohne die dafür passende Klassen zu kennen ist nicht ohne Weiteres möglich. Deshalb bietet Jackson dafür Annotations an, die bereits in der Json Struktur verwendet werden können, damit der Deserialisierer weiss, welche Java-Klasse als Gegenstück verwendet werden kann. 

Beispielsweise wird mit @JsonTypeInfo annotiert, damit Jackson die richtige Klasse findet. Es gibt eine Vielzahl an Annotations, die bei komplexeren Strukturen helfen sollen.

Gefahr besteht allerdings dann, wenn der Json-String vom Client manipuliert werden kann. Wenn dann noch das Default Typing aktiviert ist, dann kann im Json String eine beliebige Klasse angegeben werden, die Jackson beim Deserialisieren verwendet. So kann recht einfach Schadcode eingeschleust und ausgeführt werden.

Prävention:

  • Immer neueste Jackson-Databind Version verwenden
    • denn diese enthält die vollständigste Liste der gefährlichen Gadget-Klassen in der Blacklist
    • das ist kein perfekter Schutz, aber das mindeste was man tun sollte
  • Default Typing vermeiden
    • statt dessen explizit die Klassen angeben, die bei der Deserialisierung verwendet werden sollen
  • Allgemeine Klassen wie Object, Serializable, … vermeiden in den Objekten, die übertragen werden
    • dadurch wird vermieden, dass beliebige Klassen für den Angriff verwendet werden können
  • Möglichst “type name” verwenden und nicht “type class“ als Type-Id
    • @JsonTypeInfo(use = Id.NAME)  anstatt  @JsonTypeInfo(use = Id.CLASS)

Commons-Collection

Ältere Versionen der Apache Commons Collections (3.2.1 und 4.0) bieten die Möglichkeit, bei durch fehlerhafte Deserialisierung von Java-Objekten beliebigen Code auszuführen. Da dies sehr verbreitete Libraries sind, die auch von zahlreichen kommerziellen Produkten wie ApplicationServern verwendet werden, ist das eine Schwachstelle mit hohem Schadenpotential.

Ab Version 3.2.2 und Version 4.1 ist die Schwachstelle nicht mehr vorhanden.

Fazit

Unsichere Deserialisierung ist eine Angriffsart zum Einschleusen und Ausführen von beliebigem Code. Insbesondere wenn die Schnittstelle existiert, die die deserialisierte Version des Codes entgegennimmt.

Es gibt dabei viele Möglichkeiten für Gegenmaßnahmen. Insbesondere die Vermeidung von komplexen Datentypen für solche Schnittstellen sowie keine Klassen ungeprüft zu deserialisieren.

Video

Video: A8:2017 Unsichere Deserialisierung