Skip to content

Commit b8b7214

Browse files
committed
Align contribution with CONTRIBUTING checklist
- redact the local lab username from the published proof example - document the FPM profile in docs/profile-inventory.md (table synced with php2shell_inventory.py output, including the pre-existing missing 8.3.0/8.5.0 rows) - state the two-slot reclaim/retry behaviour in the profile status and the README table (probabilistic disclosure) - correct the README profile counters (14 profiles, 6 verified RCE)
1 parent 58ef08a commit b8b7214

4 files changed

Lines changed: 26 additions & 8 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ not match the profile.
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)` |
3838
| PHP 8.4.26 amd64 | `php:8.4.26-apache` | `php-8.4.26-bookworm-amd64` | yes | yes | **yes** | reference build |
39-
| PHP 8.4.26 FPM | `php:8.4.26-fpm` (nginx) | `8.4.26-fpm` | n/a | n/a | **yes** | `BG(unserialize)` chain; worker runs `uid=1000` proof |
39+
| PHP 8.4.26 FPM | php-8.4.26 source tarball (nginx+FPM) | `8.4.26-fpm` | n/a | n/a | **yes** | `BG(unserialize)` chain; two-slot reclaim, self-correcting retries |
4040
| PHP 8.4.25 aarch64 | `php:8.4.25-apache` | `php-8.4.25-bookworm-aarch64` | yes | yes | **yes** | probabilistic stage 3; retries |
4141
| PHP 8.5.0 aarch64 | `php:8.5.0-apache` | `php-8.5.0-bookworm` | yes | yes | no | stage-3 layout not found |
4242
| PHP 8.3.35 aarch64 | `php:8.3.35-apache` | `php-8.3.35-bookworm-aarch64` | yes | yes | no | stage-3 reference alive |
@@ -48,10 +48,10 @@ not match the profile.
4848
| PHP 7.4.33 aarch64 | `php:7.4-apache` | `php-7.4.33-bookworm-aarch64` | no | no | no | UAF; leak shape differs |
4949
| PHP 8.2.0 aarch64 | `php:8.2.0-apache` | — (offsets only) | n/a | n/a | no | no heap profile |
5050

51-
**13 heap profiles, 5 with verified RCE. Offsets extracted for 12 builds.**
52-
Plus one FPM profile (`8.4.26-fpm`) with verified RCE through the
53-
`BG(unserialize)` stale-state chain (no heap-leak stages; the stale struct is
54-
located with the oracle each run).
51+
**14 profiles (13 heap + 1 FPM), 6 with verified RCE. Offsets extracted for
52+
12 builds.** The FPM profile has no heap-leak stages: the stale
53+
`BG(unserialize)` struct is located with the oracle each run
54+
([docs/fpm-chain.md](docs/fpm-chain.md)).
5555

5656
`Heap leak` is stage 1, `libphp leak` is stage 2 and `RCE` is a real command
5757
proof on an isolated lab. `no` means not verified, not necessarily impossible;

‎docs/fpm-chain.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ proof separately:
132132

133133
```sh
134134
curl http://127.0.0.1:8110/out/p1
135-
# uid=1000(cryptocalypse) gid=1000(cryptocalypse) groups=...
135+
# uid=1000(...) gid=1000(...) groups=...
136136
```
137137

138138
On the reference run the proof was `uid=1000` (the unprivileged lab user),

‎docs/profile-inventory.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ Generated from `profiles/*.json` by `tools/php2shell_inventory.py`.
88
| 8.0.30 | php-8.0.30-bookworm-aarch64 | 0x31ef70 | 0x40ddb0 | - | - | - | - | no |
99
| 8.1.34 | php-8.1.34-bookworm-aarch64 | 0x341e08 | 0x42e7a0 | - | - | - | - | no |
1010
| 8.2.34 | php-8.2.34-bookworm-aarch64 | 0x36ff48 | 0x4606c0 | - | 0 | 0 | 406 | no |
11+
| 8.3.0 | php-8.3.0-bookworm | 0x3771f0 | 0x467cf0 | - | - | - | 406 | no |
1112
| 8.3.33 | php-8.3.33-wordpress-mod_php | 0x377248 | 0x4737e0 | - | 815 | 63 | 406 | no |
1213
| 8.3.35 | php-8.3.35-bookworm-aarch64 | 0x377428 | 0x46fcc0 | - | 841 | 63 | 406 | no |
1314
| 8.4.25 | php-8.4.25-bookworm-aarch64 | 0x42fa30 | 0x637ae0 | 0x690d8 | 841 | 63 | 406 | yes |
1415
| 8.4.26 | php-8.4.26-bookworm-amd64 | 0x465370 | 0x65eb60 | 0x690d8 | 841 | 79 | 406 | yes |
16+
| 8.4.26 | php-8.4.26-fpm | 0x386660 | 0x54a0d0 | - | - | - | - | yes |
17+
| 8.5.0 | php-8.5.0-bookworm | 0x503510 | 0x731d80 | - | 841 | 63 | 406 | no |
1518
| 8.5.10 | php-8.5.10-bookworm-aarch64 | 0x52e130 | 0x75d360 | 0x690d8 | 841 | 63 | 406 | yes |
16-
| 8.5.11 | php-8.5.11-bookworm-amd64 | 0x5996e0 | 0x7b47a0 | 0x690d8 | 841 | 63 | 406 | yes |
1719
| 8.5.11 | php-8.5.11-bookworm-aarch64 | 0x5314f0 | 0x761b80 | 0x690d8 | 841 | 63 | 406 | yes |
20+
| 8.5.11 | php-8.5.11-bookworm-amd64 | 0x5996e0 | 0x7b47a0 | 0x690d8 | 841 | 63 | 406 | yes |
21+
22+
**14 profiles, 6 with verified RCE** (5 Apache/mod_php + 1 PHP-FPM).
23+
| 8.4.26 FPM | php-8.4.26-fpm | 0x386660 | 0x54a0d0 | - | - | - | - | yes |
1824

1925
## Per-build notes
2026

@@ -38,6 +44,18 @@ This is the 8.5 branch, fixed in 8.5.12.
3844
`php:8.4.26-apache`, bookworm. Verified 3/3 manually and through the factory.
3945
Fixed in 8.4.27. This is the profile used against the local 8.4.26 laboratory.
4046

47+
### PHP 8.4.26 FPM — RCE verified
48+
49+
nginx + PHP-FPM, amd64, user-prefix build of the php-8.4.26 source tarball
50+
(no Docker image; built by `lab/fpm/build_php.sh`). Exploits the stale
51+
`BG(unserialize)` state instead of the Apache progress-cache chain — the
52+
stage columns do not apply. `basic_globals=0xa04660`, `struct_size=8216`;
53+
the 8216-byte struct recycles between two heap slots and a mismatch
54+
self-corrects within four retries. Verified `uid=...` proof in the local
55+
laboratory; `php2shell_fpm.py patchcheck` confirms 8.4.27 is clean
56+
(`level=0`, workers survive). Fixed in 8.4.27. See
57+
[docs/fpm-chain.md](fpm-chain.md).
58+
4159
### PHP 8.3.35 — leaks only
4260

4361
`php:8.3.35-apache`. Stage 1 (heap base) and stage 2 (libphp base) reproduce;

‎profiles/8.4.26-fpm.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@
2929
"session_upload_progress": "On",
3030
"workers": 2
3131
},
32-
"status": "RCE verified in the nginx+FPM lab: post-RINIT OOM leaves BG(unserialize) stale (level=1), the pre-RINIT rfc1867 session_start of a follow-up multipart reuses the freed struct, a controlled buffer forges var_hash so R:2 yields an IS_REFERENCE -> fake HashTable whose pDestructor is zif_system; the worker aborts after the command and FPM respawns it.",
32+
"status": "RCE verified in the nginx+FPM lab: post-RINIT OOM leaves BG(unserialize) stale (level=1), the pre-RINIT rfc1867 session_start of a follow-up multipart reuses the freed struct, a controlled buffer forges var_hash so R:2 yields an IS_REFERENCE -> fake HashTable whose pDestructor is zif_system; the worker aborts after the command and FPM respawns it. The 8216-byte struct recycles between two heap slots; a mismatch self-corrects within four retries (see docs/fpm-chain.md).",
3333
"oom_mb": 4
3434
}

0 commit comments

Comments
 (0)