
CVE-2020-2551 POC da utilizzare su Internet
POC di test (utilizzabile per test su Internet)
python CVE-2020-2551.py [HOST] [IP]
Tentativo di modifica del codebase per il caricamento remoto delle classi (fallito)
python CVE-2020-TEST.py [HOST] [IP]
(Pubblicato originariamente su Anquanke, link originale)
Per studiare questa vulnerabilità servono alcune conoscenze preliminari, come CORBA e RMI.
Ecco una breve panoramica:
CORBA è uno standard tecnico definito da OMG per le applicazioni distribuite; utilizza IDL per il supporto cross-language e la comunicazione tra client e server avviene tramite il protocollo IIOP.
RMI è un'altra tecnologia per applicazioni distribuite. In Java può essere semplificata con JNDI; client e server comunicano tramite il protocollo JRMP, ma in WebLogic l'RMI usa il protocollo T3, e in passato sono state scoperte molte vulnerabilità al riguardo.
RMI-IIOP combina i punti di forza di RMI e CORBA e consente di distribuire applicazioni RMI tramite il protocollo IIOP.
Anche la documentazione ufficiale afferma:
Gli oggetti server RMI possono utilizzare il protocollo IIOP e comunicare con oggetti client CORBA scritti in qualsiasi linguaggio.
Per ora lasciamo da parte WebLogic e concentriamoci su come scrivere un esempio RMI-IIOP:
Per il codice client si può fare riferimento al progetto di test presente in Le cose da sapere su RMI, JNDI, LDAP, JRMP, JMX e JMS in Java (Parte 1); si possono compilare da sé HelloClient e HelloServer, oppure usare quelli già compilati nel progetto di test.
Avviare il name server da riga di comando (fornito con Java):
start orbd -ORBInitialPort 1050
Avviare il server HelloServer da riga di comando e configurare il debug remoto; per come usare IDEA per il debug remoto, si può fare riferimento al metodo indicato all'inizio di questo articolo.
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer
Ovviamente si può anche avviare direttamente senza debug remoto e vedere il risultato:
java HelloServer
Avviare il client da riga di comando:
Java HelloClient
A questo punto si aprirà la calcolatrice; se il debug remoto funziona, si può vedere la seguente stack trace:
L'esecuzione del comando avviene in EvilMessage.readObejct().

Nota a margine: articoli su installazione e debug di WebLogic.
E l'RMI-IIOP in WebLogic? L'articolo RMI-IIOP in Java menziona lo sfruttamento dell'RMI-IIOP di WebLogic; sulla base di questo ho svolto alcune ricerche. Using WebLogic’s RMI over IIOP illustra diversi modi in cui WebLogic può usare client RMI-IIOP, tra cui:
La differenza tra i primi due metodi sembra essere solo nell'impostazione di JNDI_FACTORY.

In precedenza, studiando la deserializzazione T3 di WebLogic, avevo distribuito su WebLogic un'applicazione Helloserver con un metodo sayhello() utilizzabile. Ho provato a impostare entrambi i tipi di JNDI_FACTORY e a chiamarlo: con il secondo tipo di JNDI_FACTORY sono riuscito a invocare con successo il metodo sayHello().

Ho quindi modificato il POC per il protocollo T3 di WebLogic: in pratica ho solo cambiato RMI in IIOP. Ho scoperto che la catena di gadget jtaTransactionManager viene eseguita con successo e viene inviata una richiesta jrmp al jrmplisten locale.

Osservando il traffico, durante la chiamata al metodo remove() viene inviata una richiesta remove__java_lang_Object; il traffico contiene dati malevoli, ma non ho trovato la magic header aced.

C'è da supporre che sul server venga eseguito un parsing speciale prima della deserializzazione dei dati. Guardando la stack trace, la parte finale della catena di esecuzione è molto simile a quella del RMI-IIOP nativo visto prima: lì si parte da CDRInputStream.read_value(), qui da IIOPInputStream.read_value() di WebLogic (questo punto read_value era stato menzionato anche nella presentazione del 2019).

