
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
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.