Analyse d'une désérialisation non sécurisée sur JFrog Artifactory (CVE-2022-0573) - Partie 2

Introduction

Lors du premier article, nous avons décrit la recherche d’une désérialisation sur Artifactory, correspondant à la vulnérabilité CVE-2022-0573.

Nous avons ainsi pu identifier un point d’entrée où un objet Java est désérialisé.

Dans cette deuxième partie, nous allons décrire la recherche de la gadget chain permettant l’exécution de commande sur le système. Afin d’exploiter ce défaut, une technique novatrice d’exploitation a été utilisée, qui pourrait être également réutilisée, par exemple sur d’autres produits utilisant XStream.

Nous repartons donc de l’analyse des différences entre une version corrigée et une version vulnérable afin de comprendre la vulnérabilité.

Analyse de la correction de la vulnérabilité

La différence principale, que nous avions pu voir dans le premier article, est le durcissement de la liste blanche de XStream.


Note : XStream est une bibliothèque Java qui a pour objectif de convertir des instances d’objet Java en XML, et l’inverse. Afin de contrôler quelles sont les classes autorisées à être désérialisées, une liste blanche peut être définie.


On remarque lors de l’analyse des modifications que l’on passe de la méthode setupDefaultSecurity qui instancie une liste blanche par défaut, à une liste blanche personnalisée.

Cette liste décrite dans la méthode setupDefaultSecurity semble peu permissive, en n’autorisant à première vue que certains types « natifs ». Cependant, le fonctionnement de XStream permet deux types d’autorisations, soit allowType qui autorise uniquement le type (ce que l’on peut voir dans le correctif) soit allowTypeHierarchy, qui autorise aussi les classes qui implémentent ou étendent ce type.

La différence principale est la suppression de l’autorisation de la hiérarchie complète des types (comme Map) afin de n’autoriser que les types natifs uniquement.

En autorisant la hiérarchie complète de ces classes, toute classe qui implémente ou étend la classe mère (ici Map, ou Collection) peut être désérialisée. La liste des classes pouvant être désérialisées est alors bien plus étendue, et un grand nombre de classes du classpath d’Artifactory peuvent être désérialisées.

Analyse des fichiers XML ajoutés

Entre les deux versions d’Artifactory, avant la correction et après la correction, nous pouvons aussi remarquer des fichiers XML ajoutés dans des dossiers de tests unitaires. Le fichier set_system_property_by_xstream.xml notamment est à première vue un objet Java sérialisé via XStream, et qui contient uniquement des classes filles de Map et Collection.

On peut aussi voir la chaîne rmi://127.0.0.1:31339/remoteObject, qui correspond à une chaîne de connexion Java RMI. Ce type de connexion est souvent utilisé dans les vulnérabilités de désérialisation, puisque cela peut permettre de forcer l’application à récupérer un objet Java sur le serveur de l’attaquant, objet Java qui lui permettra d’exécuter des commandes sur le système.

On peut alors penser que cet objet est potentiellement la gadget chain utilisée par les chercheurs Matthias Kaiser et Jonni Passki pour exploiter la vulnérabilité.

Analyse de la première gadget chain

Nous allons alors étudier la trace d’appel de la gadget chain, afin de comprendre son fonctionnement et ce qu’elle exécute sur le système.

La tâche est alors simplifiée par rapport à la recherche de la chaîne, nous avons simplement à l’exécuter et suivre le fil des appels lors de la désérialisation.

L’objet Java est une Map, et donc lors de la désérialisation, la méthode hashCode va être appelée sur la première clé de la Map.

java.util.HashMap.put
    java.util.HashMap.hash -> hashCode

Cette clé correspond ici à un objet de la classe LazyStringArrayList. Cette classe est un wrapper autour d’une liste.

La méthode get va être appelée par hashCode, méthode qui va ici nous intéresser.

    com.google.protobuf.LazyStringArrayList.hashCode
        com.google.protobuf.AbstractProtobufList.hashCode
            com.google.protobuf.LazyStringArrayList.get

Cette méthode get va à son tour appeler get sur la liste wrappée par la classe.

Ici, la liste est une SetUniqueList, qui n’implémente pas de méthode get. Néanmoins, sa classe mère AbstractListDecorator l’implémente, et c’est sur cette classe que la méthode va être appelée. Cette classe wrap également une liste, qui ici contient une chaîne de caractère (ByteString) correspondant à java.naming.provider.url, qui va donc être retournée.

                org.apache.commons.collections.list.AbstractListDecorator.get
                    java.util.ArrayList.get -> java.naming.provider.url

