# php2shell — PHP 8.4.26 Session Vulnerability Reproduction
[中文](#中文) | [English](#english)
---
## English
Under the constraints of "fully remote, HTTP only, target page has no `session_start()`, no PHP written on target side," use **a single `exploit.py`** to achieve RCE on PHP 8.4.26 + Apache prefork + mod_php. The target page contains only `<?php echo "OK\n"; ?>`.
### Supported Scope (Read First)
> **The only supported runtime environment is the official `php:8.4.26-apache` image** (PHP 8.4.26 / Debian 12 / Apache prefork + mod_php) with configuration in the `docker/` directory of this repository.
This is not a generic ASLR bypass, nor is ASLR disabled:
- The exploit leaks **Zend MM chunk base** and **libphp base** through only two out-of-bounds reads, without leaking libc, the stack, or the complete memory map.
- The address space remains randomized by ASLR. Host kernel ASLR (kASLR), container runtime, kernel `mmap` behavior, allocators, and PHP/Apache's specific build all affect heap layout and leak results.
- Heap geometry, calibration values, and libphp offsets in the chain are measured on the official image and do not include address scanning or brute-force enumeration. After changing the image, PHP build, or distribution, even if addresses can still be leaked, the subsequent forged object layout may not hold.
There are also hard prerequisites on the Apache side:
- Must be **prefork MPM + single worker + mod_php**. After a child process aborts, the parent process directly `fork`s a new worker without `exec`, thereby preserving libphp mapping and address space required at each stage.
- `docker/apache.conf` fixes `StartServers/MinSpareServers/MaxSpareServers/MaxRequestWorkers/ServerLimit = 1`, `MaxConnectionsPerChild = 0`, and disables KeepAlive.
- Multiple workers, event/worker MPM, Apache restart/graceful restart, worker re-`exec`, container rebuild, or parent process exit may re-randomize addresses or route requests to different processes, breaking the entire chain.
- If any stage of leaking fails, ASLR remains effective; retrying only switches to a different worker and does not "defeat" ASLR.
The current RCE also requires: `display_errors=1`, `memory_limit=8M`, `max_input_vars=1000`, `session.use_cookies=1`, `session.use_strict_mode=0`, `session.upload_progress.enabled=1`.
### One-Command Reproduction
This repository does not compile PHP/Apache and has no `docker build`: the environment is the official `php:8.4.26-apache` image plus config mounts from the `docker/` directory.
```bash
# Dependencies: 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) Pull official image (PHP 8.4.26 / Debian 12 / Apache prefork)
docker compose -f docker/docker-compose.yml pull
# 2) Start target: single worker, display_errors=On, memory_limit=8M
docker compose -f docker/docker-compose.yml up -d
# 3) Wait for Apache to be ready and verify normal page
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) One command to complete the entire exploit; first run auto-reads libphp offset
python3 exploit.py -u http://127.0.0.1:8088
```