Tiago Sampaio

main ai engineering career

engineering

11 min read

php2shell: What Magento Teams Need to Do About the PHP RCE

Two critical PHP engine bugs (CVE-2026-109618, CVE-2026-109619) allow pre-auth RCE on Magento. Which PHP to install, how to patch, and how to mitigate.

php2shell-magento.png — 1920 × 1080

Cover: php2shell: update PHP now. Panel: CVE-2026-109618 and CVE-2026-109619; 9.2 CVSS v4 for both CVEs; PHP 5.4 and later affected according to Sansec; fixed versions released October 21. Sources: PHP security advisories, php.net and Sansec.

On October 21, 2026, PHP released fixes for two critical bugs in the PHP engine itself: CVE-2026-109618 and CVE-2026-109619, nicknamed php2shell. Both are rated 9.2 on CVSS v4 and allow unauthenticated remote code execution. According to Sansec, they affect every web server running PHP 5.4 or later, and researchers confirmed pre-auth remote code execution against Magento 2.4.9.

This one is different from the Magento vulnerabilities we usually patch. There is no Composer package to update and no patch file to apply to the codebase. The bug lives in the PHP binary, so a store with perfectly clean code is still exposed. Sansec puts it plainly: “You cannot fix it from within your store codebase.”

Sansec reports that Cloudflare saw early attacks hours before the release, and that technical writeups and an exploit tool are now public. The PHP Foundation says it had not seen a functional exploit at the time of release, but a proof-of-concept video that was published by mistake and then deleted had already spread. Either way, treat this as urgent. In this post I go through what the bug is, which Magento stores are affected, how to patch correctly, and what to do if you can’t patch right away.

What happened

The weakness is old. Sansec traces it back to a 2004 finding by Stefan Esser, a 2020 PHP bug report and a 2024 audit, none of which proved it was exploitable. This year, three parties turned it into working exploits.

Date (UTC)Event
2026-08-01Calif finds multiple RCE paths
2026-08-11Calif reports the first bug to PHP; OpenAI reports the same root cause
2026-08-24Wiz Research reports the vulnerabilities to PHP
2026-10-20 ~15:00The CVE IDs start circulating online with a wrong release time. The PHP Foundation says no technical details leaked. The release moves from October 22 to October 21.
2026-10-21 07:41Chainguard publishes fixed images before the release, which the Foundation says broke the embargo
2026-10-21 07:43Cloudflare reports early attacks and deploys WAF rules (per Sansec)
2026-10-21 14:00PHP releases the fixed versions

Update, October 21 (afternoon): The PHP Foundation’s post on the release corrects a few early reports. Only the CVE IDs leaked before the release, not technical details. A CVSS score of 10.0 that circulated was wrong; the real score is 9.2. I’ve updated the timeline and the opening.

The fixed versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. The php.net announcement says: “All PHP users are strongly encouraged to update immediately.”

OpenAI says Calif found one of the flaws using its GPT-5.6 Sol model, as part of an initiative called Patch the Planet. Sansec notes that all three research teams credit frontier AI models with making the exploits practical.

How the bug works

Here is the simple version. When a request crosses PHP’s memory_limit, PHP aborts it with a fatal out-of-memory error. That abort can interrupt PHP’s internal code before it finishes cleaning up. Pointers to freed memory and half-initialized state stay behind. The next request handled by the same worker then treats that freed memory as live data.

The PHP advisories describe the two CVEs this way:

CVEPHP advisory titleWhat it allows (per Sansec)
CVE-2026-109618RCE via OOM corruptionRemote memory disclosure and RCE
CVE-2026-109619RCE via stale global stateRCE

The memory disclosure part deserves attention. According to Sansec, CVE-2026-109618 can send freed worker memory back to the client inside a PHPSESSID response cookie. That memory can hold passwords, session tokens, API keys and TLS private keys from other requests. It works even when the application never calls session_start(), because the upload-progress handler runs before any application code.

Both setups are affected. Under Apache with mod_php, the worker is reused after the failed request. Under PHP-FPM, the path is different, but Sansec says the same dangling state can survive in a worker that keeps running.

Which Magento stores are affected

All of them, until PHP is updated. The real question is whether your PHP branch has a fix at all. PHP only supports 8.2 and later, so servers on PHP 8.1 or older get no official release with the fix.

