Esecuzione di codice in modalità remota con XStream

1. Panoramica

In questo tutorial analizzeremo un attacco di esecuzione di codice in modalità remota contro la libreria di serializzazione XML XStream. Questo exploit rientra nella categoria degli attacchi di deserializzazione non attendibile .

Impareremo quando XStream è vulnerabile a questo attacco, come funziona l'attacco e come prevenire tali attacchi.

2. Nozioni di base su XStream

Prima di descrivere l'attacco, esaminiamo alcune nozioni di base su XStream. XStream è una libreria di serializzazione XML che converte tra tipi Java e XML. Considera una semplice classe Person :

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

Vediamo come XStream può scrivere qualche istanza di Person in XML:

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

Allo stesso modo, XStream può leggere XML in un'istanza di Person :

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

In entrambi i casi, XStream utilizza la riflessione Java per tradurre il tipo Person in e da XML. L'attacco avviene durante la lettura di XML. Durante la lettura di XML, XStream crea un'istanza delle classi Java utilizzando la reflection.

Le classi che XStream istanzia sono determinate dai nomi degli elementi XML che analizza.

Poiché è stato configurato XStream per essere a conoscenza del tipo di persona , XStream crea un'istanza di una nuova persona quando analizza elementi XML denominati "persona".

Oltre ai tipi definiti dall'utente come Persona , XStream riconosce i tipi principali di Java fuori dagli schemi. Ad esempio, XStream può leggere una mappa da XML:

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

Vedremo come la capacità di XStream di leggere XML che rappresentano i tipi principali di Java sarà utile nell'exploit dell'esecuzione di codice remoto.

3. Come funziona l'attacco

Gli attacchi di esecuzione di codice in modalità remota si verificano quando gli aggressori forniscono un input che viene infine interpretato come codice. In questo caso, gli aggressori sfruttano la strategia di deserializzazione di XStream fornendo il codice di attacco come XML.

Con la giusta composizione delle classi, XStream alla fine esegue il codice di attacco attraverso la riflessione Java.

Costruiamo un esempio di attacco.

3.1. Includere il codice di attacco in un ProcessBuilder

Il nostro attacco mira ad avviare un nuovo processo di calcolatrice desktop. Su macOS, questo è "/Applications/Calculator.app". Su Windows, questo è "calc.exe". Per fare ciò, indurremo XStream a eseguire un nuovo processo utilizzando ProcessBuilder. Richiama il codice Java per avviare un nuovo processo:

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

Durante la lettura di XML, XStream richiama solo costruttori e imposta i campi. Pertanto, l'attaccante non dispone di un modo semplice per richiamare il metodo ProcessBuilder.start () .

Tuttavia, gli attaccanti intelligenti possono utilizzare la giusta composizione di classi per eseguire in ultima analisi, il ProcessBuilder s' start () metodo.

Il ricercatore di sicurezza Dinis Cruz ci mostra nel suo post sul blog come usano l' interfaccia Comparable per invocare il codice di attacco nel costruttore di copie della raccolta ordinata TreeSet. Riassumeremo l'approccio qui.

3.2. Crea un proxy dinamico comparabile

Ricorda che l'attaccante deve creare un ProcessBuilder e invocare il suo metodo start () . Per fare ciò, creeremo un esempio di comparabili cui confrontare metodo richiama la ProcessBuilder s' start () metodo.

Fortunatamente, i Java Dynamic Proxy ci consentono di creare dinamicamente un'istanza di Comparable .

Inoltre, la classe EventHandler di Java fornisce all'attaccante un'implementazione InvocationHandler configurabile . L'attaccante configura l'EventHandler di invocare il ProcessBuilder s' start () metodo.

Mettendo insieme questi componenti, abbiamo una rappresentazione XML XStream per il proxy comparabile :

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

3.3. Forza un confronto utilizzando il proxy dinamico comparabile

Per forzare un confronto con il nostro proxy comparabile , creeremo una raccolta ordinata. Creiamo una raccolta TreeSet che confronta due istanze comparabili : una stringa e il nostro proxy.

Useremo il costruttore di copie di TreeSet per costruire questa raccolta. Infine, abbiamo la rappresentazione XML XStream per un nuovo TreeSet contenente il nostro proxy e una stringa :

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

Infine, l'attacco si verifica quando XStream legge questo XML. Mentre lo sviluppatore si aspetta che XStream legga una persona , esegue invece l'attacco:

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

3.4. Riepilogo degli attacchi

Riassumiamo le chiamate riflessive che XStream effettua quando deserializza questo XML

  1. XStream invoca il costruttore di copie TreeSet con una Collection contenente una stringa "foo" e il nostro proxy Comparable .
  2. Il costruttore TreeSet chiama il metodo compareTo del nostro proxy comparabile per determinare l'ordine degli elementi nel set ordinato.
  3. Il nostro proxy dinamico Comparable delega tutte le chiamate ai metodi a 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 });

Inoltre, gli utenti XStream possono considerare di rafforzare i propri sistemi utilizzando un agente RASP (Runtime Application Self-Protection). Gli agenti RASP utilizzano la strumentazione bytecode in fase di esecuzione per rilevare e bloccare automaticamente gli attacchi. Questa tecnica è meno soggetta a errori rispetto alla creazione manuale di una lista bianca di tipi.

6. Conclusione

In questo articolo abbiamo appreso come eseguire un attacco di esecuzione di codice in modalità remota su un'applicazione che utilizza XStream per leggere XML. Poiché esistono attacchi come questo, XStream deve essere rafforzato quando viene utilizzato per leggere XML da fonti non attendibili.

L'exploit esiste perché XStream utilizza la riflessione per istanziare le classi Java identificate dall'XML dell'attaccante.

Come sempre, il codice per gli esempi può essere trovato su GitHub.