Redis https://redis.io/ est un “in-memory data structure store” pouvant être utilisé comme base de données, cache ou “message broker”.
Vous l’utilisez probablement (ou devriez) pour optimiser les performances de vos applications.
Malheureusement, Redis n’implémente pas de réels mécanismes de sécurité ou de contrôle d’accès mais il est possible de réduire le risque en mettant en place quelques solutions de contournement et en appliquant quelques bonnes pratiques.
Authentification
L’unique mécanisme d’authentification disponible avec Redis est très primitif.
Le mot de passe doit être mis en place dans le fichier de configuration redis.conf
à l’aide de la directive requirepass
. L’authentification se fait ensuite en utilisant la commande AUTH
.
En l’absence de rate limiting, il est recommandé d’utiliser une passphrase longue, complexe et mise à jour régulièrement.
Bien sûr, il faut tout d’abord s’assurer que les instances Redis ne sont pas exposés au réseaux externes.
Chiffrement des échanges
Redis ne fournit aucun mécanisme de chiffrement. Il faut donc utiliser un tunnel TLS pour chiffrer les échanges.
Redis recommande l’utilisation du proxy Spiped http://www.tarsnap.com/spiped.html.
Injection de code avec la commande EVAL
Redis dispose d’une commande EVAL permettant d’interpréter des scripts Lua sur les instances Redis.
Cela permet par exemple de réduire la bande passante utilisée dans certains cas ou encore d’exécuter une opération atomique.
En cas de mauvais usage, la commande EVAL peut permettre à un attaquant d’injecter du code dans la commande.
Exemple :
redis.eval(` redis.call("RPUSH", "user:${userName}:todos", "${todo}"); redis.call("INCR", "user:${userName}:todoCount"); `)
L’attaquant peut facilement exécuter des commandes arbitraires en contrôlant les valeurs userName
ou todo
. Par exemple, avec la valeur todo
suivante : "); return redis.call("GET", "criticalData
.
La bonne pratique est d’utiliser des paramètres Redis.
redis.eval(` redis.call("RPUSH", "user:"..ARGS[1]..":todos", ARGS[2]); redis.call("INCR", "user:"..ARGS[1]..":todoCount"); `, ['userName', 'todo'], [userName, todo] );
Désactiver et renommer des commandes
Redis permet de renommer les commandes ou les désactiver.
Désactiver une commande
On peut donc désactiver la commande EVAL en ajoutant la directive de configuration suivante :
rename-command EVAL ""
Renommer une commande
Ou encore renommer la commande CONFIG en lui attribuant un nom imprévisible :
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
Malheureusement cela complexifie l’utilisation de Redis sans en améliorer réellement la sécurité. C’est simplement une astuce de sécurité par l’obscurité qui est une stratégie peu recommandée.
Hash vs JSON vs MessagePack vs Sérialisation Personnalisée
Redis propose plusieurs approches pour y stocker des objets.
Hashes
La première approche consiste à utiliser des “hashes” qui permettent d’accéder et de modifier un “field” directement sans accéder à l’objet en entier.
Exemple :
HSET user:123 firstName "Foo" HSET user:123 lastName "BAR" HMGET user:123 firstName lastName
Cette approche est optimale quand on connait tous les “fields” et que l’on accède le plus souvent à certains “fields” en particulier.
Elle est moins pratique et performante lorsqu’on souhaite accéder à tous les “fields” ou des sous-objets.
JSON ou MessagePack
Il est possible de stocker une représentation sérialisée d’un objet (JSON ou MessagePack de préférence) pour simplifier l’utilisation et lorsqu’on souhaite toujours accéder à l’objet en entier.
La seule condition en terme de sécurité et d’utiliser un parser / serializer JSON / MessagePack “standard” et éprouvé.
Evitez l’utilisation d’un parser / serializer JSON custom ou de construire la sérialisation JSON “manuellement”.
Sérialisation personnalisée
Par optimisation, nous pourrions imaginer l’utilisation d’un format personnalisé mais pour des raisons de sécurité, il faut absolument éviter ce genre d’approches.
Exemple de sérialisation de l’objet “user” `{firstName: ‘Foo’, lastName: ‘BAR’, isAdmin: false}` : Foo,BAR,false
.
Tout d’abord, ce format est peu extensible. Ensuite, un attaquant pourrait facilement forger le résultat sérialisé en remplaçant BAR
par BAR,true
.