@@ -37,6 +37,10 @@ not match the profile.
3737| PHP 8.5.10 aarch64 | ` php:8.5.10-apache ` | ` php-8.5.10-bookworm-aarch64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
3838| PHP 8.4.26 amd64 | ` php:8.4.26-apache ` | ` php-8.4.26-bookworm-amd64 ` | yes | yes | ** yes** | reference build |
3939| PHP 8.4.26 FPM amd64 | php-8.4.26 source tarball (nginx+FPM) | ` php-8.4.26-fpm ` | http (stale string) | http (stale string) | ** yes** | ` BG(unserialize) ` chain; two-slot reclaim; HTTP-only ` no-oracle ` endgame |
40+ | PHP 8.4.25 FPM amd64 | php-8.4.25 source tarball (nginx+FPM) | ` php-8.4.25-fpm ` | http (stale string) | http (stale string) | ** yes** | same chain, calibrated geometry |
41+ | PHP 8.4.0 FPM amd64 | php-8.4.0 source tarball (nginx+FPM) | ` php-8.4.0-fpm ` | http (stale string) | http (stale string) | ** yes** | earliest 8.4 release of the branch |
42+ | PHP 8.3.35 FPM amd64 | php-8.3.35 source tarball (nginx+FPM) | ` php-8.3.35-fpm ` | http (stale string) | http (stale string) | ** yes** | last vulnerable 8.3 |
43+ | PHP 8.2.34 FPM amd64 | php-8.2.34 source tarball (nginx+FPM) | ` php-8.2.34-fpm ` | http (stale string) | http (stale string) | ** yes** | last vulnerable 8.2 |
4044| PHP 8.4.25 aarch64 | ` php:8.4.25-apache ` | ` php-8.4.25-bookworm-aarch64 ` | yes | yes | ** yes** | probabilistic stage 3; retries |
4145| PHP 8.5.0 aarch64 | ` php:8.5.0-apache ` | ` php-8.5.0-bookworm ` | yes | yes | no | stage-3 layout not found |
4246| PHP 8.3.35 aarch64 | ` php:8.3.35-apache ` | ` php-8.3.35-bookworm-aarch64 ` | yes | yes | no | stage-3 reference alive |
@@ -48,13 +52,14 @@ not match the profile.
4852| PHP 7.4.33 aarch64 | ` php:7.4-apache ` | ` php-7.4.33-bookworm-aarch64 ` | no | no | no | UAF; leak shape differs |
4953| PHP 8.2.0 aarch64 | ` php:8.2.0-apache ` | — (offsets only) | n/a | n/a | no | no heap profile |
5054
51- ** 14 profiles (13 heap + 1 FPM), 6 with verified RCE. Offsets extracted for
52- 12 builds.** The FPM profile has no Apache leak stages: ` endgame ` resolves the stale
53- ` BG(unserialize) ` struct and the php-fpm PIE base with the
54- ` /proc/<pid>/mem ` oracle each run; ` no-oracle ` leaks the two absolute bases
55- over HTTP from a stale-session string OOB read, or takes them from
56- ` --chunk ` /` --pie ` or local ` /proc/<pid>/maps ` , then brute-forces the two
57- slots ([ docs/fpm-chain.md] ( docs/fpm-chain.md ) ).
55+ ** 18 profiles (13 Apache/mod_php + 5 PHP-FPM), 10 with verified RCE. Offsets
56+ extracted for 12 Apache builds.** The PHP-FPM profiles have no Apache leak
57+ stages: the chain abuses the stale ` BG(unserialize) ` struct instead.
58+ ` no-oracle ` is fully remote — it leaks the chunk and php-fpm PIE bases over
59+ HTTP from a stale-session string OOB read (or takes them from ` --chunk ` /
60+ ` --pie ` ) and fires without touching ` /proc/<pid>/mem ` ; the lab-only ` endgame `
61+ can still read them through the oracle
62+ ([ docs/fpm-chain.md] ( docs/fpm-chain.md ) ).
5863
5964` Heap leak ` is stage 1 and ` libphp leak ` is stage 2; ` oracle (heap) ` and
6065` oracle (PIE) ` mean the FPM chain reads the heap chunk and the php-fpm PIE base
@@ -90,9 +95,17 @@ default retries because its stage 3 is probabilistic.
9095 CVEs against PHP-FPM behind nginx: an out-of-memory request leaves the
9196 ` BG(unserialize) ` pointer stale, the next request reclaims the struct, and a
9297 forged session variable turns the session shutdown path into ` zif_system ` .
93- Verified command execution on 8.4.26; ` patchcheck ` proves 8.4.27 is clean.
94- Requires a ` /proc/<pid>/mem ` oracle (` ptrace_scope=0 ` ), so it runs in the
95- local laboratory mode (` --builder ` endpoint for the 4 MB arm phase).
98+ Verified command execution on 8.2.34, 8.3.35, 8.4.0, 8.4.25 and 8.4.26;
99+ ` patchcheck ` proves 8.4.27 is clean. ` calibrate ` derives the leak and
100+ reclaim geometry of any 8.0+ build over HTTP only; ` poison ` (the
101+ ` session.upload_progress ` session-file plant) covers PHP 5.4-7.x where the
102+ struct-freeing chain does not apply; ` php2shell_fpm_matrix.py ` verifies the
103+ whole unpatched set in one run. The ` no-oracle ` endgame is fully remote —
104+ no ` /proc/<pid>/mem ` , no local shell.
105+ - ** PHP version registry** — ` tools/php2shell_fpm_versions.py ` is the single
106+ source of truth for the affected-version matrix: ` is_vulnerable() ` ,
107+ ` fixed_in() ` , ` source_is_fixed() ` classifies a tarball by the presence of the
108+ upstream fix marker instead of trusting a version string.
96109- ** Calibration factory** — ` php2shell_calibrate.py ` discovers stages 1-2 and
97110 verifies stage 3 with a real proof before writing ` rce: true ` ;
98111 ` php2shell_matrix.py ` runs the factory over a matrix of images.
@@ -159,25 +172,43 @@ python3 tools/php2shell_inventory.py
159172
160173### PHP-FPM laboratory (no Docker)
161174
162- The FPM chain runs against an amd64 user-prefix PHP build with nginx. The
163- default ` endgame ` uses the ` /proc/<pid>/mem ` oracle (` ptrace_scope=0 ` );
164- ` no-oracle ` avoids it at exploit time and leaks chunk/PIE over HTTP by default:
175+ The FPM chain runs against an amd64 user-prefix PHP build with nginx — no
176+ Docker, no root. Build nginx and a vulnerable PHP (any of 8.2.34, 8.3.35,
177+ 8.4.0, 8.4.25 or 8.4.26), then start the lab for that version:
178+
179+ ``` sh
180+ lab/fpm/build_nginx.sh
181+ lab/fpm/build_php.sh 8.4.26
182+ python3 tools/php2shell_fpm_labs.py up --only 8.4.26
183+ python3 tools/php2shell_fpm_labs.py status
184+ ```
185+
186+ The default ` endgame ` uses the ` /proc/<pid>/mem ` oracle (` ptrace_scope=0 ` );
187+ ` no-oracle ` is fully remote and leaks chunk/PIE over HTTP by default:
165188
166189``` sh
167- python3 tools/php2shell_fpm_labs.py up --version 8.4.26
168190python3 tools/php2shell_auto.py -u http://127.0.0.1:8110/index.php --sapi fpm \
169191 --builder http://127.0.0.1:8111
170192# or drive the chain directly:
171193python3 tools/php2shell_fpm.py \
172194 --base http://127.0.0.1:8110 --builder http://127.0.0.1:8111 --version 8.4.26 \
173195 endgame
174- # no /proc/<pid>/mem at exploit time (HTTP leak; --chunk/--pie override)
196+ # fully remote RCE: HTTP leak, no /proc at exploit time (--chunk/--pie override)
175197python3 tools/php2shell_fpm.py \
176198 --base http://127.0.0.1:8110 --builder http://127.0.0.1:8111 --version 8.4.26 \
177199 --command ' id>out/p1' --proof-file out/p1 no-oracle
178200# print only the leaked bases
179201python3 tools/php2shell_fpm.py \
180202 --base http://127.0.0.1:8110 --builder http://127.0.0.1:8111 --version 8.4.26 leak
203+ # derive a profile for any 8.0+ build over HTTP, then trust it:
204+ python3 tools/php2shell_fpm.py \
205+ --version 8.2.34 --base http://127.0.0.1:8406 --builder http://127.0.0.1:8407 calibrate
206+ # PHP <= 7.x: plant chosen bytes into a chosen session file (pre-auth)
207+ python3 tools/php2shell_fpm.py \
208+ --version 7.4.33 --base http://127.0.0.1:8336 --builder http://127.0.0.1:8337 \
209+ poison --sid plant7x --payload ' PLANTED_MARKER'
210+ # verify the remote chain on every unpatched build in one run (7.2 -> 8.4)
211+ python3 tools/php2shell_fpm_matrix.py
181212python3 tools/php2shell_fpm_labs.py down
182213```
183214
@@ -268,10 +299,12 @@ The shipped calibration targets:
268299A hardened production PHP (128M+, ` display_errors=Off ` , multiple workers, FPM)
269300does not match the shipped Apache calibration. Multi-worker Apache targets are
270301handled with ` --pin-workers ` ; ` display_errors=Off ` remains an open research
271- item. The FPM chain has its own requirements: nginx in front of FPM, a
272- ` /proc/<pid>/mem ` oracle (` yama ptrace_scope=0 ` ), a small consumer pool
273- (` memory_limit=8M ` , cleanup on) and a larger builder pool (64M, cleanup off)
274- for the 4 MB arm phase — see [ docs/fpm-chain.md] ( docs/fpm-chain.md ) .
302+ item. The FPM chain has its own requirements: nginx in front of FPM, a small
303+ consumer pool (` memory_limit=8M ` , cleanup on) and a larger builder pool (64M,
304+ cleanup off) for the 4 MB arm phase, sharing a session directory. The
305+ ` no-oracle ` endgame is fully remote; only the lab ` endgame ` /` primitive ` modes
306+ additionally need ` /proc/<pid>/mem ` (` yama ptrace_scope=0 ` ) — see
307+ [ docs/fpm-chain.md] ( docs/fpm-chain.md ) .
275308
276309## Multi-worker targets
277310
@@ -350,19 +383,29 @@ git -C vendor/php2shell-upstream checkout 4586f5c
350383
351384``` text
352385catalog/vulnerable.json vulnerable branches and fixed releases
353- .github/workflows/ci.yml tests and gitleaks
354- profiles/*.json heap profiles (offsets, heap params, verified flag)
386+ .github/workflows/ci.yml tests, profile validation and gitleaks
387+ .github/workflows/live-fpm.yml manual live FPM lab (build + exploit 8.4.x)
388+ .github/workflows/release.yml GitHub release on v* tags
389+ profiles/*.json build profiles (offsets, heap params, verified flag)
390+ profiles/*-fpm.json verified PHP-FPM profiles (8.2.34 - 8.4.26)
391+ profiles/schema.json generic build-profile validator
392+ profiles/fpm-schema.json strict PHP-FPM profile validator
355393profiles/offsets/*.json ELF symbol offsets per image
394+ requirements-dev.txt jsonschema + ruff (dev/CI)
356395vendor/ generated upstream checkout (git-ignored)
357396tools/
358397 php2shell_auto.py version-aware dispatcher (Apache + FPM)
359- php2shell_fpm.py PHP-FPM chain client (arm/reclaim/fire )
398+ php2shell_fpm.py PHP-FPM client (calibrate/poison/leak/no-oracle/endgame )
360399 php2shell_fpm_labs.py user-prefix FPM laboratories (no Docker)
361- php2shell_calibrate.py profile generator with proof verification
362- php2shell_matrix.py profile factory over image tags
400+ php2shell_fpm_matrix.py verify the remote chain on every unpatched build
401+ php2shell_fpm_profile.py draft FPM profile generator (nm + offsets)
402+ php2shell_fpm_versions.py affected-version registry (single source of truth)
403+ php2shell_fpm_offsets.c struct offsets compiled per build
404+ php2shell_calibrate.py Apache profile generator with proof verification
405+ php2shell_matrix.py Apache profile factory over image tags
363406 php2shell_rce.py manual upstream launcher
364407 php2shell_assess.py exposure/leak assessment (no RCE claim)
365- php2shell_labs.py disposable local laboratories
408+ php2shell_labs.py disposable local Apache laboratories
366409 php2shell_offsets.py ELF offset extraction for any image
367410 php2shell_inventory.py profile/offset table
368411 php2shell_paths.py upstream checkout resolution
@@ -372,7 +415,7 @@ tools/
372415lab/
373416 wordpress/ WordPress 8.3.33 + mod_php laboratory
374417 rce/ single-worker calibration lab and configs
375- fpm/ nginx + PHP-FPM laboratory (8.4.26 / 8.4.27 )
418+ fpm/ nginx + PHP-FPM laboratory (7.2 - 8.4; build_*.sh )
376419docs/
377420 php2shell-analysis.md technical analysis
378421 fpm-chain.md PHP-FPM chain and laboratory
@@ -450,10 +493,12 @@ OOM, which is why the 8.4/8.5 stage-3 carrier does not transfer.
450493
451494## Limitations
452495
453- - ** mod_php prefork and FPM.** The Apache chain needs prefork + ` mod_php ` ;
454- the FPM chain needs nginx + FPM with a ` /proc/<pid>/mem ` oracle
455- (` ptrace_scope=0 ` ) and two endpoints (consumer + builder pool). Other SAPIs
456- are not supported.
496+ - ** mod_php prefork and FPM.** The Apache chain needs prefork + ` mod_php ` ; the
497+ FPM chain needs nginx + FPM with two endpoints (a low-memory consumer pool and
498+ a high-memory builder pool) and a session directory shared between them. Only
499+ the lab-only ` endgame ` /` primitive ` oracle modes need ` /proc/<pid>/mem `
500+ (` ptrace_scope=0 ` ); ` no-oracle ` is fully remote. Other SAPIs are not
501+ supported.
457502- ** One worker / pinning.** Without pinning, the per-worker session state breaks
458503 stage 1.
459504- ** Per-build offsets.** A profile is tied to a ` libphp.so ` build; the same
0 commit comments