Cross that with Adobe’s system requirements:

Magento / Adobe CommerceSupported PHPFixed PHP version available
2.4.98.58.5.12
2.4.88.4, 8.38.4.27, 8.3.36
2.4.78.3, 8.28.3.36, 8.2.35
2.4.68.2, 8.18.2.35 (8.1: EOL source branch only, see update)
2.4.58.1No release yet (EOL source branch only, see update)

This is where I would spend my attention first. A store on 2.4.6 running PHP 8.1 has a way out: 2.4.6 supports PHP 8.2, so moving to 8.2.35 is a PHP change, not a Magento upgrade. Test it on staging, but it is a much smaller job than a release-line upgrade.

A store on 2.4.5 has no supported PHP with a released fix. Until it is upgraded, it needs the mitigations below, the EOL source branch from the update below, or it needs to be offline.

Update, October 21: PHP core developers also published source branches with a “minimal mitigation for EOL version” for PHP 8.1, 8.0 and 7.4 in the official php-src repository (for example PHP-8.1-CVE-2026-109618-109619). They aren’t an official release yet: there’s no package, the advisories don’t list them, and you have to build PHP from source. For a 2.4.5 store stuck on PHP 8.1, it’s a way to close the hole while you plan the upgrade. For 2.4.6, moving to PHP 8.2.35 is still the cleaner path. Thanks to Ümit Kayacan and Pradip Shah for pointing it out.

One more date to remember: php.net says PHP 8.2 “reaches end of life on 31 December 2026.” So 8.2.35 buys time, not a long-term home.

Patch it correctly

Updating the package is only half the job. Mark Shust made a good point about this on LinkedIn: the PHP that answers on the command line is not always the PHP that serves your pages.

1. Check what actually serves the store

Check the FPM binary, not only the CLI:

Shell
php -v                 # the CLI binary
php-fpm8.3 -v          # Debian/Ubuntu naming; use your version
php-fpm -v             # RHEL-based naming

A common tip is to check from a real web request, with a temporary file that echoes PHP_VERSION. On a standard Magento setup that file won’t run. Magento’s sample nginx configuration only executes a short list of PHP files in pub/ (index.php, get.php, static.php, health_check.php and the error pages) and denies every other .php file.

So for Magento I check two things: the version of the FPM binary, and that the running service was started after the update:

Shell
systemctl status php8.3-fpm | grep Active    # the start time must be after the update

2. Update PHP

Use your distribution’s packages or your hosting provider’s update:

Shell
# Debian/Ubuntu: list the PHP packages you actually have installed
sudo apt-get update && sudo apt-get install --only-upgrade php8.3-fpm php8.3-cli php8.3-common

# RHEL-based
sudo dnf upgrade 'php*'

Two warnings from Sansec and Mark Shust:

  • Distribution packages often backport security fixes and keep the old version label. Check your vendor’s advisory or the package changelog, not only the number.
  • If you install PHP from Remi’s repository, common on RHEL-based servers, the fix was not available there on October 21. Sansec suggests switching to your distribution’s packages or building PHP from source.

If you are on managed hosting or Adobe Commerce on Cloud, ask your provider to confirm the update and the restart.

3. Restart PHP

The old interpreter keeps running in memory until you restart it:

Shell
sudo systemctl restart php8.3-fpm     # PHP-FPM, with your version
sudo systemctl restart apache2        # mod_php on Debian/Ubuntu (httpd on RHEL)

4. Check again

Repeat step 1: the FPM binary must report the fixed version (or your vendor’s patched build), and the service start time must be after the update. If your server does execute arbitrary PHP files, a temporary file with <?php echo PHP_VERSION;, opened in the browser and deleted right after, is the most direct check.

If you can’t patch within the hour

Sansec lists temporary, partial mitigations. In its words: “None of them replaces the patch.”

Block multipart/form-data

Every known trigger arrives as a multipart/form-data request, which PHP parses before Magento runs. For nginx, in every server block that passes requests to PHP:

Nginx
if ($http_content_type ~* "multipart/form-data") {
    return 403;
}

For Apache, in every VirtualHost that serves PHP:

Apache
RewriteEngine On
RewriteCond %{HTTP:Content-Type} multipart/form-data [NC]
RewriteRule ^ - [F]

