Exécution de code à distance avec XStream

1. Vue d'ensemble

Dans ce didacticiel, nous allons disséquer une attaque d'exécution de code à distance contre la bibliothèque de sérialisation XML XStream. Cet exploit appartient à la catégorie des attaques de désérialisation non approuvées .

Nous allons apprendre quand XStream est vulnérable à cette attaque, comment l'attaque fonctionne et comment empêcher de telles attaques.

2. Principes de base de XStream

Avant de décrire l'attaque, passons en revue quelques notions de base de XStream. XStream est une bibliothèque de sérialisation XML qui traduit entre les types Java et XML. Considérez une classe Person simple :

public class Person { private String first; private String last; // standard getters and setters }

Voyons comment XStream peut écrire une instance Person en XML:

XStream xstream = new XStream(); String xml = xstream.toXML(person);

De même, XStream peut lire du XML dans une instance de Person :

XStream xstream = new XStream(); xstream.alias("person", Person.class); String xml = "JohnSmith"; Person person = (Person) xstream.fromXML(xml);

Dans les deux cas, XStream utilise la réflexion Java pour traduire le type Person vers et depuis XML. L'attaque a lieu lors de la lecture de XML. Lors de la lecture de XML, XStream instancie les classes Java à l'aide de la réflexion.

Les classes que XStream instancie sont déterminées par les noms des éléments XML analysés.

Étant donné que nous avons configuré XStream pour qu'il prenne en compte le type de personne , XStream instancie une nouvelle personne lorsqu'il analyse les éléments XML nommés «personne».

En plus des types définis par l'utilisateur tels que Person , XStream reconnaît les types Java de base prêts à l'emploi. Par exemple, XStream peut lire une carte à partir de XML:

String xml = "" + "" + " " + " foo" + " 10" + " " + ""; XStream xStream = new XStream(); Map map = (Map) xStream.fromXML(xml); 

Nous verrons comment la capacité de XStream à lire du XML représentant les types Java principaux sera utile dans l'exploit d'exécution de code à distance.

3. Comment fonctionne l'attaque

Les attaques d'exécution de code à distance se produisent lorsque des attaquants fournissent une entrée qui est finalement interprétée comme du code. Dans ce cas, les attaquants exploitent la stratégie de désérialisation de XStream en fournissant du code d'attaque au format XML.

Avec la bonne composition de classes, XStream exécute finalement le code d'attaque via la réflexion Java.

Construisons un exemple d'attaque.

3.1. Inclure le code d'attaque dans un ProcessBuilder

Notre attaque vise à démarrer un nouveau processus de calculatrice de bureau. Sur macOS, il s'agit de «/Applications/Calculator.app». Sous Windows, il s'agit de «calc.exe». Pour ce faire, nous inciterons XStream à exécuter un nouveau processus à l'aide d'un ProcessBuilder. Rappelez le code Java pour démarrer un nouveau processus:

new ProcessBuilder().command("executable-name-here").start();

Lors de la lecture de XML, XStream appelle uniquement les constructeurs et définit les champs. Par conséquent, l'attaquant ne dispose pas d'un moyen simple d'invoquer la méthode ProcessBuilder.start () .

Cependant, les attaquants intelligents peuvent utiliser la bonne composition des classes pour exécuter finalement le ProcessBuilder de démarrage () méthode.

Le chercheur en sécurité Dinis Cruz nous montre dans son article de blog comment ils utilisent l' interface Comparable pour invoquer le code d'attaque dans le constructeur de copie de la collection triée TreeSet. Nous résumerons l'approche ici.

3.2. Créer un proxy dynamique comparable

Rappelez-vous que l'attaquant doit créer un ProcessBuilder et invoquer sa méthode start () . Pour ce faire, nous allons créer une instance de Comparable dont comparer méthode appelle la ProcessBuilder de démarrage () méthode.

Heureusement, les proxys dynamiques Java nous permettent de créer dynamiquement une instance de Comparable .

De plus, la classe EventHandler de Java fournit à l'attaquant une implémentation InvocationHandler configurable . L'attaquant configure le gestionnaire d' événements pour appeler la ProcessBuilder de start () méthode.

En réunissant ces composants, nous avons une représentation XML XStream pour le proxy Comparable :