Nous pouvons passer à la deuxième partie de la gadget chain, qui va ici appeler la méthode set sur la liste wrappée par LazyStringArrayList.

                org.apache.commons.collections.list.SetUniqueList.set
                    org.apache.commons.collections.set.MapBackedSet.add
                        org.jruby.java.util.SystemPropertiesMap.put -> System.setProperty(key, value);

Cette liste (SetUniqueList) wrap un Set, qui est un MapBackedSet, qui lui même wrap une Map, SystemPropertiesMap. Cette classe est une surcouche au dessus des System Properties de la plateforme Java. À la fin de la chaîne d’appel, la méthode put est appelée :

Nous pouvons alors contrôler la clé, qui provient de la première partie de la chaîne, et la valeur, qui correspond au champ dummyValue de la SystemPropertiesMap.

La trace d’appel complète de la gadget chain est alors la suivante :

java.util.HashMap.put
    java.util.HashMap.hash
        com.google.protobuf.LazyStringArrayList.hashCode
            com.google.protobuf.AbstractProtobufList.hashCode
                com.google.protobuf.LazyStringArrayList.get
                    org.apache.commons.collections.list.AbstractListDecorator.get
                        java.util.ArrayList.get -> key
                    org.apache.commons.collections.list.SetUniqueList.set
                        org.apache.commons.collections.set.MapBackedSet.add
                            org.jruby.java.util.SystemPropertiesMap.put -> System.setProperty(key, dummyValue);

Contrairement à ce que l’on pouvait penser, cette chaîne n’effectue pas d’actions malveillantes, mais permet ici de contrôler les System Properties de la JVM. Nous avons ici initialisé java.naming.provider.url à rmi://127.0.0.1:31339/Exploit.

Nous allons donc chercher une deuxième gadget chain, qui pourrait utiliser cette System Property initialisée.

Analyse de la deuxième gadget chain

Un deuxième fichier XML load_SubList.xml peut être identifié dans ces dossiers de tests.

Ce fichier correspond aussi à un objet Java sérialisé, qui pourrait être une deuxième gadget chain intéressante. Cet objet contient notamment la chaîne javax.naming.InitialContext.

Cela correspond à la classe InitialContext, qui définit le contexte lors des opérations de nommage, notamment pour JNDI.

En analysant ce mécanisme, on se rend alors compte que les System Properties vont être utilisées par cette classe.

Notamment, lors de l’initialisation du contexte de nommage, si ces Properties sont initialisées, le contexte va être récupéré sur l’URL distante précisée.

Nous pouvons alors initialiser les Properties suivantes :

java.naming.provider.url : rmi://127.0.0.1:31339/Exploit
java.naming.factory.initial : com.sun.jndi.rmi.registry.RegistryContextFactory

Il nous faut alors trouver une gadget chain qui permet d’instancier un objet InitialContext pour pouvoir déclencher l’appel.

Le nouveau fichier load_SubList.xml correspond à la gadget chain suivante :

com.mchange.v1.util.Sublist.get
    org.apache.commons.beanutils.LazyDynaList.get
        org.apache.commons.beanutils.LazyDynaList.growList
            org.apache.commons.beanutils.LazyDynaList.transform
                org.apache.commons.beanutils.LazyDynaList.setElementType -> Initalisation d'un InitialContext

A la fin de la désérialisation, la méthode setElementType est appelée et instancie un objet dont la classe est nommée dans une chaîne de caractère en attribut.

Il nous faut alors un moyen d’appeler le début de cette chaîne, la méthode get de Sublist n’étant pas appelée lors de la désérialisation.

Nous pouvons alors utiliser la première gadget chain, qui appelait la méthode get sur une classe fille de Collection. La trace complète de la chaîne est la suivante :

java.util.HashMap.put
    java.util.HashMap.hash
        com.google.protobuf.LazyStringArrayList.hashCode
            com.google.protobuf.AbstractProtobufList.hashCode
                com.mchange.v1.util.Sublist.get
                    org.apache.commons.beanutils.LazyDynaList.get
                        org.apache.commons.beanutils.LazyDynaList.growList
                            org.apache.commons.beanutils.LazyDynaList.transform
                                org.apache.commons.beanutils.LazyDynaList.setElementType -> Initalisation d'un InitialContext

Construction de la désérialisation du contexte

