
Rapporto di indagine sulle vulnerabilità Struts2 S2-045, S2-055 e le vulnerabilità Jackson CVE-2017-7525, CVE-2017-15095
Abbiamo pubblicato un riassunto conciso e facile da leggere. Consigliato a chi vuole una panoramica o ha poco tempo.
Il 1° dicembre 2017 è stato rilasciato un aggiornamento di sicurezza per Struts2. Prima del rilascio, si parlava su mailing list di una correlazione con le vulnerabilità di Jackson (una libreria JSON popolare in Java); anche l'autore, che utilizza Jackson in sistemi e strumenti interni, era curioso di sapere nel dettaglio di cosa si trattasse.
I contenuti effettivamente rilasciati risolvevano i seguenti due problemi di sicurezza. Solo S2-055 è interessato dalla vulnerabilità di jackson-databind, componente di Jackson.
Il plugin REST includeva già sia un handler basato su JSON-lib che uno basato su Jackson, consentendo all'utente di scegliere. Con S2-054 l'handler predefinito è stato cambiato in Jackson, e con S2-055 la versione di Jackson (che era obsoleta) è stata aggiornata all'ultima. Questo è il quadro completo della correzione.
Allora, di che vulnerabilità si tratta esattamente CVE-2017-7525? Poiché l'autore utilizza Jackson abitualmente quando elabora JSON in Java, questo articolo è il risultato di un'indagine condotta nel fine settimana del 2-3 dicembre.
Ambiente dell'autore utilizzato per la verifica del codice di esempio:
Sul blog di Adam Caudill è stata pubblicata una spiegazione di CVE-2017-7525.
Riassumendo con parole mie: jackson-databind fornisce la funzionalità di mappare JSON in oggetti Java (classe ObjectMapper).
Chiamando ObjectMapper.enableDefaultTyping(), è possibile effettuare la mappatura utilizzando un nome di classe incorporato arbitrariamente nel JSON.
Chi ha già avuto un brutto presentimento al pensiero di "poter specificare un nome di classe dall'input JSON" ha ragione: è proprio quel brutto presentimento a concretizzarsi con CVE-2017-7525.
Prima di descrivere la vulnerabilità, spieghiamo perché questa funzionalità è stata implementata.
Per un utilizzo base della deserializzazione con jackson-databind, fare riferimento al seguente codice di esempio. (In questo articolo usiamo Groovy per i codici di esempio Jackson. @Grab permette di cambiare facilmente la versione di jackson-databind.)
Nel codice di esempio sopra, la chiave "animal" viene semplicemente mappata direttamente alla classe Animal. E in casi come il seguente?```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
Con questa configurazione, il contenuto della chiave 'animal' può riferirsi alla classe Dog o alla classe Cat, generando due possibilità. Pertanto, sono necessarie informazioni aggiuntive per determinare con quale classe eseguire il mapping.
Per risolvere ciò, jackson-databind ha integrato un meccanismo personalizzato che consente di incorporare il nome della classe da mappare nel JSON.
Ad esempio, come segue, si trasforma il contenuto della chiave 'animal' in un array e si specifica il nome della classe nel primo elemento.```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
In questo modo, ObjectMapper.readValue() riconosce che il contenuto della chiave "animal" è della classe Dog e procede al mapping.
Naturalmente, così com'è, non è possibile distinguere se il contenuto della chiave "animal" fosse originariamente un array o se contenesse le informazioni sul nome della classe proprie di jackson-databind.
Questo è controllato dal metodo ObjectMapper.enableDefaultTyping().
Esiste anche un altro metodo: definire l'annotazione @JsonTypeInfo nella classe. Per i dettagli, consultare la documentazione Jackson di seguito.
Di seguito viene mostrato un esempio di codice che utilizza effettivamente il metodo ObjectMapper.enableDefaultTyping().
Come abbiamo visto, fornendo il nome della classe seguito dalle sue proprietà in JSON, è possibile creare una classe arbitraria con proprietà arbitrarie, sebbene con alcune limitazioni. Questa è la vulnerabilità sfruttata da CVE-2017-7525, probabilmente innescata dal seguente report:
Per le librerie di serializzazione/deserializzazione comunemente usate in Java come Jackson, è stato segnalato il pericolo di portare all'esecuzione di codice arbitrario manipolando i nomi delle classi, e sono state elencate le classi specifiche ritenute pericolose. Non so se sia in risposta a ciò, ma dal punto di vista delle date, subito dopo il primo commit del repository sopra, è stato aperto il seguente Issue in jackson-databind e sono iniziati i lavori.
Com'è effettivamente un dato JSON e un codice Java che sfruttano questa vulnerabilità? Un suggerimento si trova nel codice di test di jackson-databind 2.8.9 che ha risolto questo Issue:
Basato su questo codice di test, di seguito viene mostrato un esempio di codice adattato per poter verificare il funzionamento.