@@ -35,6 +35,10 @@ not match the profile.
3535| PHP 8.5.11 aarch64 | ` php:8.5.11-apache ` | ` php-8.5.11-bookworm-aarch64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
3636| PHP 8.5.11 amd64 | ` php:8.5.11-apache ` | ` php-8.5.11-bookworm-amd64 ` | yes | yes | ** yes** | separate offsets, same ` htdata ` |
3737| PHP 8.5.10 aarch64 | ` php:8.5.10-apache ` | ` php-8.5.10-bookworm-aarch64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
38+ | PHP 8.5.10 amd64 | ` php:8.5.10-apache ` | ` php-8.5.10-bookworm-amd64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
39+ | PHP 8.5.5 amd64 | ` php:8.5.5-apache ` | ` php-8.5.5-bookworm-amd64 ` | yes | yes | ** yes** | vessel stage 3; ` uid=33(www-data) ` |
40+ | PHP 8.5.0 amd64 | ` php:8.5.0-apache ` | ` php-8.5.0-bookworm-amd64 ` | yes | yes | ** yes** | vessel stage 3; ` uid=33(www-data) ` |
41+ | PHP 8.5.1 – 8.5.9 amd64 | ` php:8.5.{1..9}-apache ` | ` php-8.5.<v>-bookworm-amd64 ` | yes | yes | ** yes** | vessel stage 3; ` uid=33(www-data) ` |
3842| PHP 8.4.26 amd64 | ` php:8.4.26-apache ` | ` php-8.4.26-bookworm-amd64 ` | yes | yes | ** yes** | reference build |
3943| PHP 8.4.26 deb13/apxs2 amd64 | source build (Debian 13, Apache 2.4.69 apxs2) | ` php-8.4.26-deb13-apxs2-amd64 ` | yes | yes | ** yes** | third stage-3 layout; instance-dependent; requires ` --profile deb13-apxs2 ` |
4044| 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 |
@@ -47,44 +51,73 @@ not match the profile.
4751| PHP 7.4.33 FPM amd64 | php-7.4.33 source tarball (nginx+FPM) | ` php-7.4.33-fpm ` | http (stale string) | http (stale string) | ** yes** | same chain; ` poison ` also works |
4852| PHP 7.2.34 FPM amd64 | php-7.2.34 source tarball (nginx+FPM) | — (plant only) | no | no | no | arms the stale state; reclaim shape not found; ` poison ` plant, no RCE profile |
4953| PHP 8.4.25 aarch64 | ` php:8.4.25-apache ` | ` php-8.4.25-bookworm-aarch64 ` | yes | yes | ** yes** | probabilistic stage 3; retries |
50- | PHP 8.5.0 aarch64 | ` php:8.5.0-apache ` | ` php-8.5.0-bookworm ` | yes | yes | no | stage-3 layout not found |
54+ | PHP 8.4.25 amd64 | ` php:8.4.25-apache ` | ` php-8.4.25-bookworm-amd64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
55+ | PHP 8.4.20 amd64 | ` php:8.4.20-apache ` | ` php-8.4.20-bookworm-amd64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
56+ | PHP 8.4.19 amd64 | ` php:8.4.19-apache ` | ` php-8.4.19-bookworm-amd64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
57+ | PHP 8.4.11 amd64 | ` php:8.4.11-apache ` | ` php-8.4.11-bookworm-amd64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
58+ | PHP 8.4.10 amd64 | ` php:8.4.10-apache ` | ` php-8.4.10-bookworm-amd64 ` | yes | yes | ** yes** | ` uid=33(www-data) ` |
59+ | PHP 8.4.13, 8.4.8 amd64 | ` php:8.4.{8,13}-apache ` | ` php-8.4.<v>-bookworm-amd64 ` | yes | yes | no | stage-3 sweep attempts exhausted |
60+ | PHP 8.4.14 – 8.4.18, 8.4.12, 8.4.7, 8.4.6 amd64 | ` php:8.4.<v>-apache ` | ` php-8.4.<v>-bookworm-amd64 ` | yes | pending | no | stage-2 leak needs re-run (cookie decode fix) |
61+ | PHP 8.5.0 aarch64 | ` php:8.5.0-apache ` | ` php-8.5.0-bookworm ` | yes | yes | no | lab build; stage-3 layout not found |
62+ | PHP 8.3.35 amd64 | ` php:8.3.35-apache ` | ` php-8.3.35-bookworm-amd64 ` | yes | yes | no | stage-3 reference alive; vessel geometry does not fit this heap |
5163| PHP 8.3.35 aarch64 | ` php:8.3.35-apache ` | ` php-8.3.35-bookworm-aarch64 ` | yes | yes | no | stage-3 reference alive |
5264| PHP 8.3.33 mod_php | ` wordpress:7.1.1-apache ` | ` php-8.3.33-wordpress-mod_php ` | yes | yes | no | stage-3 reference alive |
65+ | PHP 8.4.5 amd64 | ` php:8.4.5-apache ` | ` php-8.4.5-bookworm-amd64 ` | yes | no | no | stale block at v0892; no libphp pointer |
66+ | PHP 8.3.20 amd64 | ` php:8.3.20-apache ` | ` php-8.3.20-bookworm-amd64 ` | yes | no | no | stale block at v0892; no libphp pointer |
67+ | PHP 8.4.2 amd64 | ` php:8.4.2-apache ` | ` php-8.4.2-bookworm-amd64 ` | no | no | no | UAF; leak shape differs |
5368| PHP 8.3.0 aarch64 | ` php:8.3.0-apache ` | ` php-8.3.0-bookworm ` | no | no | no | ` PS(id) ` dangling; cookie not reclaimed |
69+ | PHP 8.2.34 amd64 | ` php:8.2.34-apache ` | ` php-8.2.34-bookworm-amd64 ` | no | no | no | UAF; leak shape differs |
5470| PHP 8.2.34 aarch64 | ` php:8.2.34-apache ` | ` php-8.2.34-bookworm-aarch64 ` | no | no | no | UAF; leak shape differs |
55- | PHP 8.1.34 aarch64 | ` php:8.1-apache ` | ` php-8.1.34-bookworm-aarch64 ` | no | no | no | UAF; leak shape differs |
71+ | PHP 8.2.28 amd64 | ` php:8.2.28-apache ` | ` php-8.2.28-bookworm-amd64 ` | no | no | no | UAF; leak shape differs |
72+ | PHP 8.1.34 amd64 | ` php:8.1-apache ` (bullseye) | ` php-8.1.34-bullseye-amd64 ` | no | no | no | UAF; leak shape differs; EOL branch |
73+ | PHP 8.1.34 aarch64 | ` php:8.1-apache ` | ` php-8.1.34-bookworm-aarch64 ` | no | no | no | UAF; leak shape differs; EOL branch |
5674| PHP 8.0.30 aarch64 | ` php:8.0-apache ` | ` php-8.0.30-bookworm-aarch64 ` | no | no | no | UAF; leak shape differs |
75+ | PHP 7.4.33 amd64 | ` php:7.4-apache ` (buster) | ` php-7.4.33-buster-amd64 ` | no | no | no | UAF; leak shape differs; EOL branch |
5776| PHP 7.4.33 aarch64 | ` php:7.4-apache ` | ` php-7.4.33-bookworm-aarch64 ` | no | no | no | UAF; leak shape differs |
5877| PHP 8.2.0 aarch64 | ` php:8.2.0-apache ` | — (offsets only) | n/a | n/a | no | no heap profile |
59-
60- ** 22 profiles (14 Apache/mod_php + 8 PHP-FPM), 14 with verified RCE. Offsets
61- extracted for 12 official-image Apache builds.** The PHP-FPM profiles are
62- ** contributor-reported, not independently confirmed** and environment-specific (the compile-time prefix
63- shifts the heap); re-run ` calibrate ` on the host that will run the exploit
64- instead of trusting the shipped geometry. They have no Apache leak stages: the
65- chain abuses the stale ` BG(unserialize) ` struct instead.
66- ` no-oracle ` is fully remote — it leaks the chunk and php-fpm PIE bases over
67- HTTP from a stale-session string OOB read (or takes them from ` --chunk ` /
68- ` --pie ` ) and fires without touching ` /proc/<pid>/mem ` ; the lab-only ` endgame `
69- can still read them through the oracle
70- ([ docs/fpm-chain.md] ( docs/fpm-chain.md ) ).
71-
72- ` Heap leak ` is stage 1 and ` libphp leak ` is stage 2; ` oracle (heap) ` and
73- ` oracle (PIE) ` mean the FPM chain reads the heap chunk and the php-fpm PIE base
74- from ` /proc/<pid>/mem ` in local lab mode instead. ` RCE ` is a real command proof
75- on an isolated lab. ` no ` means not verified, not necessarily impossible; ` n/a `
76- means no heap profile exists for that build. The 8.2.0 row is an
77- offsets-only entry. All ` yes ` Apache rows use the lab configuration: prefork
78- ` mod_php ` , one worker, ` display_errors=On ` , ` memory_limit=8M ` and
79- ` session.upload_progress=On ` ; the FPM row uses the nginx+FPM lab. Per-image
78+ | PHP 8.0.30 amd64 | ` php:8.0-apache ` | — (offsets only) | n/a | n/a | no | no heap profile |
79+
80+ ** 56 profiles (48 Apache/mod_php + 8 PHP-FPM), 30 with verified RCE. Offsets
81+ extracted for 48 image/arch rows covering 39 official-image Apache builds.**
82+
83+ Independent re-verification on 2026-10-30 reproduced the command proof
84+ (` uid=33(www-data) ` ) for every RCE profile added by the stage-3 PR: 8.4.10,
85+ 8.4.11, 8.4.19, 8.4.20, 8.4.25 amd64 and all of 8.5.0–8.5.10 amd64 plus
86+ 8.5.11 aarch64. Those profiles say
87+ ` contributor-reported, maintainer-confirmed ` in their ` status ` : the contributor
88+ supplied the calibration and the maintainer reproduced it. The PHP-FPM profiles
89+ are ** contributor-reported, not independently confirmed** and
90+ environment-specific (the compile-time prefix shifts the heap); re-run
91+ ` calibrate ` on the host that will run the exploit. The 8.4.26 deb13/apxs2
92+ source build is likewise ** contributor-reported, not independently confirmed** .
93+ The four Apache profiles that already existed on ` main ` before the stage-3 PR
94+ were verified earlier and were not re-run during this review.
95+
96+ ` Heap leak ` is stage 1, ` libphp leak ` is stage 2 and ` RCE ` is a real command
97+ proof on an isolated lab. ` no ` means not verified, not necessarily impossible;
98+ ` pending ` means the sweep predates the cookie ` '+' ` decoding fix and the leak
99+ stage must be re-run; ` n/a ` means no heap profile exists for that build. The
100+ 8.2.0 and 8.0.30-amd64 rows are offsets-only entries. All ` yes ` Apache rows use
101+ the lab configuration: Apache prefork ` mod_php ` , one worker,
102+ ` display_errors=On ` , ` memory_limit=8M ` and ` session.upload_progress=On ` . The
103+ FPM rows use the nginx+FPM lab: ` oracle (heap) ` /` oracle (PIE) ` mean the FPM
104+ chain reads the chunk and php-fpm PIE base from ` /proc/<pid>/mem ` in local lab
105+ mode; the ` no-oracle ` path leaks them over HTTP and is fully remote. Per-image
80106offsets and ` libphp ` sha256 are in
81- [ the offset inventory] ( docs/offset-inventory.md ) . The 8.4.25 profile needs the
82- default retries because its stage 3 is probabilistic. The 8.4.26 deb13/apxs2
83- row is a ** contributor-reported, not independently confirmed source build** ,
84- not an official image: its stage-3 phase is instance-dependent (the contributor reported 4/4 runs on one Apache master and
85- 0/5+ on others), so treat ` rce: true ` as “reproduced on the reporter's lab” and
86- retry on a fresh worker/master. Because two 8.4.26 amd64 profiles exist, the
87- dispatcher requires ` --profile deb13-apxs2 ` for this build.
107+ [ the offset inventory] ( docs/offset-inventory.md ) . The 8.4.25 (aarch64) profile
108+ needs the default retries because its stage 3 is probabilistic. The 8.5.0 –
109+ 8.5.9 rows use the ** vessel stage 3** (` tools/php2shell_vessel.py ` , documented
110+ in [ the profile inventory] ( docs/profile-inventory.md ) ): the early-8.5 heap puts
111+ the dangling reference out of the upstream carrier's reach, so request S
112+ steers it one page up onto B2's blob page and a blob embeds the reference
113+ pattern itself. The 8.1/8.2/7.4 and 8.4.2 rows are the real-world "older
114+ subversion" builds: on those the stage-1 UAF shapes the heap differently and
115+ the leak does not reproduce with the 8.4.10+ parameters — an honest negative,
116+ reported as such. The 8.4.26 deb13/apxs2 row is a contributor-reported source
117+ build, not an official image: its stage-3 phase is instance-dependent (the
118+ contributor reported 4/4 runs on one Apache master and 0/5+ on others), so
119+ retry on a fresh master and select it with ` --profile deb13-apxs2 ` because
120+ two 8.4.26 amd64 profiles exist.
88121
89122## Features
90123
@@ -93,8 +126,11 @@ dispatcher requires `--profile deb13-apxs2` for this build.
93126 parameters explicitly. ` --php-version ` covers servers that hide their version;
94127 ` --arch ` selects aarch64 or amd64 (auto-detected for local containers,
95128 required for remote targets), and ` --profile ` forces an exact build.
96- - ** Memory-limit probe** — one request reveals the target ` memory_limit ` from
97- the fatal message; the chain then runs once instead of sweeping every value.
129+ - ** Configuration probe** — one request reveals the target ` memory_limit ` ,
130+ ` post_max_size ` and whether request-startup diagnostics render
131+ (` display_errors ` ). The chain then runs once instead of sweeping every
132+ memory limit, and a ` post_max_size ` that would silently reject the OOM body
133+ is reported as ` CONFIG BLOCKED ` before any exploit attempt.
98134- ** Worker pinning** — keeps N-1 prefork workers busy with idle keep-alive
99135 connections so the whole chain lands on one process. Automatic for loopback
100136 laboratories; opt-in for remote infrastructure.
@@ -168,8 +204,9 @@ Expected shape:
16820400:00.00 DISCOVER http://127.0.0.1:8105/index.php
16920500:00.01 OK PHP 8.4.26 · Apache/2.4.69 (Debian)
17020600:00.01 PREFLIGHT php-8.4.26-bookworm-amd64
171- 00:00.15 PROBE memory_limit probe: 16 MiB body
207+ 00:00.15 PROBE config probe: 16 MiB body
17220800:00.16 OK detected memory_limit=8M
209+ 00:00.16 OK display_errors=On (request-startup diagnostics are rendered)
17321000:00.17 EXECUTE memory_limit=8M · huge_mb=12
174211 │ [+] chunk base = 0x7ffffc400000
175212 │ [+] libphp base = 0x7ffffe013000
@@ -324,13 +361,18 @@ The shipped calibration targets:
324361
325362- Apache prefork + ` mod_php ` with ** one worker** ;
326363- ` display_errors=On ` , ` memory_limit=8M ` , ` max_input_vars=1000 ` ;
364+ - ** ` post_max_size ` ≥ ` memory_limit ` + 12M** (the labs ship 512M): stage 1
365+ posts a body of ` memory_limit ` + 12M, and PHP rejects a body larger than
366+ ` post_max_size ` * before* parsing it — no OOM, no leak, no error. The stock
367+ default ` post_max_size=8M ` blocks the whole chain on its own;
327368- ` session.upload_progress.enabled=On ` , ` session.use_cookies=1 ` ,
328369 ` session.use_strict_mode=0 ` .
329370
330371A hardened production PHP (128M+, ` display_errors=Off ` , multiple workers, FPM)
331372does not match the shipped Apache calibration. Multi-worker Apache targets are
332373handled with ` --pin-workers ` ; ` display_errors=Off ` remains an open research
333- item. The FPM chain runs against two supported shapes — run ` probe ` against a
374+ item. The dispatcher reports a blocking ` post_max_size ` as ` CONFIG BLOCKED ` and
375+ skips memory-limit candidates whose OOM body the target would reject. The FPM chain runs against two supported shapes — run ` probe ` against a
334376target to see which ones its configuration satisfies:
335377
336378- ** Stock installation (single surface, ` --arm-mode growth ` , auto when
@@ -572,7 +614,7 @@ OOM, which is why the 8.4/8.5 stage-3 carrier does not transfer.
572614 configuration.
573615- ** ` expose_php=Off ` changes stage 3.** Hiding ` X-Powered-By ` shifts the startup
574616 heap; the known stage-3 pads do not fit and a dedicated calibration is needed.
575- - ** 8.5.0 / 8. 3.0** have leaks or UAF preconditions but no verified stage 3.
617+ - ** 8.3.0** has UAF preconditions but no verified stage 3.
576618- ** 8.4.25** is probabilistic; use the default retries.
577619- ** Remote proof is not guaranteed.** ` id>p1 ` only works if the webroot is
578620 writable and the file is served at ` /p1 ` .
0 commit comments