PHP2Shell (CVE-2026-109618 and CVE-2026-109619): how to check if you are affected and how to patch

Two critical remote code execution flaws in PHP, fixed on October 21, 2026 in versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35. What happens, who is exposed, how to check your version, what temporary mitigations exist and what to do after patching.

In this article
In 30 seconds

On October 21, 2026 PHP fixed two critical flaws (CVSS 9.2) that allow unauthenticated code execution; the only complete mitigation is updating to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, and anyone on 8.1 or earlier must ask their vendor for a patch or migrate.

1. What happened#

On October 20, 2026 the PHP project warned of an urgent security release for the next day. On October 21 it published versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35, which fix two critical vulnerabilities allowing unauthenticated remote code execution. The press calls them PHP2Shell.

Date (2026)Event
October 20PHP announces a security release for October 21 at 14:00 UTC with two critical flaws (CVSS 9.2), and reserves the right to release earlier if details become public
Before the patchesCloudflare's CTO said it was already seeing early attacks against both flaws; its WAF rules were temporary
October 21PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 ship, with the advisories published in PHP's repository
October 21 to 22Qualys reports that within 12 hours only 199,000 of 155 million installations with a known version were patched

There are signs of exploitation: Cloudflare said it already saw early attacks against both flaws before the patches, and PHP describes a demonstrated exploit on Apache with mod_php. The UK's National Cyber Security Centre urged organizations to update immediately or take affected systems offline.

The sources differ on one detail: the advisories and PHP's news page date the releases October 21, while php.net's changelog shows the 22nd for 8.5.12 and 8.4.27. We use the 21st, the date of the advisories.

Documentation: PHP: 2026 news archive ↗

2. The two vulnerabilities#

Both have a CVSS v4.0 score of 9.2 (critical). According to BleepingComputer, they were discovered by Calif, OpenAI and Wiz. They share the same underlying problem: PHP leaves corrupt memory when a request ends abnormally.

ItemCVE-2026-109618CVE-2026-109619
AdvisoryGHSA-w8hw-w9fx-j79wGHSA-6wmf-4rv5-p3rp
Advisory titleRCE via OOM corruptionRCE via stale global state
CVSS v4.09.2 critical (AV:N/AC:H/AT:P/PR:N/UI:N)9.2 critical (AV:N/AC:H/AT:P/PR:N/UI:N)
MechanismPHP leaves an out-of-memory error with longjmp() and leaves request data half-initialized that shutdown treats as validGlobal state from one request survives its freed memory and a later request on the same worker uses it before it is reset
Confirmed impactUnauthenticated remote code execution with the web worker's privilegesUse-after-free, memory disclosure and unauthenticated remote code execution
Where it was confirmedApache mod_php with default limitsmod_php (PHP 8.5 fatal backtraces) and mod_php and PHP-FPM (session upload progress)

The first flaw triggers when memory runs out. The advisories describe several ways to push a request into that state while staying within apparent limits: multipart requests with no usable declared length, unlimited headers per part and empty file fields with nested names. The second needs two requests handled by the same worker: one that leaves the state stale and one that uses it.

The advisory clarifies that exploit success depends on the PHP build, the extensions and the memory layout, so it is harder in some environments than in others. That does not make it safe: researchers demonstrated code execution with default limits.

Documentation: Advisory GHSA-w8hw-w9fx-j79w (CVE-2026-109618) ↗ · Advisory GHSA-6wmf-4rv5-p3rp (CVE-2026-109619) ↗

3. Who is affected#

All versions below 8.2.35, 8.3.36, 8.4.27 and 8.5.12 are affected. Qualys and the press include in the risk any installation serving web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed.

PHP branchFixed versionStatus
8.58.5.12Supported
8.48.4.27Supported
8.38.3.36Supported
8.28.2.35Reaches end of life on December 31, 2026
8.1, 8.0 and 7.4No official fixed versionOut of support; PHP offers source patches for 7.4, 8.0 and 8.1

