# php2shell — PHP 8.4.26 session vulnerability reproduction
[Chinese](#中文) | [English](#english)
---
## English
Under the constraints of "completely remote, HTTP-only, target page has no `session_start()`, no PHP code written to target side," use **a single `exploit.py`** to achieve RCE against 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 from the [docker/](docker/) directory in this repository.
This is not a universal ASLR bypass and does not disable ASLR:
- The exploitation process leaks only the **Zend MM chunk base** and **libphp base** through two out-of-bounds reads, without leaking libc, stack, or complete memory map.
- The address space remains randomized by ASLR. Host kernel ASLR (kASLR), container runtime, kernel `mmap` behavior, allocator, and specific builds of PHP/Apache all affect heap layout and leak results.
- 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.
There are also hard prerequisites on the Apache side:
- Must be **prefork MPM + single worker + mod_php**. After the child process aborts, the parent process directly `fork`s a new worker without `exec`, preserving libphp mappings and address space dependencies across stages.
- `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 remains effective; retry merely switches workers 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 or run `docker build`: the environment is the official `php:8.4.26-apache` image with configuration mounts from `docker/`.
```bash
# Requirements: 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 readiness 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 exploitation in one command; first run auto-reads libphp offset
python3 exploit.py -u http://127.0.0.1:8088