Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 55 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,59 @@ not match the profile.
| PHP 8.5.11 aarch64 | `php:8.5.11-apache` | `php-8.5.11-bookworm-aarch64` | yes | yes | **yes** | `uid=33(www-data)` |
| PHP 8.5.11 amd64 | `php:8.5.11-apache` | `php-8.5.11-bookworm-amd64` | yes | yes | **yes** | separate offsets, same `htdata` |
| PHP 8.5.10 aarch64 | `php:8.5.10-apache` | `php-8.5.10-bookworm-aarch64` | yes | yes | **yes** | `uid=33(www-data)` |
| PHP 8.5.10 amd64 | `php:8.5.10-apache` | `php-8.5.10-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
| 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)` |
| 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)` |
| 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)` |
| PHP 8.4.26 amd64 | `php:8.4.26-apache` | `php-8.4.26-bookworm-amd64` | yes | yes | **yes** | reference build |
| PHP 8.4.25 aarch64 | `php:8.4.25-apache` | `php-8.4.25-bookworm-aarch64` | yes | yes | **yes** | probabilistic stage 3; retries |
| PHP 8.5.0 aarch64 | `php:8.5.0-apache` | `php-8.5.0-bookworm` | yes | yes | no | stage-3 layout not found |
| PHP 8.4.25 amd64 | `php:8.4.25-apache` | `php-8.4.25-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
| PHP 8.4.20 amd64 | `php:8.4.20-apache` | `php-8.4.20-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
| PHP 8.4.19 amd64 | `php:8.4.19-apache` | `php-8.4.19-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
| PHP 8.4.11 amd64 | `php:8.4.11-apache` | `php-8.4.11-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
| PHP 8.4.10 amd64 | `php:8.4.10-apache` | `php-8.4.10-bookworm-amd64` | yes | yes | **yes** | `uid=33(www-data)` |
| 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 |
| 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) |
| 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 |
| 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 |
| PHP 8.3.35 aarch64 | `php:8.3.35-apache` | `php-8.3.35-bookworm-aarch64` | yes | yes | no | stage-3 reference alive |
| 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 |
| 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 |
| 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 |
| PHP 8.4.2 amd64 | `php:8.4.2-apache` | `php-8.4.2-bookworm-amd64` | no | no | no | UAF; leak shape differs |
| PHP 8.3.0 aarch64 | `php:8.3.0-apache` | `php-8.3.0-bookworm` | no | no | no | `PS(id)` dangling; cookie not reclaimed |
| PHP 8.2.34 amd64 | `php:8.2.34-apache` | `php-8.2.34-bookworm-amd64` | no | no | no | UAF; leak shape differs |
| PHP 8.2.34 aarch64 | `php:8.2.34-apache` | `php-8.2.34-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
| PHP 8.1.34 aarch64 | `php:8.1-apache` | `php-8.1.34-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
| PHP 8.2.28 amd64 | `php:8.2.28-apache` | `php-8.2.28-bookworm-amd64` | no | no | no | UAF; leak shape differs |
| 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 |
| PHP 8.1.34 aarch64 | `php:8.1-apache` | `php-8.1.34-bookworm-aarch64` | no | no | no | UAF; leak shape differs; EOL branch |
| PHP 8.0.30 aarch64 | `php:8.0-apache` | `php-8.0.30-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
| 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 |
| PHP 7.4.33 aarch64 | `php:7.4-apache` | `php-7.4.33-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
| PHP 8.2.0 aarch64 | `php:8.2.0-apache` | — (offsets only) | n/a | n/a | no | no heap profile |
| PHP 8.0.30 amd64 | `php:8.0-apache` | — (offsets only) | n/a | n/a | no | no heap profile |

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

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

## Features

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

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

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

## Multi-worker targets