Qui la richiesta viene prima gestita da clusterableServerRef.invoke(); a seconda dell'invoker viene chiamato this.invoker.invoke(). In questo caso viene invocato Mejb_dj5nps_HomeImpl_WLSkel.invoke(); poiché è “remove”, si entra nel ramo case 6 che chiama IIOPInputStream.readObject(); nel metodo read_value() vengono analizzati i dati di IIOPInputStream, innescando la deserializzazione. Questo è il POC che sfrutta il metodo remove().
L'[articolo](https://lucifaer.com/2020/02/25/WebLogic WLS核心组件RCE分析(CVE-2020-2551)/?from=timeline&isappinstalled=0#2-2-Weblogic解析流程) di analisi di Lucifaer menziona lo sfruttamento tramite il metodo bind(), che è anche il metodo di sfruttamento più comune su Internet; seguiamo la stack trace.

Come prima, la richiesta viene prima gestita da clusterableServerRef.invoke(); a seconda dell'invoker viene chiamato this.invoker.invoke(). Qui viene invocato CobraServerRef.invoke(); successivamente, in _NamingContextAnyImplBase._invoke(), poiché va1 è “bind_any”, si entra nel ramo case 0 che chiama IIOPInputStream.read_any(); in seguito verrà comunque chiamato IIOPInputStream.read_value() per innescare la deserializzazione. Prima ho detto che nel traffico non avevo visto la magic header aced: il motivo è che in IIOPInputStream esiste un meccanismo di parsing. La forma hex-value di IIOPInputStream è la seguente e contiene il nome della classe e le informazioni sui campi:

Alla fine viene chiamato il metodo readObejct() della classe maliziosa.

Ho dato un'occhiata alla patch e ho scoperto che si trova nello stesso punto della patch per lo sfruttamento della deserializzazione T3 del 2015.

Nei test di Analisi della vulnerabilità WebLogic CVE-2020-2551 si vede che anche le classi filtrate per CVE-2020-2551 si trovano nella classe weblogic.iiop.Utils.

Tuttavia, nei test locali, WebLogic 10.3.6 con la patch del 2015 non ha attivato la funzione isBlacklisted() (ma sia MsgAbbrevInputStream che InboundMsgAbbrev chiamano isBlacklisted() per la verifica della blacklist... strano).

La patch di CVE-2020-2551 ha aggiunto il metodo di filtraggio verifyclassermitted() in weblogic.iiop.Utils.LoadClass().

La blacklist filtra le classi malevole, inclusa la classe padre di JtaTransactionManager, com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager, una classe inclusa in WebLogic e molto pericolosa. Mentre guardavo la patch mi è venuta un'idea: poiché la verifica alla riga 606 avviene dopo LoadClass(), se durante il caricamento di className venisse caricata una classe ed eseguito un blocco statico malevolo, non si aggirerebbe la difesa? Ne parlerò più avanti.
Il POC scritto in Java ha problemi di rete: funziona se si colpisce direttamente un servizio WebLogic locale, ma non funziona contro un container Docker o una macchina su Internet. Ecco gli articoli di analisi che menzionano il problema:
Guida passo passo per risolvere i problemi di rete del POC di WebLogic CVE-2020-2551
Considerazioni su WebLogic-CVE-2020-2551
Di seguito si esegue il debug del POC; si può fare riferimento a quello di remove() descritto prima, oppure a quello di Y4er. I due articoli precedenti menzionano due metodi di soluzione:
Ho provato entrambi. Dopo aver ricompilato WebLogic, compare l'errore java.lang.NoSuchMethodError:weblogic.security.subject.SubjectManager.installCESubjectManager, ma non ho trovato una soluzione.
Ho quindi provato a simulare il protocollo IIOP, iniziando impostando un breakpoint nel POC per il debug.

Ho scoperto che durante new InitialContext(env), in EndPointImpl.sendReceive() vengono inviati e ricevuti due pacchetti.

LocateReply contiene le informazioni IOR. A questo punto bisogna capire cos'è l'IOR: quando un client RMI-IIOP interagisce con un oggetto server tramite il protocollo IIOP, fornisce host e porta necessari per la comunicazione IIOP; la parte nel riquadro rosso, Object_key, serve a distinguere i diversi oggetti sul server.

Nella simulazione del protocollo IIOP, il punto da tenere d'occhio è Object_key; host e IP in realtà non influiscono. All'inizio dei test ho semplicemente riprodotto di nuovo tutti i pacchetti; inviando resolve_any ho ricevuto in risposta location forward.

La documentazione ufficiale di GIOP dice che in caso di location forward l'Object_key cambia: richieste diverse possono restituire Object_key diversi (qui l'Object_key è il key address nel pacchetto dati). Come detto prima, questo Object_key serve a distinguere con quale oggetto comunicare quando si usa il protocollo IIOP; questo valore va ottenuto dinamicamente dalla LocateReply.

