Sécuriser Redis en 5 étapes

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.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s