Lors de l’initialisation d’un objet InitialContext, et en fonction des System Properties, cette initialisation peut se faire via RMI en récupérant un objet dans un registre distant.

Contrairement aux exploits « JNDI injection » classiques, on ne peut pas exécuter de méthodes sur cet objet par défaut (comme dans la vulnérabilité log4shell par exemple).

Néanmoins, le protocole RMI reposant sur des objets Java sérialisés, on peut considérer une dernière désérialisation. L’objet correspondant au InitialContext serait une gadget chain, qui lui exécuterait une commande sur le système victime.

Des protections sont en place, notamment depuis Java 8u121 afin d’empêcher les vulnérabilités de désérialisation via RMI en limitant la confiance donnée aux objets récupérés à distance. Heureusement pour notre exploit, cette protection peut justement être désactivée en initialisant une System Property, que nous contrôlons grâce à la première gadget chain :

com.sun.jndi.rmi.object.trustURLCodebase : true

Après avoir accordé la confiance à notre code provenant d’un serveur distant, nous devons trouver une gadget chain, mais ici sans aucun filtrage spécifique, la désérialisation étant celle native à Java.

La libraire Commons Collections étant présente dans le classpath d’Artifactory, et pour faciliter l’exploitation, nous pouvons réactiver la déserialisation « unsafe » de la librairie en initialisant également un autre System Property :

org.apache.commons.collections.enableUnsafeSerialization : true

Cela permet d’utiliser la chaîne CommonsCollections6, qui lors de sa désérialisation va exécuter une commande sur le système.

Construction du serveur RMI distant

La dernière étape de notre exploitation consiste à développer un serveur RMI, qui va répondre notre objet sérialisé CommonsCollections6. Nous ne pouvons utiliser l’implémentation RMI native, puisque notre objet devra être envoyé durant la communication.

Pour cela, nous repartons du serveur RMI développé au sein du projet de marshalsec, et modifions la méthode handleRMI.

Lors de l’appel à notre serveur, il va ainsi renvoyer un objet sérialisé correspondant à la chaîne CommonsCollections6, et après la désérialisation, un reverse shell sera exécuté sur le système sous-jacent à Artifactory.

Exploit final

Pour résumer, notre exploitation finale se sépare en trois étapes :

Une première étape avec 3 désérialisations permettant d’initialiser les System Properties liées au contexte de nommage, et liées au contournement des sécurités en place (trustURLCodebase et enableUnsafeSerialization).

Une deuxième étape avec une désérialisation menant à l’instanciation d’un objet InitialContext, utilisant les System Properties pour s’initialiser, et allant ainsi rechercher ce contexte sur le poste de l’attaquant.

Et enfin une troisième et dernière étape de réponse d’une gadget chain à la demande de contexte, et de l’exécution de commandes arbitraires sur le serveur Artifactory.

Conclusion

Cette exploitation est ainsi intéressante lorsque le serveur a peu de restrictions sur ses flux de sortie, notamment dans le cadre d’un réseau interne. Au vu de la taille du classpath Artifactory, il est aussi possible qu’une gadget chain existe permettant l’exécution de commandes directement, sans passer par un serveur RMI distant.

Concernant la méthode utilisée, cette méthode découverte par Matthias Kaiser et Jonni Passki est novatrice, et présente deux intérêts principaux.

Premièrement elle permet ainsi d’identifier que l’utilisation de la méthode setupDefaultSecurity de XStream peut s’avérer potentiellement dangereuse. Par l’autorisation de hiérarchie de types, un grand nombre de classes peuvent ainsi être désérialisées et servir à une gadget chain. Ces classes pouvant ainsi faire partie de bases de code non contrôlées par les développeurs (librairies et dépendances), il peut être compliqué d’évaluer le risque d’une gadget chain exploitable sur un projet.

Deuxièmement, la méthode de désérialisation à partir d’un objet InitialContext est également intéressante. Couplée avec le contrôle des System Properties Java, elle pourrait être réutilisée dans d’autres gadgets chains, non limitées à XStream, et peut ainsi constituer une piste pour des recherches de gadget chain dans les produits Java.

Références

Arnaud Lagadec

Découvrir d'autres articles

  • Pentest

    Des dés aux données : de l’importance des nombres aléatoires (partie 2/2)

    Lire l'article
  • Pentest

    Des dés aux données : de l’importance des nombres aléatoires (partie 1/2)

    Lire l'article
  • Pentest

    Tour d’horizon des attaques et vulnérabilités sur le gestionnaire de mots de passe KeePass 

    Lire l'article