This breaks every file upload, including product images in the Magento admin. Sansec also warns that some stores submit cart or checkout forms as multipart, so place a test order after adding the rule, and remove it once PHP is updated.

Put the store behind Cloudflare, and lock the origin

Cloudflare deployed WAF rules against the known exploits for all customers, including the free plan. Sansec notes they only help if the origin refuses traffic that doesn’t come from Cloudflare. If an attacker knows your origin IP, they go around the WAF. Set the firewall to accept HTTP and HTTPS only from Cloudflare’s IP ranges.

Go offline or read-only

If you can take the store offline, stop PHP-FPM and serve a static maintenance page from the web server. Magento’s own maintenance mode does not help, because PHP parses the request before Magento checks the flag.

If you can’t go offline, Sansec suggests making the store read-only by allowing only GET and HEAD:

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

Customers can browse, but they can’t log in or place orders.

PHP settings

For PHP-FPM, Sansec gives these pool settings, for example in /etc/php/8.3/fpm/pool.d/www.conf:

INI
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[memory_limit] = -1

Reload the workers and confirm the values from a web request. Be careful with memory_limit = -1: it closes the memory-exhaustion path, but a single request can then use all of the server’s memory. Treat it as short-term and watch memory use. Sansec also notes that turning off upload progress alone is not enough.

The PHP advisory for CVE-2026-109619 adds two more options: on PHP 8.5, set fatal_error_backtraces=0 and restart every worker; and consider pm.max_requests=1, which makes each FPM worker serve a single request. That second one costs performance, so I would only use it for a short window.

Detecting exploitation

These attacks run below the application layer, so Magento’s own logs may show little. Sansec lists the signals Wiz reported:

  • unusual multipart requests followed by memory-limit errors or repeated HTTP 500 responses
  • unexpectedly large or malformed PHPSESSID response cookies
  • unexplained PHP worker restarts or churn

A spike of PHP fatal errors around memory_limit is worth a look:

Shell
grep -i "allowed memory size" /var/log/nginx/error.log /var/log/php*-fpm.log 2>/dev/null | tail -50

Adjust the paths to your setup. With PHP-FPM behind nginx, these errors often show up in the nginx error log as “FastCGI sent in stderr”.

My own recommendation, beyond what the sources say: because CVE-2026-109618 can leak memory from other requests, I would treat any server that ran a vulnerable PHP on the public internet after the CVE IDs went public on October 20 as a candidate for secret rotation. That means admin passwords, integration tokens, payment and API keys, and the Magento encryption key if you have reason to believe the server was hit. Run a malware scan too, since an RCE leaves no trace in your code until someone uses it.

What this says about old bugs

The part that stays with me is the timeline. The weakness was known in some form for about twenty years. What changed in 2026 was the cost of weaponizing it. According to Sansec, Wiz reports that a single researcher built the exploit in about a week, using AI models to analyze failures and adapt it across PHP versions and applications.

For those of us who run Magento stores, that changes the patch math. The time between “an old weakness becomes exploitable” and “mass exploitation” is getting shorter. Servers stuck on an unsupported PHP version were always a risk. Now they are a risk that can be turned into a working exploit in days.

A checklist for today

  • Check the PHP version that PHP-FPM actually runs, not only php -v.
  • Update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or confirm your vendor’s backported fix.
  • Restart PHP-FPM or Apache, then check the version and the service start time.
  • Stores on 2.4.6 with PHP 8.1: move to PHP 8.2.35.
  • Stores on 2.4.5: mitigate, build the EOL source branch, or take offline, and plan the upgrade.
  • If you can’t patch now: block multipart, lock the origin behind Cloudflare, or go offline with a static page.
  • Watch for memory-limit errors, 500s and odd PHPSESSID cookies.
  • After patching, consider rotating secrets on exposed servers.

How many PHP versions are you running across your Magento servers today, and how fast could you update all of them?

Sources

Written by

TS

Tiago Sampaio

Adobe Commerce & Magento 2 Expert

Senior developer with 15+ years building software. Writes practical fixes for Magento 2 and Adobe Commerce, and honest notes on growing as a developer.

$ git remote add inbox

Get new commits by email

New posts from both branches, engineering and career. No spam, unsubscribe any time.

$ git notes show

Leave a Reply