The most urgent case is Apache with mod_php: the advisory describes an exploit that fires before application code runs. In PHP-FPM the first flaw is not fully demonstrated, but PHP validated code execution through the session path. The command line is not mentioned.

Branches 8.1, 8.0 and 7.4 get no updates from the project; PHP offers source patches that your vendor must apply. Qualys estimates that of the 167 million PHP installations it analyzed, 71% use unsupported versions (from 5.4 to 8.1). It is a security vendor's figure: use it as an order of magnitude.

Documentation: Qualys: PHP2Shell, how to find, mitigate and patch ↗ · BleepingComputer: emergency patches for PHP2Shell ↗

4. How to check if you are affected#

Look at the version that serves web requests, not the one you see in your terminal: Qualys warns that php -v may show a different installation from the one serving the site. Also check containers, parallel branches and the PHP bundled with hosting panels.

bash
# The version that serves web requests (not the terminal's): compare it with the running process
php -v
php-fpm -v || php-fpm8.4 -v
apache2ctl -M 2>/dev/null | grep -i php        # is mod_php loaded?
ps aux | grep -E "php-fpm|apache2|httpd|frankenphp" | grep -v grep

# Containers: the image that runs, not the one on your laptop
docker ps --format "{{.Names}} {{.Image}}"
docker exec NAME php -v

# After updating: really restart the processes that serve requests
sudo systemctl restart php8.4-fpm    # or apache2 if you use mod_php
Reference commands; this guide does not run them because they depend on your server.

The script compares a version with the fixed ones. If you use a distribution package, the number may still be old even though it has the patch: check your vendor's advisory.

python
import re

FIXED = {(8, 5): 12, (8, 4): 27, (8, 3): 36, (8, 2): 35}   # fixed patch level per branch

def parse(version):
    return tuple(int(n) for n in re.findall(r"\d+", version)[:3])

def status(version):
    major, minor, patch = parse(version)
    if (major, minor) in FIXED:
        minimum = FIXED[(major, minor)]
        if patch >= minimum:
            return "fixed"
        return f"VULNERABLE: update to {major}.{minor}.{minimum}"
    if (major, minor) > (8, 5):
        return "branch newer than the fixed ones: check the advisories"
    return "NO OFFICIAL FIXED VERSION: ask your vendor for a build with the patch or migrate"

for v in ["8.5.11", "8.5.12", "8.4.26", "8.4.27", "8.3.36", "8.2.34", "8.1.31", "7.4.33"]:
    print(v, "->", status(v))
Prints the status of eight versions: 8.5.11, 8.4.26 and 8.2.34 vulnerable, 8.5.12, 8.4.27 and 8.3.36 fixed, and 8.1.31 and 7.4.33 with no official fixed version.

5. How to patch#

  • Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 according to your branch. If you are on 8.1 or earlier, ask your vendor for a build with the patch or migrate to a supported branch.
  • Restart the processes that serve requests (PHP-FPM, Apache, FrankenPHP): updating the package is not enough until the workers load the new version.
  • In containers, rebuild the image on a base with the fixed version and redeploy.
  • On shared hosting or with a panel (Plesk, cPanel), confirm with the provider that it has applied the patch.
  • If PHP 8.2 is your branch, plan the migration: it reaches end of life on December 31, 2026.

Go deeperOctober 2026 security patches: Next.js 16.4.1 and PgBouncer 1.26.1

6. If you cannot patch today: temporary mitigations#

PHP publishes settings that reduce exposure, but insists they do not make an unpatched system safe: the only complete mitigation is updating. They must be applied administratively (php.ini, the FPM pool or the server configuration), not with ini_set() or .user.ini, and workers must be restarted.

