# PHP2Shell: two critical PHP RCEs, and how to tell if you’re affected

**URL:** https://mazehq.com/blog/php2shell
**Date:** 2026-10-22

If you run PHP behind Apache with mod_php, an unauthenticated attacker may be able to run code on your server before any of your application code runs. Two use-after-free bugs in PHP, nicknamed “PHP2Shell” and tracked as [CVE-2026-109618](https://nvd.nist.gov/vuln/detail/CVE-2026-109618) and [CVE-2026-109619](https://nvd.nist.gov/vuln/detail/CVE-2026-109619), were patched on October 21, 2026. Both are rated CVSS 9.2 on v4.0 and, depending on the server, can lead to remote code execution or disclosure of sensitive process memory.

PHP released the patch a day earlier than planned after details leaked, and at least one attacker appears to have had them sooner. [Amazon’s honeypots recorded an exploitation attempt](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/) on October 17, four days before disclosure, and [watchTowr reports active exploitation attempts](https://watchtowr.com/intelligence/php-remote-code-execution-php2shell/) against both CVEs. However, [CISA’s alert](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching) said it hadn’t observed exploitation as of publication.

As always, best practice is to patch, and in this case, it’s the only true fix. How urgent it is for you depends mostly on how PHP is wired into your web server. The demonstrated, application-independent attack targets Apache with mod_php, where it runs before your code. PHP-FPM requires more conditions to align, but researchers have also confirmed code execution there.

## **Are you affected?**

Any version below 8.5.12, 8.4.27, 8.3.36, or 8.2.35 is affected, and the source backports PHP shipped for 7.4, 8.0, and 8.1 put those branches in scope too. The advisories don’t name an earliest affected version. Beyond the version, your risk depends on how you serve PHP.

You’re in the highest-risk group if both of these are true:

- You serve PHP through Apache mod_php (FrankenPHP is likely in the same position for CVE-2026-109618).
- You haven’t applied the October 21 patch.

That combination is pre-auth and doesn’t depend on your application, because the vulnerable state is processed before any of your code runs.

If all of these are true, you’re still affected, but it’ll be more challenging for an attacker. Regardless, our recommendation is to patch still:

- You run PHP-FPM (behind nginx or Apache) or LSAPI.
- After startup, your application decodes serialized data on a request an attacker can influence, such as session_start(), session_decode(), unserialize(), or a wrapper like WordPress’s maybe_unserialize().
- Session upload progress is enabled by default, and the consumer picks up the stale state.

One risk is worth separating from the RCE headline. The advisories describe memory disclosure alongside code execution, and both CVEs are rated High for confidentiality impact. When a request is interrupted mid-flight, a later request on the same worker can be handed the freed memory, which may include secrets from other requests such as passwords, session tokens, or keys. Per the advisory, this happens without code execution and without an application call to session_start(). It’s quieter than a full compromise and easy to miss if you only watch for new processes or files, so we suggest patching both bugs rather than figuring out which one applies to you.

## **Check if you’re still vulnerable**

The version string alone is a weak signal because distributions backport the fix without changing it, so a version that looks vulnerable may already be patched. To be sure from the version, check the package changelog for CVE-2026-109618 and CVE-2026-109619.

You can also confirm the patch state on a PHP endpoint that shows errors, usually a dev or staging instance with display_errors on, without crashing anything. It sends a POST with an invalid session cookie and a PHP_SESSION_UPLOAD_PROGRESS field alongside a file part, which reaches PHP’s session startup through the upload progress path. Every build that gets there warns “Session ID is too long or contains illegal characters” and “Failed to read session data”, but only an unpatched build also warns “Failed to write session data” because the fix returns early once session startup fails.

On a server you control, the probe is a single curl:

```
curl -sk --cookie 'PHPSESSID=.' -H 'Accept-Encoding: identity' 
  -F "PHP_SESSION_UPLOAD_PROGRESS=$(date +%s)" 
  -F 'upload=@-;filename=probe.bin;type=application/octet-stream' 
  https://TARGET/ <<<x 
  | grep -E 'too long or contains illegal|Failed to read session data|Failed to write session data'
```

Patched and unpatched builds both print the first two lines, which only confirm the probe reached session startup, so the third line is the one that tells you something:

- **“Failed to write session data” is present.** The build is unpatched.
- **“Failed to write session data” is missing, but the first two lines are there.** The build is patched or backported.
- **Nothing prints at all.** The check couldn’t tell, so a silent result isn’t a clean bill of health.

The probe has limits: it only tests the session upload progress path and assumes the default files handler, so it may not be conclusive for Redis, Memcached, or a custom session.save_handler. It needs display_errors on, which makes it a dev or staging check and never a production fleet scanner, where silence just means errors are suppressed.

We’ve confirmed the behavior across all four supported branches (8.2.35, 8.3.36, 8.4.27 and 8.5.12 vs the prior release). When in doubt, checking the version and changelog is the most reliable way to evaluate both CVEs.

## **How to fix it**

PHP’s advisory is clear that updating is the only complete fix. The patch removes the out-of-memory longjmp() that caused the corruption and resets the worker-global state that could survive a request boundary, and no configuration change does either of those things.

- Update to PHP 8.5.12, 8.4.27, 8.3.36, or 8.2.35. Linux distributions and LTS vendors received the patches in advance, so check yours for updated packages.
- On PHP 7.4, 8.0, or 8.1, the PHP project no longer ships updates, but it published minimal source backports for people building from source. Otherwise, rely on your distribution’s or LTS vendor’s packages.

If you can’t patch right away, the [PHP advisory](https://www.php.net/archive/2026.php#2026-10-21-5) lists interim mitigations, each followed by a restart of every worker:

- memory_limit=-1, set where the application can’t override it, which removes the limit the attacker drives the request into (CVE-2026-109618)
- a conservative post_max_size, with request size also enforced at the web server or proxy (CVE-2026-109618)
- session.upload_progress.enabled=0 (CVE-2026-109619)
- fatal_error_backtraces=0 on PHP 8.5 (CVE-2026-109619)

None of these are a complete fix. The advisory says they reduce exposure to specific attack paths without making an installation safe, and it covers where each one falls short.

On managed or shared hosting, you patch on your provider’s schedule, which can lag the release by hours or days, and third-party package repositories can lag too.

If you don’t control the runtime, ask your host three things:

- The PHP patch level on both your live and staging environments
- Whether they’ve confirmed it against the CVE-2026-109618 and CVE-2026-109619 changelog
- If you’re served through mod_php or PHP-FPM, since mod_php is the pre-auth, application-independent case.

## **Check whether you were impacted**

Crashes and out-of-memory errors show up in different places, so check both.

A worker that segfaults never writes a PHP error line, so crashes show up at the server and OS layer, in the Apache error log, the PHP-FPM log, and dmesg, journald, or core dumps. PHP’s own log catches the out-of-memory fatals and other diagnostics, but only with log_errors=On, a working error_log, and error_reporting=E_ALL set in startup config, since per-request settings won’t help here. Then look for these signs:

- **Worker crashes.** In Apache, child pid N exit signal Segmentation fault (11). In PHP-FPM, child N exited on signal 11 (SIGSEGV) or SIGABRT, or workers respawning in clusters. A burst of these, especially from one source, is worth a look.
- **Out-of-memory fatals before your code runs.** Allowed memory size of N bytes exhausted, particularly on upload or multipart requests, or repeated from one IP.
- **Odd multipart requests at the proxy.** POSTs with no usable Content-Length or with chunked transfer encoding, an unusual number of parts or headers, deeply nested field names (files[a][b][c]…), or empty-filename upload parts.

Treat crashes as a lead, but not a clear verdict on its own. A failed or clumsy attempt is more likely to crash a worker than a careful one, and a successful exploit doesn’t need to crash anything, so a quiet log proves nothing on its own.

A working attack doesn’t depend on display_errors, and none of these signals is a clean exploitation fingerprint. If you run mod_php internet-facing on an unpatched version, treat compromise as possible and check the data an attacker could reach, not just the logs.

However, there’s one exception: because the disclosure path runs through session upload progress, the same “Failed to read session data” and “Failed to write session data” warnings the probe looks for can land in your error log if logging was on, so it’s worth grepping old logs for them. And because that path needs no shell and drops no file, none of the crash and process signals above will surface it, so if an exposed worker held secrets in memory, rotate them rather than wait for a log line.

## **How it works**

Both bugs are [use-after-frees](https://cwe.mitre.org/data/definitions/416.html) with the same shape. PHP is interrupted mid-request by an out-of-memory condition in CVE-2026-109618 or a fatal error in CVE-2026-109619, leaving behind a state that later code treats as valid.

On mod_php, CVE-2026-109618 is reached before any application code runs, and CVE-2026-109619 reaches its stale state through session upload progress, which is why disabling that feature is a listed mitigation. The advisories have the full mechanism and code paths.

## **References**

- [PHP Foundation: today’s PHP security release](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/)
- [PHP October 2026 security release advisory](https://www.php.net/archive/2026.php#2026-10-21-5)
- [GHSA-w8hw-w9fx-j79w, RCE via OOM corruption (CVE-2026-109618)](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w)
- [GHSA-6wmf-4rv5-p3rp, RCE via stale global state (CVE-2026-109619)](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)
- [NVD: CVE-2026-109618](https://nvd.nist.gov/vuln/detail/CVE-2026-109618)
- [NVD: CVE-2026-109619](https://nvd.nist.gov/vuln/detail/CVE-2026-109619)
- [Amazon Threat Intelligence: PHP2Shell exploitation before public disclosure](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/)
- [watchTowr: PHP2Shell remote code execution](https://watchtowr.com/intelligence/php-remote-code-execution-php2shell/)
- [CISA: Critical vulnerabilities in PHP require immediate patching](https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching)