# php2shell — PHP 8.4.26 Session Vulnerability Reproduction
[中文](#中文) | [English](#english)
---
## English
Under the constraints of "fully remote, HTTP only, target page without `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) combined with the configuration in [docker/](docker/) of this repository.
This is not a generic ASLR bypass, nor is ASLR disabled:
- The exploitation process leaks **Zend MM chunk base** and **libphp base** through only two out-of-bounds reads, without leaking libc, stack, or complete memory maps.
- The address space remains randomized by ASLR. Host kernel ASLR (kASLR), container runtime, kernel `mmap` behavior, allocator, and the specific builds of PHP/Apache all affect heap layout and leak results.
- The heap geometry, calibration values, and libphp offsets in the chain are empirically measured against the official image and do not include address scanning or brute-force enumeration. After switching images, PHP builds, or distributions, even if addresses can still be leaked, the subsequent object forgery layout may not hold.
Apache also has hard prerequisites:
- Must be **prefork MPM + single worker + mod_php**. After a child process aborts, the parent process directly `fork`s a new worker without re-`exec`, thus preserving the 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.
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 configuration mounts from `docker/`.
```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) Complete the entire exploit with one command; first run automatically reads libphp offset
python3 exploit.py -u http://127.0.0.1:8088