Alla fine, qui non ho scelto di simulare remove(), ma di simulare la richiesta IIOP emessa dal metodo bind(), perché ci sono meno pacchetti. Guardiamo il pacchetto dati dell'utilizzo normale in locale:

Viene inviato LocateRequest, si ricevono i dati e tramite un'espressione regolare si estrae il key address dalla LocateReply.

Imposto manualmente l'indirizzo del server jrmp malevolo (rmi://...) e invio il pacchetto bind_any ogni secondo. Poiché la richiesta jrmp generata da questa catena di gadget non usa DGCClient, non è soggetta a JEP290 e può essere sfruttata tramite jrmplisten.

Il POC è stato testato con successo in un ambiente Docker. Si può usare l'ambiente SSRF di vulhub, impostando l'IP dell'host; Docker ha ricevuto correttamente la richiesta jrmp dell'host. Il codice è su GitHub.

Prima ho menzionato l'idea di usare il codebase per caricare codice remoto e aggirare i controlli. Chi ha studiato gli attacchi JNDI dovrebbe sapere che il codebase può essere usato per specificare la posizione di classi remote: se il codebase è controllabile e il programma consente il caricamento remoto delle classi, è possibile caricare una classe remota maliziosa ed eseguire il codice malevolo nel blocco statico.
Leggendo il codice si vede che il secondo parametro di weblogic.iiop.Utils.lodaClass() rappresenta il codebase; questo parametro viene letto in IIOPInputStream.read_value(), ovvero il parametro var8. Alla riga 1659 viene chiamato readIndirectingRepositoryId(var8), che alla fine richiama weblogic.iiop.Utils.lodaClass(). Per eseguire le righe 1644 e 1659, devono valere (va4r & 1)=1 e (va4 & 6)=2, quindi var4 deve essere 3.
La stack trace da readIndirectingRepositoryId a getClassFromId termina eseguendo loadclass() alla riga 304.

Osservando il pacchetto bind_any, è composto essenzialmente da GIOP Header e GIOP Request; GIOP Request include anche key address (uguale a quello nella LocateteReply), ServiceContextList e stub_data. Il valore di var4 è \x7f\xff\xff\x02 presente in stub_data, quindi (va4r & 1)=0 e (va4 & 6)=2: la riga 1644 per impostare il codebase non viene eseguita.

Modificando il primo riquadro qui sotto, \x7f\xff\xff\x02, in \x00\x00\x00\x03, e aggiungendo nel secondo riquadro la lunghezza e il valore del codebase (il codice è su GitHub), si può notare che viene eseguita anche un'operazione di allineamento. Questo è un punto insidioso: prima di leggere le informazioni sulla classe successiva, viene controllato se la posizione del byte successivo è un multiplo di 4; in caso contrario, vengono ignorati alcuni byte. Ad esempio, se la posizione del byte successivo è 1, vengono ignorati 3 byte e la lettura riparte dal byte in posizione 4. Questa posizione è relativa all'intero pacchetto bind_any; se il byte non è allineato a un multiplo di 4, si aggiungono zeri di riempimento.

Un altro problema: guardando la precedente stack trace da readIndirectingRepositoryId a getClassFromId, si passa dalla funzione findClassInfo(). Qui, se una classe è già stata caricata, le informazioni sull'ID della classe vengono salvate; findClassInfo() restituisce direttamente le informazioni della classe senza entrare in weblogic.iiop.Utils.getClassFromID().

Quindi, durante i test, ogni volta bisogna cambiare il nome della classe.

Comunque sia, alla fine sono riuscito a simulare il protocollo IIOP, a modificare il valore del codebase e a eseguire la funzione weblogic.iiop.Utils.getClassFromId().
Sfortunatamente, il recupero di RMIURLClassFinder restituisce NULL e la funzione RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() restituisce false.

Il motivo è che il valore del parametro _NetworkClassLoadingEnable in ServerMBeanImpl è False.

Volevo vedere in quale file di configurazione di WebLogic viene impostato questo parametro, ma non l'ho trovato...
In realtà, durante lo studio di questa vulnerabilità ho scoperto che servono molte conoscenze preliminari, come la deserializzazione Java, RMI, JNDI e simili. Per approfondire si può fare riferimento a questa rubrica di articoli. Anche se alla fine il tentativo di sfruttamento modificando il codebase è fallito, ho comunque imparato molto.