Expand Down Expand Up @@ -380,7 +421,7 @@ OOM, which is why the 8.4/8.5 stage-3 carrier does not transfer.
configuration.
- **`expose_php=Off` changes stage 3.** Hiding `X-Powered-By` shifts the startup
heap; the known stage-3 pads do not fit and a dedicated calibration is needed.
- **8.5.0 / 8.3.0** have leaks or UAF preconditions but no verified stage 3.
- **8.3.0** has UAF preconditions but no verified stage 3.
- **8.4.25** is probabilistic; use the default retries.
- **Remote proof is not guaranteed.** `id>p1` only works if the webroot is
writable and the file is served at `/p1`.
Expand Down
202 changes: 179 additions & 23 deletions catalog/vulnerable.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"source": "ANALISIS_PHP2SHELL.md, section 2 (fixed releases) and section 1 (CVEs)",
"updated": "2026-10-21",
"updated": "2026-10-23",
"branches": [
{
"branch": "5.4",
Expand Down Expand Up @@ -98,39 +98,195 @@
}
],
"profiles": {
"8.4.26": {
"file": "profiles/8.4.26-bookworm.json",
"build": "php:8.4.26-apache (Debian bookworm, mod_php, prefork)",
"rce": true,
"verified": true
"7.4.33/aarch64": {
"file": "profiles/7.4.33-bookworm-aarch64.json",
"build": "7.4.33 bookworm aarch64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"7.4.33/amd64": {
"file": "profiles/7.4.33-buster-amd64.json",
"build": "7.4.33 buster amd64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.0.30/aarch64": {
"file": "profiles/8.0.30-bookworm-aarch64.json",
"build": "8.0.30 bookworm aarch64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.1.34/aarch64": {
"file": "profiles/8.1.34-bookworm-aarch64.json",
"build": "8.1.34 bookworm aarch64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.1.34/amd64": {
"file": "profiles/8.1.34-bullseye-amd64.json",
"build": "8.1.34 bullseye amd64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.2.28/amd64": {
"file": "profiles/8.2.28-bookworm-amd64.json",
"build": "8.2.28 bookworm amd64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.2.34/aarch64": {
"file": "profiles/8.2.34-bookworm-aarch64.json",
"build": "8.2.34 bookworm aarch64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.3.33": {
"8.2.34/amd64": {
"file": "profiles/8.2.34-bookworm-amd64.json",
"build": "8.2.34 bookworm amd64",
"rce": false,
"verified": false,
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.3.0/lab": {
"file": "profiles/8.3.0-bookworm.json",
"build": "8.3.0 bookworm",
"rce": false,
"verified": false,
"note": "lab build (Dockerfile), not the official image"
},
"8.3.20/amd64": {
"file": "profiles/8.3.20-bookworm-amd64.json",
"build": "8.3.20 bookworm amd64",
"rce": false,
"verified": false,
"note": "stage-1 leak only; stage-2 sweep found no libphp pointer"
},
"8.3.33/lab": {
"file": "profiles/8.3.33-wordpress.json",
"build": "wordpress:7.1.1-apache (PHP 8.3.33, mod_php, prefork)",
"build": "8.3.33 wordpress",
"rce": false,
"verified": false,
"note": "stages 1-2 leaked; stage 3 not calibrated"
"note": "wordpress:7.1.1-apache, not the php image"
},
"8.5.11": {
"file": "profiles/8.5.11-bookworm-aarch64.json",
"build": "php:8.5.11-apache (bookworm, mod_php, prefork)",
"8.3.35/aarch64": {
"file": "profiles/8.3.35-bookworm-aarch64.json",
"build": "8.3.35 bookworm aarch64",
"rce": false,
"verified": false,
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
},
"8.3.35/amd64": {
"file": "profiles/8.3.35-bookworm-amd64.json",
"build": "8.3.35 bookworm amd64",
"rce": false,
"verified": false,
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
},
"8.4.10/amd64": {
"file": "profiles/8.4.10-bookworm-amd64.json",
"build": "8.4.10 bookworm amd64",
"rce": true,
"verified": true
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.3.35": {
"file": "profiles/8.3.35-bookworm-aarch64.json",
"build": "php:8.3.35-apache (bookworm, mod_php, prefork)",
"8.4.2/amd64": {
"file": "profiles/8.4.2-bookworm-amd64.json",
"build": "8.4.2 bookworm amd64",
"rce": false,
"verified": false,
"note": "stages 1-2 leaked; stage 3 not calibrated"
"note": "offsets extracted; stage-1 leak not reproduced"
},
"8.2.34": {
"file": "profiles/8.2.34-bookworm-aarch64.json",
"build": "php:8.2.34-apache (bookworm, mod_php, prefork)",
"8.4.20/amd64": {
"file": "profiles/8.4.20-bookworm-amd64.json",
"build": "8.4.20 bookworm amd64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.4.25/aarch64": {
"file": "profiles/8.4.25-bookworm-aarch64.json",
"build": "8.4.25 bookworm aarch64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.4.25/amd64": {
"file": "profiles/8.4.25-bookworm-amd64.json",
"build": "8.4.25 bookworm amd64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.4.26/amd64": {
"file": "profiles/8.4.26-bookworm-amd64.json",
"build": "8.4.26 bookworm amd64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.4.5/amd64": {
"file": "profiles/8.4.5-bookworm-amd64.json",
"build": "8.4.5 bookworm amd64",
"rce": false,
"verified": false,
"note": "stage-1 leak only; stage-2 sweep found no libphp pointer"
},
"8.5.0/amd64": {
"file": "profiles/8.5.0-bookworm-amd64.json",
"build": "8.5.0 bookworm amd64",
"rce": false,
"verified": false,
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
},
"8.5.0/lab": {
"file": "profiles/8.5.0-bookworm.json",
"build": "8.5.0 bookworm",
"rce": false,
"verified": false,
"note": "lab build (Dockerfile), not the official image"
},
"8.5.10/aarch64": {
"file": "profiles/8.5.10-bookworm-aarch64.json",
"build": "8.5.10 bookworm aarch64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.5.10/amd64": {
"file": "profiles/8.5.10-bookworm-amd64.json",
"build": "8.5.10 bookworm amd64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.5.11/aarch64": {
"file": "profiles/8.5.11-bookworm-aarch64.json",
"build": "8.5.11 bookworm aarch64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.5.11/amd64": {
"file": "profiles/8.5.11-bookworm-amd64.json",
"build": "8.5.11 bookworm amd64",
"rce": true,
"verified": true,
"note": "full chain reproduced (command proof)"
},
"8.5.5/amd64": {
"file": "profiles/8.5.5-bookworm-amd64.json",
"build": "8.5.5 bookworm amd64",
"rce": false,
"verified": false,
"note": "offsets extracted; heap leak not reproduced"
"note": "stage-1 and stage-2 leaks reproduce; stage-3 layout not found"
}
},
"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."
}
"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."
}
10 changes: 2 additions & 8 deletions docs/calibration-logs/matrix_report.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
[
{
"label": "8.4.25-bookworm-aarch64",
"label": "8.4.8-bookworm-amd64",
"status": "leaks-only",
"rce": false,
"profile": "/Users/dpua/Downloads/php-vuln/profiles/8.4.25-bookworm-aarch64.json"
},
{
"label": "8.5.10-bookworm-aarch64",
"status": "rce",
"rce": true,
"profile": "/Users/dpua/Downloads/php-vuln/profiles/8.5.10-bookworm-aarch64.json"
"profile": "profiles/8.4.8-bookworm-amd64.json"
}
]
Loading