SettingValueProtects against
memory_limit-1, set with php_admin_valueCVE-2026-109618
post_max_size8M without uploads; 32M with uploadsCVE-2026-109618
upload_max_filesizeLower than post_max_sizeCVE-2026-109618
session.upload_progress.enabled0 in php.ini, the FPM pool or the server; not with ini_set() or .user.iniCVE-2026-109619
fatal_error_backtraces0, PHP 8.5 onlyCVE-2026-109619 (the confirmed PHP 8.5 path)
pm.max_requests1, FPM only and with a performance costCVE-2026-109619
python
import re

def to_mb(value):
    m = re.fullmatch(r"(\d+)\s*([KMG]?)", value.strip().upper())
    if not m:
        return None
    return int(m.group(1)) * {"": 1 / 1024**2, "K": 1 / 1024, "M": 1, "G": 1024}[m.group(2)]

def audit(ini, version):
    """Checks the temporary mitigations PHP lists; they do not replace the patch."""
    cfg = dict(l.split("=", 1) for l in (x.split(";")[0].strip() for x in ini.splitlines()) if "=" in l)
    cfg = {k.strip(): v.strip() for k, v in cfg.items()}
    missing = []
    if cfg.get("memory_limit") != "-1":
        missing.append("memory_limit = -1 (set as php_admin_value)")
    limit = to_mb(cfg.get("post_max_size", "8M"))
    if limit is None or limit > 32:
        missing.append("post_max_size <= 32M (8M if there are no uploads)")
    if cfg.get("session.upload_progress.enabled", "1").lower() not in ("0", "off"):
        missing.append("session.upload_progress.enabled = 0")
    if parse(version)[:2] == (8, 5) and cfg.get("fatal_error_backtraces", "1").lower() not in ("0", "off"):
        missing.append("fatal_error_backtraces = 0 (PHP 8.5 only)")
    return missing

def parse(version):
    return tuple(int(n) for n in re.findall(r"\d+", version)[:3])

partial = "memory_limit = -1\npost_max_size = 8M ; no uploads\nsession.upload_progress.enabled = 0\n"
print(audit(partial, "8.4.26"))
print(audit(partial, "8.5.11"))
print(audit("memory_limit = 128M\npost_max_size = 64M\n", "8.3.35"))
Checks the mitigations for the two vulnerabilities in a configuration file. With the example it prints an empty list, the one missing on PHP 8.5, and the three missing on an unhardened configuration.

Two warnings from the advisory: do not use RLIMIT_AS or RLIMIT_DATA process limits, because they can trigger the vulnerable path, and hiding errors with error_reporting, display_errors or the @ operator does not help. Also, memory_limit = -1 only works if no application can set a finite limit again.

7. After patching#

Qualys reports that researchers demonstrated unauthenticated disclosure of worker memory, which can expose passwords, tokens and API keys. If your server was exposed with a vulnerable version, treat the secrets that passed through PHP as possibly compromised.

  • Rotate the database credentials, API keys and session tokens that application handled.
  • Review the server and PHP logs since October 20: odd or very large multipart requests, repeated fatal errors and worker restarts.
  • Look for new or modified files in the web directory and in upload folders.
  • Keep a WAF rule that limits or blocks multipart where you do not use it, as an extra measure, not a substitute for the patch.
  • Record the date and version at which you patched each server.

These recommendations are good practice, not from PHP's advisory; adapt them to your environment and your security team.

8. Keep your numbers current#

The versions, identifiers and mitigations are those of the advisories published through October 22, 2026. New advisories and exploitation details may appear; check PHP's security repository and your distribution's advisory.

  • Subscribe to the security advisories of PHP and your vendor.
  • Keep an inventory of which PHP version runs on each server and container.
  • Automate an alert when a version falls below the fixed one.

Sources and scope

Documentation checked on October 22, 2026. Examples and decision criteria are editorial proposals; adapt them to your application's contract and validate them in an authorized test environment.

Compare these options

Compare cloud options

Review pricing, limits, conditions and sources for each option.

Open comparison
Advice

Is your case different?

Tell us your context and the options you're weighing. We reply with criteria and sources, and never sign you up for anything.

Request advice