PHP2Shell
Two PHP core bugs, CVSS 9.2, unauthenticated RCE before your code runs a single line. This is a patch-tonight problem.
8.2.35, 8.3.36, 8.4.27 or 8.5.12 now. If you cannot patch tonight, turn off session.upload_progress and restart every worker before you do anything else.What just landed
PHP shipped emergency patches on 21 October 2026 for two core bugs that got dubbed PHP2Shell. Both carry a CVSS v4.0 score of 9.2. Wiz is credited with the find, with Calif and OpenAI. This is not a plugin bug or a framework bug. It is in the interpreter itself, which is why every PHP shop on the planet is in scope at once.
And it is not theoretical. Datadog reports AWS saw exploitation attempts before public disclosure. watchTowr's honeypots picked up exploitation on the 21st. The technical details leaked ahead of the coordinated announcement, so the usual window of calm before mass scanning never happened. Treat the clock as already running.
The bug, in one breath
PHP saves memory pointers that belong to a single request into variables that live longer than the request. When an out-of-memory fatal error fires, the engine jumps straight past the code that should have cleared those pointers. The next request comes in, trusts a pointer into memory that was already freed, and you have a classic use-after-free. From there it is RCE or a memory leak, attacker's choice.
CVE-2026-109618 is that pattern in the session and request-decode path. CVE-2026-109619 is the same defect in other persistent state: the saved fatal-error backtraces added in PHP 8.5, and OPcache shared memory that gets corrupted across workers. Same root cause, two faces. JFrog's analysis calls them two angles on one defect.
The chain that makes it ugly
Here is why this one is worse than a normal memory bug, following JFrog's reconstruction of the flow.
- You send a multipart upload. With
session.upload_progress.enabledOn, which is the factory default, PHP writes a session file and lets you influence the session ID, a key suffix, and the stored filename. - On the next session open, PHP unserialises that session file before any of your script runs, and it never checks where the file came from.
- Decoding it builds internal objects and a bookkeeping struct. Trigger a fatal error mid-decode and the cleanup is skipped, so engine globals are left pointing at freed memory.
- A second upload handled by the same worker reuses that freed memory. Its decode now runs attacker-controlled bytes, and the session write-back calls a fake object's serialize handler. That is control of the instruction pointer.
Read that second bullet again. The dangerous work happens before your application code executes. You do not need a vulnerable upload form, a vulnerable route, or a vulnerable anything in your app. You need PHP, running, with defaults.
Why this one actually matters
Plenty of PHP memory bugs need a weird build or a specific app feature. This does not.
- Unauthenticated. No login, no token, no foothold.
- Default config.
session.upload_progress.enabledships On. Nobody turns it off because nobody knows it is there. - Application-independent on mod_php. Apache mod_php is confirmed for app-independent RCE. FrankenPHP is likely in the same boat. PHP-FPM is reachable too, through state set after a clean startup.
- Cheap. The only real constraint is landing two requests on the same worker. On a busy mod_php server that is trivial.
Am I exposed
Check the version that is actually serving traffic, not whatever your laptop prints.
# check the version actually serving requests, not just the CLI
php -v
php -i | grep -i "Server API" # which SAPI is live (mod_php / FPM / CGI)
php-fpm8.3 -v 2>/dev/null # match your branchAffected branches, per JFrog's numbers:
- PHP 8.2.x before
8.2.35 - PHP 8.3.x before
8.3.36 - PHP 8.4.x before
8.4.27 - PHP 8.5.x before
8.5.12
The reporting says the defect reaches back through older branches as well. There is no full fix for PHP 5, and only limited updates for 7.4, 8.0 and 8.1. If you are still running end-of-life PHP in production, this is the day that bill comes due.
Patch, or barricade
The fix is the version bump. Upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12 and restart. Everything below is a stopgap for the hours before you can, and every one of them is partial.
; STOPGAP ONLY. not a substitute for patching. each line is partial.
session.upload_progress.enabled = Off ; closes the main upload path
fatal_error_backtraces = Off ; 8.5.x only
memory_limit = -1 ; removes the OOM trigger, and the DoS guardOn PHP-FPM, you can also break the same-worker requirement:
; php-fpm pool: breaks the "two requests, same worker" requirement
pm.max_requests = 1And at the edge, if a given app never takes uploads, just refuse multipart there:
# nginx stopgap: refuse multipart where you never accept uploads
if ($content_type ~* "multipart/form-data") { return 403; }session.upload_progress closes the main upload path, but it does not touch the OPcache or backtrace paths behind CVE-2026-109619. memory_limit = -1 removes the OOM trigger and your denial-of-service protection in the same move. Restart every worker after any change, or the old state lingers. None of these is a patch. They buy you hours, not safety.Hunting it
Be honest about this part: clean network and log signatures are thin right now. Nobody has published solid IoCs yet. So you hunt the version and the side effects.
- Inventory. Point your SCA at everything and flag any PHP below the fixed versions. This is the highest-signal thing you can do tonight.
- Crashes. A use-after-free usually kills a worker before it lands cleanly, so a spike of segfaults in your PHP-FPM or Apache error logs is a tell.
# a UAF usually crashes a worker before it lands cleanly. watch for it.
grep -iE "segfault|signal 11|exited on signal" \
/var/log/php*-fpm.log /var/log/apache2/error.log- Odd multipart. Watch for multipart POSTs hammering endpoints that have no business receiving uploads, and for new session files with strange prefixes.
- Do not run strangers' PoCs. Fake PoCs are already on GitHub. JFrog flagged one that is a prank which changes your desktop wallpaper and ships no real exploit. The next one will not be a prank. Read code before you run it.
Verdict
PHP2Shell does not care what your application does. The bug fires in the engine, before your first line, on a factory-default setting, with no login required. That is about as bad as the shape of a vulnerability gets. Patch now. If you genuinely cannot patch now, kill session.upload_progress, restart every worker, and patch in the morning. There is no clever third option here.
Sources and further reading
- JFrog Security Research, all you need to know about CVE-2026-109618 and CVE-2026-109619: research.jfrog.com
- Datadog Security Labs, detection, analysis and remediation: securitylabs.datadoghq.com
- Sansec, PHP2Shell PHP RCE research: sansec.io
- watchTowr, PHP2Shell FAQ: watchtowr.com
- SecurityWeek, mass exploitation looms for leaked PHP flaws: securityweek.com
- Help Net Security, critical PHP vulnerabilities patched: helpnetsecurity.com
- BleepingComputer, PHP ships emergency patches: bleepingcomputer.com
- The Hacker News, two unpatched critical PHP RCE flaws: thehackernews.com
No comments:
Post a Comment