Vulnerabilidade e execução remota
Se você usou o npm para gerenciar as dependências do seu código, usou o CouchDB mais vezes do que imagina.
O CouchDB é um banco de dados não relacional “NoSQL” cujo principal objetivo é replicar dados em vários dispositivos da forma mais simples possível. Ele funciona como uma fonte de armazenamento KVS para salvar blobs de JSON (documentos), com recursos como validação de dados, consultas e autenticação de usuários integrada. Dessa forma, temos uma API muito completa para gerenciar nossos dados com autenticação embutida, tudo rodando em uma única instância, com a capacidade de se replicar e sincronizar com outras instâncias ou ambientes compatíveis de maneira rápida, simples e automatizada.
Mas não estamos aqui para entrar em mais detalhes. Se você quiser aprender mais sobre CouchDB e outras maneiras de armazenar dados, aqui está uma apresentação nossa sobre armazenamento no lado do cliente que resume melhor este tópico e o compara com outras formas tradicionais de persistir dados.
O que viemos contar hoje é sobre um exploit do CouchDB que permite que um usuário mal-intencionado (ou entediado) execute código em uma instância produtiva.
Como isso é possível?
Bem, o CouchDB foi originalmente escrito em Erlang, mas permite especificar validações de documentos em JavaScript. Esses scripts são avaliados automaticamente sempre que um documento é criado ou atualizado no banco de dados. Eles são executados em um novo processo e estão disponíveis imediatamente após serem serializados no formato JSON pelo Erlang. O problema é que o CouchDB gerencia contas de usuários por meio de uma tabela especial chamada _users
. Sempre que você cria ou modifica um usuário, o servidor verifica suas alterações com uma função em JavaScript chamada validate_doc_update
, que garante que você não esteja tentando ações não permitidas, como registrar-se como um usuário administrador.
Quem disse… “bugs”?
Ok, mas como isso acontece? Qual é a vulnerabilidade?
O problema está em uma discrepância entre o parser JSON do JavaScript (usado para validações de scripts) e o parser que o CouchDB usa internamente, chamado jiffy
, que serializa os dados antes de passá-los ao JavaScript. Quando você tenta analisar um objeto com chaves duplicadas, como {"foo":"bar", "foo":"baz"}
, acontece o seguinte:
Erlang:>> jiffy:decode("{\"foo\":\"bar\", \"foo\":\"baz\"}").
{[{<<"foo">>,<<"bar">>},{<<"foo">>,<<"baz">>}]}
JavaScript:>> JSON.parse("{\"foo\":\"bar\", \"foo\": \"baz\"}")
{foo: "baz"}
Para uma determinada chave, o parser do Erlang mantém todos os valores, mas o parser do JavaScript mantém apenas o último valor repetido. Infelizmente, a função interna do CouchDB que exibe esses dados retorna apenas o primeiro valor:
% Within couch_util:get_value
lists:keysearch(Key, 1, List).
Isso permite que os atacantes ignorem verificações importantes e criem um usuário administrador da seguinte forma:
Internamente, no Erlang, teremos permissões de administrador, enquanto no lado do JavaScript, nosso campo roles[]
aparecerá vazio, sem nenhuma permissão especial. Infelizmente para o atacante, toda a lógica importante relacionada à autenticação ocorre no lado do Erlang.
Agora que temos um usuário administrador, temos controle total sobre todo o banco de dados. O mais curioso desse exploit é que não é fácil perceber essa situação pela interface gráfica de administração (geralmente conhecida como Fauxton), pois, ao investigar o usuário que acabou de ser criado, o campo roles
aparece vazio, como se não tivesse nenhuma permissão especial—isso porque, desse lado, a análise é feita no JavaScript!
O que podemos fazer para evitar isso?
Atualize para a versão 2.2 do CouchDB ou superior para corrigir esse bug. Isso resolve a inconsistência entre os parsers e impede esse problema.
Sobrescreva o comportamento nativo do banco
_users
. Por padrão, usuários anônimos podem criar contas nesse banco de dados. Para restringir isso, modifique a função internavalidate_doc_update
no documento_design/_auth
. A verificação!is_server_or_database_admin(userCtx, secObj)
já existe; basta usá-la. Para permitir que apenas administradores criem novas contas, adicione a seguinte exceção:Adicione isso no início da verificação para bloquear qualquer ação subsequente quando o usuário não for um administrador.
A função
validate_doc_update
ficaria assim:Tenha cuidado ao usar o Docker para criar instâncias produtivas. Alguns repositórios permitem criar imagens do Docker com vulnerabilidades ocultas preexistentes. No caso do CouchDB, o repositório mostra claramente a vulnerabilidade mencionada, e aqui está como um script cria automaticamente um usuário chamado ‘wooyun’. Se você encontrar usuários estranhos criados inesperadamente no banco de dados, significa que sua imagem está infectada com essas vulnerabilidades.
¡Até logo!