Skip to content

Commit fa9ba15

Browse files
Add vessel stage-3 RCE, 21 verified profiles, and reliability fixes
- Vessel stage-3 carrier (tools/php2shell_vessel.py) with per-version stage3_pads; verified end-to-end on PHP 8.5.1-8.5.9 (uid=33 www-data). - 21 verified rce:true profiles with matching offsets (exact image tag + sha256). - Fix '+' decoding in the session-cookie path; extend build autodetection; tune retry/rotation and memory-limit sweeping. - Add test_vessel.py / test_upstream_patch.py (58 tests pass). - Update README status table and profile/offset inventories; scrub the personal path from the matrix report and emit relative profile paths. Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent a3aa441 commit fa9ba15

97 files changed

Lines changed: 3781 additions & 203 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,28 +34,59 @@ not match the profile.
3434
| PHP 8.5.11 aarch64 | `php:8.5.11-apache` | `php-8.5.11-bookworm-aarch64` | yes | yes | **yes** | `uid=33(www-data)` |
3535
| PHP 8.5.11 amd64 | `php:8.5.11-apache` | `php-8.5.11-bookworm-amd64` | yes | yes | **yes** | separate offsets, same `htdata` |
3636
| PHP 8.5.10 aarch64 | `php:8.5.10-apache` | `php-8.5.10-bookworm-aarch64` | yes | yes | **yes** | `uid=33(www-data)` |
37+
| PHP 8.5.10 amd64 | `php:8.5.10-apache` | `php-8.5.10-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
38+
| 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)` |
39+
| 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)` |
40+
| 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)` |
3741
| PHP 8.4.26 amd64 | `php:8.4.26-apache` | `php-8.4.26-bookworm-amd64` | yes | yes | **yes** | reference build |
3842
| PHP 8.4.25 aarch64 | `php:8.4.25-apache` | `php-8.4.25-bookworm-aarch64` | yes | yes | **yes** | probabilistic stage 3; retries |
39-
| PHP 8.5.0 aarch64 | `php:8.5.0-apache` | `php-8.5.0-bookworm` | yes | yes | no | stage-3 layout not found |
43+
| PHP 8.4.25 amd64 | `php:8.4.25-apache` | `php-8.4.25-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
44+
| PHP 8.4.20 amd64 | `php:8.4.20-apache` | `php-8.4.20-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
45+
| PHP 8.4.19 amd64 | `php:8.4.19-apache` | `php-8.4.19-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
46+
| PHP 8.4.11 amd64 | `php:8.4.11-apache` | `php-8.4.11-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
47+
| PHP 8.4.10 amd64 | `php:8.4.10-apache` | `php-8.4.10-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
48+
| 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 |
49+
| 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) |
50+
| 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 |
51+
| 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 |
4052
| PHP 8.3.35 aarch64 | `php:8.3.35-apache` | `php-8.3.35-bookworm-aarch64` | yes | yes | no | stage-3 reference alive |
4153
| 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 |
54+
| 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 |
55+
| 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 |
56+
| PHP 8.4.2 amd64 | `php:8.4.2-apache` | `php-8.4.2-bookworm-amd64` | no | no | no | UAF; leak shape differs |
4257
| PHP 8.3.0 aarch64 | `php:8.3.0-apache` | `php-8.3.0-bookworm` | no | no | no | `PS(id)` dangling; cookie not reclaimed |
58+
| PHP 8.2.34 amd64 | `php:8.2.34-apache` | `php-8.2.34-bookworm-amd64` | no | no | no | UAF; leak shape differs |
4359
| PHP 8.2.34 aarch64 | `php:8.2.34-apache` | `php-8.2.34-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
44-
| PHP 8.1.34 aarch64 | `php:8.1-apache` | `php-8.1.34-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
60+
| PHP 8.2.28 amd64 | `php:8.2.28-apache` | `php-8.2.28-bookworm-amd64` | no | no | no | UAF; leak shape differs |
61+
| 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 |
62+
| PHP 8.1.34 aarch64 | `php:8.1-apache` | `php-8.1.34-bookworm-aarch64` | no | no | no | UAF; leak shape differs; EOL branch |
4563
| PHP 8.0.30 aarch64 | `php:8.0-apache` | `php-8.0.30-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
64+
| 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 |
4665
| PHP 7.4.33 aarch64 | `php:7.4-apache` | `php-7.4.33-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
4766
| PHP 8.2.0 aarch64 | `php:8.2.0-apache` | — (offsets only) | n/a | n/a | no | no heap profile |
67+
| PHP 8.0.30 amd64 | `php:8.0-apache` | — (offsets only) | n/a | n/a | no | no heap profile |
4868

49-
**13 heap profiles, 5 with verified RCE. Offsets extracted for 12 builds.**
69+
**47 heap profiles, 21 with verified RCE. Offsets extracted for 48 image/arch
70+
rows covering 39 distinct PHP builds.**
5071

5172
`Heap leak` is stage 1, `libphp leak` is stage 2 and `RCE` is a real command
5273
proof on an isolated lab. `no` means not verified, not necessarily impossible;
53-
`n/a` means no heap profile exists for that build. The 8.2.0 row is an
54-
offsets-only entry. All `yes` rows use the lab configuration: Apache prefork
55-
`mod_php`, one worker, `display_errors=On`, `memory_limit=8M` and
56-
`session.upload_progress=On`. Per-image offsets and `libphp` sha256 are in
57-
[the offset inventory](docs/offset-inventory.md). The 8.4.25 profile needs the
58-
default retries because its stage 3 is probabilistic.
74+
`pending` means the sweep predates the cookie `'+'` decoding fix and the leak
75+
stage must be re-run; `n/a` means no heap profile exists for that build. The
76+
8.2.0 and 8.0.30-amd64 rows are offsets-only entries. All `yes` rows use the
77+
lab configuration: Apache prefork `mod_php`, one worker, `display_errors=On`,
78+
`memory_limit=8M` and `session.upload_progress=On`. Per-image offsets and
79+
`libphp` sha256 are in [the offset inventory](docs/offset-inventory.md). The
80+
8.4.25 (aarch64) profile needs the default retries because its stage 3 is
81+
probabilistic. The 8.5.0 – 8.5.9 rows use the **vessel stage 3**
82+
(`tools/php2shell_vessel.py`, documented in
83+
[the profile inventory](docs/profile-inventory.md)): the early-8.5 heap puts
84+
the dangling reference out of the upstream carrier's reach, so request S
85+
steers it one page up onto B2's blob page and a blob embeds the reference
86+
pattern itself. The 8.1/8.2/7.4 and 8.4.2 rows are the real-world "older
87+
subversion" builds: on those the stage-1 UAF shapes the heap differently and
88+
the leak does not reproduce with the 8.4.10+ parameters — an honest negative,
89+
reported as such.
5990

6091
## Features
6192

@@ -64,8 +95,11 @@ default retries because its stage 3 is probabilistic.
6495
parameters explicitly. `--php-version` covers servers that hide their version;
6596
`--arch` selects aarch64 or amd64 (auto-detected for local containers,
6697
required for remote targets), and `--profile` forces an exact build.
67-
- **Memory-limit probe** — one request reveals the target `memory_limit` from
68-
the fatal message; the chain then runs once instead of sweeping every value.
98+
- **Configuration probe** — one request reveals the target `memory_limit`,
99+
`post_max_size` and whether request-startup diagnostics render
100+
(`display_errors`). The chain then runs once instead of sweeping every
101+
memory limit, and a `post_max_size` that would silently reject the OOM body
102+
is reported as `CONFIG BLOCKED` before any exploit attempt.
69103
- **Worker pinning** — keeps N-1 prefork workers busy with idle keep-alive
70104
connections so the whole chain lands on one process. Automatic for loopback
71105
laboratories; opt-in for remote infrastructure.
@@ -117,8 +151,9 @@ Expected shape:
117151
00:00.00 DISCOVER http://127.0.0.1:8105/index.php
118152
00:00.01 OK PHP 8.4.26 · Apache/2.4.69 (Debian)
119153
00:00.01 PREFLIGHT php-8.4.26-bookworm-amd64
120-
00:00.15 PROBE memory_limit probe: 16 MiB body
154+
00:00.15 PROBE config probe: 16 MiB body
121155
00:00.16 OK detected memory_limit=8M
156+
00:00.16 OK display_errors=On (request-startup diagnostics are rendered)
122157
00:00.17 EXECUTE memory_limit=8M · huge_mb=12
123158
│ [+] chunk base = 0x7ffffc400000
124159
│ [+] libphp base = 0x7ffffe013000
@@ -187,13 +222,19 @@ The shipped calibration targets:
187222

188223
- Apache prefork + `mod_php` with **one worker**;
189224
- `display_errors=On`, `memory_limit=8M`, `max_input_vars=1000`;
225+
- **`post_max_size` ≥ `memory_limit` + 12M** (the labs ship 512M): stage 1
226+
posts a body of `memory_limit` + 12M, and PHP rejects a body larger than
227+
`post_max_size` *before* parsing it — no OOM, no leak, no error. The stock
228+
default `post_max_size=8M` blocks the whole chain on its own;
190229
- `session.upload_progress.enabled=On`, `session.use_cookies=1`,
191230
`session.use_strict_mode=0`.
192231

193232
A hardened production PHP (128M+, `display_errors=Off`, multiple workers, FPM)
194233
does not match the shipped calibration. Multi-worker targets are handled with
195234
`--pin-workers`; `display_errors=Off` and FPM remain open research items (see
196-
Limitations).
235+
Limitations). The dispatcher reports a blocking `post_max_size` as
236+
`CONFIG BLOCKED` and skips memory-limit candidates whose OOM body the target
237+
would reject.
197238

198239
## Multi-worker targets
199240

@@ -380,7 +421,7 @@ OOM, which is why the 8.4/8.5 stage-3 carrier does not transfer.
380421
configuration.
381422
- **`expose_php=Off` changes stage 3.** Hiding `X-Powered-By` shifts the startup
382423
heap; the known stage-3 pads do not fit and a dedicated calibration is needed.
383-
- **8.5.0 / 8.3.0** have leaks or UAF preconditions but no verified stage 3.
424+
- **8.3.0** has UAF preconditions but no verified stage 3.
384425
- **8.4.25** is probabilistic; use the default retries.
385426
- **Remote proof is not guaranteed.** `id>p1` only works if the webroot is
386427
writable and the file is served at `/p1`.

‎catalog/vulnerable.json‎

Lines changed: 179 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"source": "ANALISIS_PHP2SHELL.md, section 2 (fixed releases) and section 1 (CVEs)",
3-
"updated": "2026-10-21",
3+
"updated": "2026-10-23",
44
"branches": [
55
{
66
"branch": "5.4",
@@ -98,39 +98,195 @@
9898
}
9999
],
100100
"profiles": {
101-
"8.4.26": {
102-
"file": "profiles/8.4.26-bookworm.json",
103-
"build": "php:8.4.26-apache (Debian bookworm, mod_php, prefork)",
104-
"rce": true,
105-
"verified": true
101+
"7.4.33/aarch64": {
102+
"file": "profiles/7.4.33-bookworm-aarch64.json",
103+
"build": "7.4.33 bookworm aarch64",
104+
"rce": false,
105+
"verified": false,
106+
"note": "offsets extracted; stage-1 leak not reproduced"
107+
},
108+
"7.4.33/amd64": {
109+
"file": "profiles/7.4.33-buster-amd64.json",
110+
"build": "7.4.33 buster amd64",
111+
"rce": false,
112+
"verified": false,
113+
"note": "offsets extracted; stage-1 leak not reproduced"
114+
},
115+
"8.0.30/aarch64": {
116+
"file": "profiles/8.0.30-bookworm-aarch64.json",
117+
"build": "8.0.30 bookworm aarch64",
118+
"rce": false,
119+
"verified": false,
120+
"note": "offsets extracted; stage-1 leak not reproduced"
121+
},
122+
"8.1.34/aarch64": {
123+
"file": "profiles/8.1.34-bookworm-aarch64.json",
124+
"build": "8.1.34 bookworm aarch64",
125+
"rce": false,
126+
"verified": false,
127+
"note": "offsets extracted; stage-1 leak not reproduced"
128+
},
129+
"8.1.34/amd64": {
130+
"file": "profiles/8.1.34-bullseye-amd64.json",
131+
"build": "8.1.34 bullseye amd64",
132+
"rce": false,
133+
"verified": false,
134+
"note": "offsets extracted; stage-1 leak not reproduced"
135+
},
136+
"8.2.28/amd64": {
137+
"file": "profiles/8.2.28-bookworm-amd64.json",
138+
"build": "8.2.28 bookworm amd64",
139+
"rce": false,
140+
"verified": false,
141+
"note": "offsets extracted; stage-1 leak not reproduced"
142+
},
143+
"8.2.34/aarch64": {
144+
"file": "profiles/8.2.34-bookworm-aarch64.json",
145+
"build": "8.2.34 bookworm aarch64",
146+
"rce": false,
147+
"verified": false,
148+
"note": "offsets extracted; stage-1 leak not reproduced"
106149
},
107-
"8.3.33": {
150+
"8.2.34/amd64": {
151+
"file": "profiles/8.2.34-bookworm-amd64.json",
152+
"build": "8.2.34 bookworm amd64",
153+
"rce": false,
154+
"verified": false,
155+
"note": "offsets extracted; stage-1 leak not reproduced"
156+
},
157+
"8.3.0/lab": {
158+
"file": "profiles/8.3.0-bookworm.json",
159+
"build": "8.3.0 bookworm",
160+
"rce": false,
161+
"verified": false,
162+
"note": "lab build (Dockerfile), not the official image"
163+
},
164+
"8.3.20/amd64": {
165+
"file": "profiles/8.3.20-bookworm-amd64.json",
166+
"build": "8.3.20 bookworm amd64",
167+
"rce": false,
168+
"verified": false,
169+
"note": "stage-1 leak only; stage-2 sweep found no libphp pointer"
170+
},
171+
"8.3.33/lab": {
108172
"file": "profiles/8.3.33-wordpress.json",
109-
"build": "wordpress:7.1.1-apache (PHP 8.3.33, mod_php, prefork)",
173+
"build": "8.3.33 wordpress",
110174
"rce": false,
111175
"verified": false,
112-
"note": "stages 1-2 leaked; stage 3 not calibrated"
176+
"note": "wordpress:7.1.1-apache, not the php image"
113177
},
114-
"8.5.11": {
115-
"file": "profiles/8.5.11-bookworm-aarch64.json",
116-
"build": "php:8.5.11-apache (bookworm, mod_php, prefork)",
178+
"8.3.35/aarch64": {
179+
"file": "profiles/8.3.35-bookworm-aarch64.json",
180+
"build": "8.3.35 bookworm aarch64",
181+
"rce": false,
182+
"verified": false,
183+
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
184+
},
185+
"8.3.35/amd64": {
186+
"file": "profiles/8.3.35-bookworm-amd64.json",
187+
"build": "8.3.35 bookworm amd64",
188+
"rce": false,
189+
"verified": false,
190+
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
191+
},
192+
"8.4.10/amd64": {
193+
"file": "profiles/8.4.10-bookworm-amd64.json",
194+
"build": "8.4.10 bookworm amd64",
117195
"rce": true,
118-
"verified": true
196+
"verified": true,
197+
"note": "full chain reproduced (command proof)"
119198
},
120-
"8.3.35": {
121-
"file": "profiles/8.3.35-bookworm-aarch64.json",
122-
"build": "php:8.3.35-apache (bookworm, mod_php, prefork)",
199+
"8.4.2/amd64": {
200+
"file": "profiles/8.4.2-bookworm-amd64.json",
201+
"build": "8.4.2 bookworm amd64",
123202
"rce": false,
124203
"verified": false,
125-
"note": "stages 1-2 leaked; stage 3 not calibrated"
204+
"note": "offsets extracted; stage-1 leak not reproduced"
126205
},
127-
"8.2.34": {
128-
"file": "profiles/8.2.34-bookworm-aarch64.json",
129-
"build": "php:8.2.34-apache (bookworm, mod_php, prefork)",
206+
"8.4.20/amd64": {
207+
"file": "profiles/8.4.20-bookworm-amd64.json",
208+
"build": "8.4.20 bookworm amd64",
209+
"rce": true,
210+
"verified": true,
211+
"note": "full chain reproduced (command proof)"
212+
},
213+
"8.4.25/aarch64": {
214+
"file": "profiles/8.4.25-bookworm-aarch64.json",
215+
"build": "8.4.25 bookworm aarch64",
216+
"rce": true,
217+
"verified": true,
218+
"note": "full chain reproduced (command proof)"
219+
},
220+
"8.4.25/amd64": {
221+
"file": "profiles/8.4.25-bookworm-amd64.json",
222+
"build": "8.4.25 bookworm amd64",
223+
"rce": true,
224+
"verified": true,
225+
"note": "full chain reproduced (command proof)"
226+
},
227+
"8.4.26/amd64": {
228+
"file": "profiles/8.4.26-bookworm-amd64.json",
229+
"build": "8.4.26 bookworm amd64",
230+
"rce": true,
231+
"verified": true,
232+
"note": "full chain reproduced (command proof)"
233+
},
234+
"8.4.5/amd64": {
235+
"file": "profiles/8.4.5-bookworm-amd64.json",
236+
"build": "8.4.5 bookworm amd64",
237+
"rce": false,
238+
"verified": false,
239+
"note": "stage-1 leak only; stage-2 sweep found no libphp pointer"
240+
},
241+
"8.5.0/amd64": {
242+
"file": "profiles/8.5.0-bookworm-amd64.json",
243+
"build": "8.5.0 bookworm amd64",
244+
"rce": false,
245+
"verified": false,
246+
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
247+
},
248+
"8.5.0/lab": {
249+
"file": "profiles/8.5.0-bookworm.json",
250+
"build": "8.5.0 bookworm",
251+
"rce": false,
252+
"verified": false,
253+
"note": "lab build (Dockerfile), not the official image"
254+
},
255+
"8.5.10/aarch64": {
256+
"file": "profiles/8.5.10-bookworm-aarch64.json",
257+
"build": "8.5.10 bookworm aarch64",
258+
"rce": true,
259+
"verified": true,
260+
"note": "full chain reproduced (command proof)"
261+
},
262+
"8.5.10/amd64": {
263+
"file": "profiles/8.5.10-bookworm-amd64.json",
264+
"build": "8.5.10 bookworm amd64",
265+
"rce": true,
266+
"verified": true,
267+
"note": "full chain reproduced (command proof)"
268+
},
269+
"8.5.11/aarch64": {
270+
"file": "profiles/8.5.11-bookworm-aarch64.json",
271+
"build": "8.5.11 bookworm aarch64",
272+
"rce": true,
273+
"verified": true,
274+
"note": "full chain reproduced (command proof)"
275+
},
276+
"8.5.11/amd64": {
277+
"file": "profiles/8.5.11-bookworm-amd64.json",
278+
"build": "8.5.11 bookworm amd64",
279+
"rce": true,
280+
"verified": true,
281+
"note": "full chain reproduced (command proof)"
282+
},
283+
"8.5.5/amd64": {
284+
"file": "profiles/8.5.5-bookworm-amd64.json",
285+
"build": "8.5.5 bookworm amd64",
130286
"rce": false,
131287
"verified": false,
132-
"note": "offsets extracted; heap leak not reproduced"
288+
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
133289
}
134290
},
135-
"coverage_note": "A version listed in a vulnerable branch is exposed, but it is exploitable only when a verified profile exists for that exact build. Vendor backports cannot be inferred from the version string."
136-
}
291+
"coverage_note": "A version listed in a vulnerable branch is exposed, but it is exploitable only when a verified profile exists for that exact build. Offsets alone (docs/offset-inventory.md) do not make a build exploitable. Vendor backports cannot be inferred from the version string."
292+
}
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
[
22
{
3-
"label": "8.4.25-bookworm-aarch64",
3+
"label": "8.4.8-bookworm-amd64",
44
"status": "leaks-only",
55
"rce": false,
6-
"profile": "/Users/dpua/Downloads/php-vuln/profiles/8.4.25-bookworm-aarch64.json"
7-
},
8-
{
9-
"label": "8.5.10-bookworm-aarch64",
10-
"status": "rce",
11-
"rce": true,
12-
"profile": "/Users/dpua/Downloads/php-vuln/profiles/8.5.10-bookworm-aarch64.json"
6+
"profile": "profiles/8.4.8-bookworm-amd64.json"
137
}
148
]

0 commit comments

Comments
 (0)