 java.lang.Comparable    open /Applications/Calculator.app   start  

3.3. Forcer une comparaison à l'aide du proxy dynamique comparable

Pour forcer une comparaison avec notre proxy Comparable , nous allons créer une collection triée. Construisons une collection TreeSet qui compare deux instances comparables : une chaîne et notre proxy.

Nous utiliserons le constructeur de copie de TreeSet pour construire cette collection. Enfin, nous avons la représentation XML XStream pour un nouveau TreeSet contenant notre proxy et une chaîne :

 foo  java.lang.Comparable    open /Applications/Calculator.app   start    

En fin de compte, l'attaque se produit lorsque XStream lit ce XML. Alors que le développeur s'attend à ce que XStream lise une personne , il exécute à la place l'attaque:

String sortedSortAttack = // XML from above XStream xstream = new XStream(); Person person = (Person) xstream.fromXML(sortedSortAttack);

3.4. Résumé de l'attaque

Résumons les appels réflexifs que XStream effectue lorsqu'il désérialise ce XML

  1. XStream invoque le constructeur de copie TreeSet avec une Collection contenant une chaîne «foo» et notre proxy Comparable .
  2. Le constructeur TreeSet appelle la méthode compareTo de notre proxy Comparable afin de déterminer l'ordre des éléments dans l'ensemble trié.
  3. Notre proxy dynamique Comparable délègue tous les appels de méthode à EventHandler .
  4. The EventHandler is configured to invoke the start() method of the ProcessBuilder it composes.
  5. The ProcessBuilder forks a new process running the command the attacker wishes to execute.

4. When Is XStream Vulnerable?

XStream can be vulnerable to this remote code execution attack when the attacker controls the XML it reads.

For instance, consider a REST API that accepts XML input. If this REST API uses XStream to read XML request bodies, then it may be vulnerable to a remote code execution attack because attackers control the content of the XML sent to the API.

On the other hand, an application that only uses XStream to read trusted XML has a much smaller attack surface.

For example, consider an application that only uses XStream to read XML configuration files set by an application administrator. This application is not exposed to XStream remote code execution because attackers are not in control of the XML the application reads (the admin is).

5. Hardening XStream Against Remote Code Execution Attacks

Fortunately, XStream introduced a security framework in version 1.4.7. We can use the security framework to harden our example against remote code execution attacks. The security framework allows us to configure XStream with a whitelist of types it is allowed to instantiate.

This list will only include basic types and our Person class:

XStream xstream = new XStream(); xstream.addPermission(NoTypePermission.NONE); xstream.addPermission(NullPermission.NULL); xstream.addPermission(PrimitiveTypePermission.PRIMITIVES); xstream.allowTypes(new Class[] { Person.class });

De plus, les utilisateurs de XStream peuvent envisager de renforcer leurs systèmes à l'aide d'un agent Runtime Application Self-Protection (RASP). Les agents RASP utilisent une instrumentation bytecode au moment de l'exécution pour détecter et bloquer automatiquement les attaques. Cette technique est moins sujette aux erreurs que la création manuelle d'une liste blanche de types.

6. Conclusion

Dans cet article, nous avons appris comment effectuer une attaque d'exécution de code à distance sur une application qui utilise XStream pour lire du XML. Étant donné que de telles attaques existent, XStream doit être renforcé lorsqu'il est utilisé pour lire du XML à partir de sources non fiables.

L'exploit existe parce que XStream utilise la réflexion pour instancier les classes Java identifiées par le XML de l'attaquant.

Comme toujours, le code des exemples se trouve à l'adresse over sur GitHub.