diff --git a/README.md b/README.md index 8e66392..43be2cb 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ against `cleanup=1`, the poisoned session file is includable via LFI for RCE. | `bypass/waf_bypass_test.py` | WAF rules | Tests Content-Type evasion against WAF rules | | `nolfi/exploit_nolfi.py` | No LFI available | Served save_path + fpm pathinfo — **RCE with no application include** | | `fcgi/exploit_fcgi.py` | FPM reachable | FastCGI slow-drip + auto_prepend_file — **RCE, default config, no LFI** | -| `uaf-rce/exploit.py` | Apache mod_php | OOM → UAF via stale BG(serialize) → heap spray → **RCE, no LFI, no framework** | +| `uaf-rce/rce_exploit.py` | Apache mod_php | OOM → dangling PS(id) → heap spray → full ASLR bypass → **RCE, no LFI, no framework** | ## Prerequisites @@ -160,32 +160,49 @@ pool-level configuration can prevent this when FastCGI is reachable. ## UAF RCE (Apache mod_php, No LFI, No Framework) -`uaf-rce/exploit.py` exploits the same CVEs through a pure memory corruption -path that requires **no LFI, no framework, no libraries, and no non-default -PHP settings**. The only SAPI requirement is Apache mod_php with prefork MPM -(default on Debian/Ubuntu). +`uaf-rce/rce_exploit.py` exploits the same CVEs through a pure memory +corruption path that requires **no LFI, no framework, no libraries, and no +non-default PHP settings**. The only SAPI requirement is Apache mod_php with +prefork MPM (default on Debian/Ubuntu). -The attack triggers an OOM during multipart parsing to leave stale -`BG(serialize)` state in the surviving Apache prefork worker. On the next -request, the session upload progress handler reuses the stale pointer (a freed -64-byte ZendMM slot) as a `php_serialize_data` struct. Heap spray reclaims the -slot with attacker data shaped as a fake `HashTable`, giving control of -`pDestructor` for arbitrary function call. +Three-stage exploit chain with full ASLR bypass, all over plain HTTP: + +1. **Heap leak**: OOM during multipart parsing leaves a dangling `PS(id)` + pointer. A second request sprays a fake `zend_string` with `len=0x1000` + over the freed slot. `php_session_send_cookie` reads 4096 bytes of heap + data into a `Set-Cookie` header, revealing the Zend MM chunk base. +2. **Code leak**: Same mechanism, but the spray places a real `HashTable` + at the target slot. The `pDestructor` field leaks the libphp.so base. +3. **RCE**: A fake `HashTable` with `pDestructor = zif_system` and an + embedded command string is sprayed onto the heap. Session cleanup calls + `zend_hash_destroy` on the fake table, invoking `system(cmd)`. + +The `post_max_size` limit is bypassed via chunked `Transfer-Encoding`: +PHP only checks `Content-Length`, so a chunked request with no +`Content-Length` header delivers the full OOM payload (140MB) despite the +default 8MB limit. + +Tested: 5/5 first-attempt RCE on `php:8.4.26-apache`, `php:8.3.35-apache`, +and WordPress 6.8.1 on stock PHP, all with zero configuration changes. ```bash -# Confirm all primitives (OOM, worker survival, session poison, stale state) -python3 uaf-rce/exploit.py --target http://host:port/index.php --oom-only +# Auto-detect PHP version, look up offsets, exploit +python3 uaf-rce/rce_exploit.py -u http://target/index.php -# Full chain: OOM + heap spray + UAF trigger -python3 uaf-rce/exploit.py --target http://host:port/index.php +# Verify RCE via docker exec +python3 uaf-rce/rce_exploit.py -u http://target/ \ + --docker-proof CONTAINER /tmp/pwn -# With crash detection -python3 uaf-rce/exploit.py --target http://host:port/index.php --crash-test +# Multi-worker Apache +python3 uaf-rce/rce_exploit.py -u http://target/ --workers 150 + +# Custom command (max 14 bytes; use 'sh/tmp/pwn' ``` PHP-FPM is not vulnerable: workers exit on bailout (code 70), so stale state -never persists across requests. See `uaf-rce/FINDINGS.md` for the full -vulnerability analysis and patch details. +never persists across requests. See `uaf-rce/README.md` for the full +vulnerability analysis, patch details, and offset database. ## Patch Bypass Findings