Advisory: php2shell PHP Remote Code Execution (CVE-2026-109618, CVE-2026-109619)

Two PHP memory bugs reachable before any application code runs allow remote code execution on mod_php and PHP-FPM. Upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, and apply these mitigations until you can.

October 21, 2026

Status as of October 21, 2026, 9:41 AM EDT. We will update it as fixed packages land and exploitation reports are confirmed or ruled out.

Two vulnerabilities in PHP’s request handling, collectively nicknamed “php2shell”, allow remote code execution. Both are reached while PHP parses the incoming request, before your framework, middleware, login checks or security plugins run. Any internet-facing PHP application should be treated as exposed until it is upgraded.

Summary

  • Upgrade PHP to 8.2.35, 8.3.36, 8.4.27 or 8.5.12. PHP says this is the only complete fix.
  • PHP 8.1 and older will not be fixed. Keep the mitigations below in place, or take those sites offline, until you’ve migrated.
  • Until you can upgrade: set memory_limit = -1 and session.upload_progress.enabled = 0 server-wide, block multipart requests at the web server for apps that don’t accept uploads, and restart every PHP worker.

1. The fix: upgrade PHP

Fixed versions are 8.2.35, 8.3.36, 8.4.27 and 8.5.12. PHP’s release builds are scheduled for 10:00 AM EDT (14:00 UTC) on October 21.

After upgrading, restart everything that runs PHP: FPM masters, Apache with mod_php, and Laravel Octane and queue workers. A worker that was started before the upgrade is still running the vulnerable code.

How you get the fix depends on where your PHP comes from:

  • Ondřej Surý’s Ubuntu PPA: run apt-get update, upgrade the phpX.Y-* packages, then confirm the new version with apt-cache policy php8.4-fpm. Some mirrors lag behind; use ppa.launchpadcontent.net directly if yours doesn’t have the update yet.
  • Ubuntu and Debian distribution packages: these may backport the fix without changing the PHP version number. Check the package version, not php -v.
  • Remi’s repository (common on RHEL, CentOS, AlmaLinux and Rocky): will not have the fix today. Apply the mitigations below.
  • FrankenPHP and other static builds: need a new release of the binary. apt can’t patch PHP that’s compiled into them.
  • Chainguard images: Chainguard says its free images for 8.2 to 8.5 are already patched (image directory). We haven’t verified this; check the image build date before relying on it.

2. The vulnerabilities

CVE-2026-109618: out-of-memory during multipart parsing

Official advisory (GHSA-w8hw-w9fx-j79w)

An attacker can force an out-of-memory error while PHP parses a multipart upload. Ways to trigger it include a request with no usable Content-Length, parts with unlimited headers, or empty file fields with nested names. The end-of-request cleanup then reads the broken memory left behind, which can lead to remote code execution.

  • Apache mod_php: confirmed exploitable with default settings, before any application code runs.
  • PHP-FPM: vulnerable according to the source code. No working FPM exploit has been shown publicly.
  • FrankenPHP: likely affected. LSAPI: may be affected.

CVE-2026-109619: use-after-free across requests

Official advisory (GHSA-6wmf-4rv5-p3rp)

State left over from one request is used after it has been freed, on the next request handled by the same worker. Remote code execution is confirmed:

  • on mod_php and FPM through session upload progress, which is on by default and does not require your code to call session_start()
  • on mod_php through PHP 8.5’s fatal_error_backtraces

3. Interim mitigations until you’ve patched

Set each of these in the server-wide php.ini or the FPM pool config, not per directory, then restart every worker.

memory_limit = -1                     ; the main stopgap for 109618, but not complete
session.upload_progress.enabled = 0   ; keep this off even after patching
fatal_error_backtraces = 0            ; PHP 8.5 only

In an FPM pool config:

php_admin_flag[session.upload_progress.enabled] = off

Then:

  • Block multipart requests at the web server for any app that doesn’t take uploads (rules in section 4).
  • Shrink what PHP will parse. Keep post_max_size small and enforce the decoded body size at the web server as well. Lower max_input_nesting_level and max_input_vars, and cap the number of multipart parts, headers per part and uploads.
  • Do not set RLIMIT_AS or RLIMIT_DATA. They make mmap() fail, which triggers the vulnerable code path. A cgroup memory.max limit helps only partly.
  • Remove per-directory PHP settings that throw warnings before the request starts: .htaccess and Apache php_value or php_admin_* lines, and nginx PHP_VALUE or PHP_ADMIN_VALUE parameters. Hiding the warnings with display_errors, error_reporting or @ does not protect you.
  • On FPM, if you can take the performance hit, set pm.max_requests = 1 so no worker serves a second request.

