
Caso de estudio y PoC de CVE-2017-12635: Apache CouchDB 1.7.0 / 2.x < 2.1.1 - Escalada remota de privilegios
Caso de estudio y PoC de CVE-2017-12635 (Apache CouchDB 1.7.0 / 2.x < 2.1.1) - Escalada de privilegios remota
Apache CouchDB es una base de datos NoSQL orientada a documentos, implementada en Erlang.
CouchDB utiliza múltiples formatos y protocolos para almacenar, transferir y procesar sus datos; usa JSON para almacenar datos, JavaScript como su lenguaje de consultas mediante MapReduce, y HTTP como API.
CouchDB puede usarse como una base de datos de nodo único, así como en clúster.
Debido a la discrepancia entre el analizador JSON basado en Erlang y el analizador JSON basado en JavaScript, existía una vulnerabilidad en CouchDB anterior a 1.7.0 y en las versiones 2.x anteriores a 2.1.1 que permitía a usuarios no administradores escalar privilegios enviando documentos _users con claves roles duplicadas utilizadas para el control de acceso dentro de las bases de datos, incluido el caso especial del rol _admin, que denota a los usuarios administrativos.
En resumen, la vulnerabilidad permite que usuarios no administradores se otorguen privilegios de administrador.
De fábrica, CouchDB permite que cualquier solicitud sea realizada por cualquier persona. Todos tienen privilegios para hacer cualquier cosa.
CouchDB tiene el concepto de usuario administrador (por ejemplo, un administrador, un superusuario o root) al que se le permite hacer cualquier cosa en una instalación de CouchDB. Por defecto, todos son administradores. Si no te gusta eso, puedes crear usuarios administradores específicos con nombre de usuario y contraseña como credenciales.
CouchDB también define un conjunto de solicitudes que solo los usuarios administradores pueden realizar. Visita documentación oficial para más información.
CouchDB tiene una base de datos de autenticación especial que almacena a todos los usuarios registrados como documentos JSON.
CouchDB utiliza una base de datos especial (llamada _users por defecto) para almacenar información sobre los usuarios registrados. Esta es una base de datos de sistema, lo que significa que, si bien comparte la API común de bases de datos, existen algunas restricciones especiales relacionadas con la seguridad y acuerdos aplicados sobre la estructura de los documentos.
Solo los administradores pueden GET, PUT o DELETE cualquier documento en la base de datos _users.
Los usuarios solo pueden acceder (GET /_users/org.couchdb.user:<nombredeusuario>) o modificar (PUT /_users/org.couchdb.user:<nombredeusuario>) los documentos que poseen.
Cada usuario de CouchDB se almacena en formato de documento. Estos documentos contienen varios campos obligatorios que CouchDB maneja para el correcto proceso de autenticación. Nos interesa el campo roles.
El campo roles es una lista de roles de usuario. CouchDB no proporciona roles integrados, por lo que eres libre de definir los tuyos propios según tus necesidades. Sin embargo, no puedes establecer roles de sistema como _admin ahí. Además, solo los administradores pueden asignar roles a los usuarios; por defecto, todos los usuarios no tienen roles.
CouchDB está escrito en Erlang, pero permite a los usuarios especificar scripts de validación de documentos en JavaScript. Estos scripts se evalúan automáticamente cuando se crea o actualiza un documento. Se inician en un proceso nuevo y reciben documentos serializados en JSON desde el lado de Erlang.
CouchDB envía funciones y documentos a un intérprete de JavaScript. Este mecanismo es lo que permite a los usuarios escribir funciones de validación de documentos en JavaScript. La función validate_doc_update se ejecuta para cada documento creado o actualizado. Si la función de validación lanza una excepción, la actualización se deniega; cuando no lo hace, las actualizaciones se aceptan.
CouchDB utiliza la función validate_doc_update para evitar que se realicen actualizaciones de documentos inválidas o no autorizadas.
function(newDoc, oldDoc, userCtx, secObj) {...}
Argumentos:
newDoc – Nueva versión del documento que se almacenaráoldDoc – Versión anterior del documento ya almacenadouserCtx – Objeto de contexto de usuariosrcObj – Objeto de seguridadA la función se le pasa el documento nuevo de la solicitud de actualización, el documento actual almacenado en la base de datos, un Objeto de Contexto de Usuario que contiene información sobre el usuario que escribe el documento (si está presente), y un Objeto de Seguridad con listas de roles de seguridad de la base de datos.
El analizador JSON utilizado internamente por CouchDB es jiffy y el utilizado en los scripts de validación por JavaScript es JSON.
El problema es que existe una discrepancia entre JSON y jiffy al tratar con claves duplicadas.
Para una clave dada, el analizador de Erlang almacenará ambos valores, pero el analizador de JavaScript solo almacenará el último.
Por ejemplo, analizar {"name":"John", "name":"Jane"} con ambos analizadores producirá:
jiffy: {[{<<"name">>,<<"John">>},{<<"name">>,<<"Jane">>}]}JSON: {name: "Jane"}La función getter para la representación interna de los datos de CouchDB solo devolverá el primer valor.
Podemos omitir toda la validación de entrada relevante y crear un usuario administrador creando un usuario con la clave roles duplicada.
Así es como se ve el documento del nuevo usuario: {..., "roles": ["_admin"], "roles": [], ...} .
La función getter para la representación interna de los datos de CouchDB solo devolverá el primer valor. Como resultado, en el terreno de Erlang, nos veremos a nosotros mismos con el rol _admin, mientras que en el terreno de JavaScript parecemos no tener permisos especiales.
Afortunadamente para el atacante, casi toda la lógica importante relacionada con la autenticación y la autorización, aparte del script de validación de entrada, ocurre en la parte de Erlang de CouchDB.
Para esta demo, usaremos una imagen Docker del repositorio oficial couchdb. Necesitamos un cliente HTTP para realizar llamadas a la API y usaremos cURL para ello.
Se puede usar cualquier sistema operativo para esta demo.
Requisitos previos:
Estos son los comandos a ejecutar para explotar la vulnerabilidad:
Crear un contenedor basado en la imagen oficial de couchdb
docker container run -d --name couchdb-sandbox -p 5984:5984 couchdb:1.6.1
Elegimos la etiqueta 1.6.1 porque la versión 1.6.1 de CouchDB es vulnerable.
Asegurarse de que la instancia de CouchDB esté lanzada y funcionando
curl -X GET http://localhost:5984
Consulta: Todas las bases de datos en la instancia
curl -X GET http://localhost:5984/_all_dbs
Consulta: Crear una nueva base de datos llamada records
curl -X PUT http://localhost:5984/records
Consulta: Asegurarse de que la base de datos records se haya creado
curl -X GET http://localhost:5984/_all_dbs
Podemos obtener, agregar e incluso eliminar todos los registros de la instancia de CouchDB porque una instalación predeterminada de CouchDB proporciona acceso de nivel administrador a todos los usuarios que se conectan. Esta configuración se conoce como Admin Party. Podemos terminar la fiesta simplemente creando la primera cuenta de administrador.
Consulta: Crear una cuenta de administrador con las credenciales admin:admin
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'