
Étude de cas et POC de CVE-2017-12635 : Apache CouchDB 1.7.0 / 2.x < 2.1.1 - Élévation de privilèges à distance
Étude de cas et PoC de CVE-2017-12635 (Apache CouchDB 1.7.0 / 2.x < 2.1.1) - Élévation de privilèges à distance
Apache CouchDB est une base de données NoSQL orientée documents, implémentée en Erlang.
CouchDB utilise plusieurs formats et protocoles pour stocker, transférer et traiter ses données ; il utilise JSON pour stocker les données, JavaScript comme langage de requête avec MapReduce, et HTTP pour une API.
CouchDB peut être utilisé comme base de données à nœud unique ainsi que comme cluster.
En raison de la divergence entre le parseur JSON basé sur Erlang et le parseur JSON basé sur JavaScript, il existait une vulnérabilité dans CouchDB avant 1.7.0 et 2.x avant 2.1.1 permettant aux utilisateurs non administrateurs d'élever leurs privilèges en soumettant des documents _users avec des clés roles dupliquées utilisées pour le contrôle d'accès dans les bases de données, y compris le cas particulier du rôle _admin, qui désigne les utilisateurs administratifs.
Pour récapituler, la vulnérabilité permet aux utilisateurs non administrateurs de s'octroyer des privilèges d'administrateur.
Par défaut, CouchDB permet à n'importe qui d'effectuer n'importe quelle requête. Tout le monde a les privilèges pour tout faire.
CouchDB a la notion d'utilisateur administrateur (par exemple un administrateur, un super utilisateur ou root) qui est autorisé à tout faire sur une installation CouchDB. Par défaut, tout le monde est administrateur. Si cela ne vous plaît pas, vous pouvez créer des utilisateurs administrateurs spécifiques avec un nom d'utilisateur et un mot de passe comme identifiants.
CouchDB définit également un ensemble de requêtes que seuls les utilisateurs administrateurs sont autorisés à effectuer. Consultez la documentation officielle pour plus d'informations.
CouchDB possède une base de données d'authentification spéciale qui stocke tous les utilisateurs enregistrés sous forme de documents JSON.
CouchDB utilise une base de données spéciale (appelée _users par défaut) pour stocker les informations sur les utilisateurs enregistrés. C'est une base de données système – cela signifie que bien qu'elle partage l'API de base de données commune, certaines contraintes spéciales liées à la sécurité sont appliquées et des conventions sur la structure des documents sont utilisées.
Seuls les administrateurs peuvent GET, PUT ou DELETE un document dans la base de données _users.
Les utilisateurs ne peuvent accéder (GET /_users/org.couchdb.user:<username>) ou modifier (PUT /_users/org.couchdb.user:<username>) que les documents qu'ils possèdent.
Chaque utilisateur CouchDB est stocké au format document. Ces documents contiennent plusieurs champs obligatoires que CouchDB gère pour le processus d'authentification correct. Nous nous intéressons au champ roles.
Le champ roles est une liste de rôles utilisateur. CouchDB ne fournit aucun rôle intégré, vous êtes donc libre de définir les vôtres selon vos besoins. Cependant, vous ne pouvez pas y définir de rôles système comme _admin. De plus, seuls les administrateurs peuvent attribuer des rôles aux utilisateurs – par défaut, tous les utilisateurs n'ont aucun rôle.
CouchDB est écrit en Erlang, mais permet aux utilisateurs de spécifier des scripts de validation de documents en Javascript. Ces scripts sont automatiquement évalués lorsqu'un document est créé ou mis à jour. Ils démarrent dans un nouveau processus et reçoivent des documents sérialisés en JSON depuis le côté Erlang.
CouchDB envoie les fonctions et les documents à un interpréteur JavaScript. Ce mécanisme permet aux utilisateurs d'écrire des fonctions de validation de documents en JavaScript. La fonction validate_doc_update est exécutée pour chaque document créé ou mis à jour. Si la fonction de validation lève une exception, la mise à jour est refusée ; sinon, les mises à jour sont acceptées.
CouchDB utilise la fonction validate_doc_update pour empêcher les mises à jour de documents invalides ou non autorisées.
function(newDoc, oldDoc, userCtx, secObj) {...}
Arguments :
newDoc – Nouvelle version du document qui sera stockéeoldDoc – Version précédente du document déjà stockéuserCtx – Objet de contexte utilisateursrcObj – Objet de sécuritéLa fonction reçoit le nouveau document de la requête de mise à jour, le document actuel stocké dans la base de données, un objet de contexte utilisateur contenant des informations sur l'utilisateur qui écrit le document (si présent), et un objet de sécurité avec des listes de rôles de sécurité de la base de données.
L'analyseur JSON utilisé en interne par CouchDB est jiffy et celui utilisé dans les scripts de validation par JavaScript est JSON.
Le problème est qu'il existe une divergence entre JSON et jiffy lorsqu'il s'agit de clés dupliquées.
Pour une clé donnée, l'analyseur Erlang stockera les deux valeurs, mais l'analyseur Javascript ne stockera que la dernière.
Par exemple, analyser {"name":"John", "name":"Jane"} avec les deux analyseurs produira :
jiffy : {[{<<"name">>,<<"John">>},{<<"name">>,<<"Jane">>}]}JSON : {name: "Jane"}La fonction d'accès (getter) pour la représentation interne des données de CouchDB ne renverra que la première valeur.
Nous pouvons contourner toute la validation des entrées pertinente et créer un utilisateur administrateur en créant un utilisateur avec une clé roles dupliquée.
Voici à quoi ressemble le document du nouvel utilisateur : {..., "roles": ["_admin"], "roles": [], ...} .
La fonction d'accès pour la représentation interne des données de CouchDB ne renverra que la première valeur. Par conséquent, côté Erlang, nous nous verrons comme ayant le rôle _admin, tandis que côté Javascript, nous apparaîtrons sans permissions spéciales.
Heureusement pour l'attaquant, presque toute la logique importante concernant l'authentification et l'autorisation, en dehors du script de validation des entrées, se produit dans la partie Erlang de CouchDB.
Pour cette démonstration, nous utiliserons une image Docker du dépôt officiel couchdb. Nous avons besoin d'un client HTTP pour effectuer des appels API et nous utiliserons cURL pour cela.
N'importe quel système d'exploitation peut être utilisé pour cette démonstration.
Prérequis :
Voici les commandes à exécuter pour exploiter la vulnérabilité :
Créez un conteneur basé sur l'image officielle de couchdb
docker container run -d --name couchdb-sandbox -p 5984:5984 couchdb:1.6.1
Nous avons choisi le tag 1.6.1 car la version 1.6.1 de CouchDB est vulnérable.
Assurez-vous que l'instance CouchDB est lancée et fonctionne
curl -X GET http://localhost:5984
Requête : Toutes les bases de données de l'instance
curl -X GET http://localhost:5984/_all_dbs
Requête : Créez une nouvelle base de données nommée records
curl -X PUT http://localhost:5984/records
Requête : Assurez-vous que la base de données records est créée
curl -X GET http://localhost:5984/_all_dbs
Nous pouvons obtenir, ajouter et même supprimer tous les enregistrements de l'instance CouchDB car une installation CouchDB par défaut fournit un accès de niveau administrateur à tous les utilisateurs qui se connectent. Cette configuration est connue sous le nom d'Admin Party. Nous pouvons mettre fin à la fête simplement en créant le premier compte administrateur.