Verifying the upload progress setting:

  • ini_set() can’t change session.upload_progress.enabled, so setting it in application code does nothing.
  • Check the effective value from the web side with ini_get(), or on the server with php-fpm8.4 -i | grep upload_progress.
  • .user.ini only works under FPM and can take up to 5 minutes to apply.

4. Web server rules: block multipart requests

These rules come from Mattias Geniar, who tested them on nginx 1.29, Apache 2.4, Caddy 2.10, lighttpd 1.4 and FrankenPHP 1.13 (write-up). They match “multipart” in any letter case.

File uploads will break unless you allowlist those paths, and allowlisted paths remain exposed.

nginx

In the http {} block:

map $http_content_type $block_multipart { default 0; "~*multipart" 1; }

Then in every server {} block that passes requests to PHP, including the catch-all server:

if ($block_multipart) { return 403; }   # use 444 if error_page 403 routes back to PHP

With an upload allowlist:

map "$http_content_type|$uri" $block_multipart {
    default 0;
    "~*multipart.*\|/(avatar/upload|livewire/upload-file)$" 0;   # your upload paths
    "~*multipart" 1;
}

Apache 2.4

In each vhost or in .htaccess:

<If "req('Content-Type') =~ /multipart/i">
    Require all denied
</If>

Caddy

@multipart header_regexp Content-Type (?i)multipart
respond @multipart 403

lighttpd

server.modules += ( "mod_access" )
$REQUEST_HEADER["Content-Type"] =~ "(?i)multipart" { url.access-deny = ( "" ) }

FrankenPHP

In the Caddyfile’s global block:

{ frankenphp { php_ini session.upload_progress.enabled 0 } }

Read-only fallback

If you can’t take a site offline, you can reject everything except GET and HEAD. Logins, forms and checkout will stop working.

if ($request_method !~ ^(GET|HEAD)$) { return 503; }

Test the block

curl -s -o /dev/null -w '%{http_code}\n' -F a=b https://example.com/   # expect 403

5. Managed WAF rules (an extra layer only)

Managed rules can stop the current public exploit. They don’t fix the memory bugs, so use them alongside patching, not instead of it.

  • Imunify360: ModSecurity ruleset 8.83 blocks the public exploit. Check your version with cat /etc/apache2/conf.d/modsec_vendor_configs/imunify360-full-apache/VERSION and update with imunify360-agent update --force modsec-rules.
  • Bunny Shield: blocks the public exploit pattern automatically.
  • OWASP Core Rule Set: an unverified report says it catches more than 95% of attack attempts.

Any WAF can be bypassed if your origin server is reachable directly. Lock the origin down so it only accepts traffic from your WAF’s IP addresses.

6. Don’t rely on these

  • Blocking the PHPSESSID cookie breaks every login and cart, and doesn’t stop the attack.
  • file_uploads = Off: PHP still parses the multipart body.
  • enable_post_data_reading = Off breaks forms and doesn’t cover request_parse_body() in PHP 8.4.
  • Application-level defenses (middleware, CSRF or login checks, Laravel maintenance mode, WordPress security plugins) all run after PHP has already parsed the request.
  • disable_functions (for example, disabling system) only stops the current public exploit, not the underlying memory bug.
  • The Qi Deng write-up and the ModSecurity rule based on it: the author retracted the use-after-free path. The official advisories supersede it.
  • The LLM-written patch posted to pastebin from Hacker News is untrusted code. Don’t apply it.

7. Taking a site offline safely

Stop the web server, or return 503 for every request.

Do not just disable the PHP handler. Apache and nginx will then serve your .php files as plain text, including any passwords and API keys in them. Stopping only PHP-FPM is safe, because nginx returns 502 instead.

CERT-EU advises taking internet-facing PHP applications off the public network if you can’t patch. Short of that, put them behind a VPN, an IP allowlist or a login at the edge.

8. Reduce the damage and watch for attacks

Hardening:

  • Run PHP as a user that can’t write to disk, keep sudoers clean, and use AppArmor or SELinux to stop PHP from launching other programs.
  • Remove stored credentials and cloud IAM roles from servers that run PHP.

Detection:

  • Alert when php-fpm, php-cgi, httpd or apache2 starts a shell, a script interpreter, curl or wget.
  • Watch for PHP workers crashing with SIGSEGV or SIGABRT on multipart POSTs.
  • Watch for PHP_SESSION_UPLOAD_PROGRESS in requests to routes that don’t accept uploads, and for unexpected session files.

If you find a hit, isolate the server, preserve the logs, and check for web shells, new cron jobs and new user accounts. Patching doesn’t remove anything an attacker already left behind.

Exploitation status

Some security vendors report attacks in the wild. Germany’s BSI says none are confirmed. As of this writing, nobody has published evidence either way.

Questions about your setup? Contact us or open a ticket from your dashboard.