# 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 to 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 the configuration mounts from [docker/](docker/) in this repository.
This is not a universal ASLR bypass, and ASLR is not 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 for 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 object forgery layout may not hold.
Apache-side hard prerequisites also apply:
- Must be **prefork MPM + single worker + mod_php**. After child process abort, the parent process directly `fork`s a new worker without `exec`ing, thus preserving libphp mappings and address space dependencies 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 leak fails, ASLR is still effective; retrying just 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 and confirm 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 auto-reads libphp offset
python3 exploit.py -u http://127.0.0.1:8088