Full RCE exploit chain for two critical PHP vulnerabilities in the multipart parser and session upload progress handler, plus three bypass exploits that defeat the official security patch (PHP 8.4.27) and all recommended mitigations. Core exploits: - CVE-2026-109618: OOM in multipart parser → session persistence → LFI → RCE - CVE-2026-109619: Session poisoning via upload progress (direct + race) → RCE - WordPress-targeted wrapper with LFI auto-detection Patch bypass exploits (confirmed RCE on PHP 8.4.27): - Race condition: TOCTOU inherent to upload progress feature design - RLIMIT_AS: mmap() failure bypasses the patched OOM handler - /proc/PID/fd: temp file inclusion bypasses ALL mitigations
102 lines
4.2 KiB
Markdown
102 lines
4.2 KiB
Markdown
# PHP 8.4.27 Patch Bypass Findings
|
|
|
|
## Summary
|
|
|
|
The PHP security patch released 2026-10-21 (PHP 8.4.27, 8.3.36, 8.2.35, 8.5.12)
|
|
for CVE-2026-109618 and CVE-2026-109619 has **three confirmed bypass paths** that
|
|
achieve Remote Code Execution on fully patched systems.
|
|
|
|
The advisory's recommended mitigation `session.upload_progress.enabled=0` blocks
|
|
two of the three bypasses but is itself defeated by the third.
|
|
|
|
## Bypass #1: Race Condition (TOCTOU)
|
|
|
|
**File:** `race_bypass_patched.py`
|
|
**Confirmed RCE:** Yes, first attempt
|
|
**Patches bypassed:** PHP 8.4.27 OOM fix
|
|
**Mitigations bypassed:** session.upload_progress.cleanup=1
|
|
|
|
The session upload progress feature writes attacker-controlled data to the session
|
|
file on disk *during* the multipart upload. Cleanup only runs at `MULTIPART_EVENT_END`.
|
|
A concurrent LFI request can race to include the session file while the upload is
|
|
still in progress.
|
|
|
|
This is a TOCTOU (Time-of-Check-Time-of-Use) issue inherent to the feature's
|
|
design — the data must exist on disk for the feature to work, creating a window
|
|
that cannot be eliminated without removing the feature.
|
|
|
|
**Blocked by:** `session.upload_progress.enabled=0`
|
|
|
|
## Bypass #2: RLIMIT_AS / mmap() Failure
|
|
|
|
**File:** `rlimit_bypass_patched.py`
|
|
**Confirmed RCE:** Yes
|
|
**Patches bypassed:** PHP 8.4.27 OOM fix (different code path)
|
|
**Mitigations bypassed:** Standard memory_limit-based OOM handling
|
|
|
|
The patch intercepts OOM from PHP's `memory_limit` (via zend_mm allocator).
|
|
When `memory_limit=-1` and the OS enforces address space limits via `RLIMIT_AS`
|
|
(or cgroup memory.max), `mmap()` fails through a **different code path** that
|
|
the patch does not cover.
|
|
|
|
- Worker exits with code **1** (not the patched exit code **70**)
|
|
- Session file **persists** on disk with attacker-controlled PHP code
|
|
- New worker spawns → LFI includes poisoned session → RCE
|
|
|
|
Relevant in production:
|
|
- Docker/k8s containers with cgroup memory limits
|
|
- Shared hosting with per-tenant RLIMIT_AS
|
|
- Admins using `memory_limit=-1` trusting OS-level limits
|
|
|
|
**Blocked by:** `session.upload_progress.enabled=0`
|
|
|
|
## Bypass #3: /proc/PID/fd Temp File Inclusion
|
|
|
|
**File:** `procfd_bypass_hardened.py`
|
|
**Confirmed RCE:** Yes, first attempt
|
|
**Patches bypassed:** ALL patches
|
|
**Mitigations bypassed:** ALL mitigations including `upload_progress=0`
|
|
|
|
During a multipart file upload, PHP creates a temporary file with attacker-
|
|
controlled contents. This file is visible via `/proc/<pid>/fd/<N>` to any
|
|
process that can read procfs. A concurrent LFI request from a different FPM
|
|
worker can include this file directly.
|
|
|
|
This attack does NOT use session poisoning at all — it bypasses every
|
|
session-related mitigation.
|
|
|
|
**Blocked by:**
|
|
- Fixing the LFI vulnerability
|
|
- `hidepid=2` on /proc mount (hides other processes' FDs)
|
|
- `open_basedir` restricting /proc access
|
|
- Disabling file uploads entirely (impractical)
|
|
|
|
## Mitigation Effectiveness Matrix
|
|
|
|
| Mitigation | Race | RLIMIT | /proc/fd |
|
|
|-------------------------------|------|--------|----------|
|
|
| PHP 8.4.27 patch alone | FAIL | FAIL | FAIL |
|
|
| + upload_progress=0 | OK | OK | FAIL |
|
|
| + hidepid=2 on /proc | FAIL | FAIL | OK |
|
|
| + upload_progress=0 + hidepid | OK | OK | OK |
|
|
| Fix the LFI | OK | OK | OK |
|
|
|
|
## Tested Vectors That Did NOT Bypass the Patch
|
|
|
|
- **Multipart part header flood:** OOM triggered but the patch handles it
|
|
correctly (exit code 70, session file cleaned up)
|
|
- **WAF bypass via PUT/PATCH:** Passes POST-only WAF rules but PHP's
|
|
`session.upload_progress` only fires on POST requests
|
|
- **Content-Type comma tricks:** Pass WAF matching but PHP doesn't parse
|
|
as multipart/form-data
|
|
- **Access log poisoning:** Works conceptually but fragile (previous log
|
|
entries may cause PHP parse errors)
|
|
|
|
## Recommendations
|
|
|
|
1. **Immediate:** Set `session.upload_progress.enabled=0` in php.ini
|
|
2. **Immediate:** Mount /proc with `hidepid=2` (or `hidepid=invisible` on newer kernels)
|
|
3. **Critical:** Fix any Local File Inclusion vulnerabilities in applications
|
|
4. **Defense in depth:** Set `open_basedir` to restrict filesystem access
|
|
5. **Do NOT rely on the patch alone** — it leaves two bypass paths open
|