On 21 October 2026 the PHP project released fixes for two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, now widely known as PHP2Shell. Both can allow unauthenticated remote code execution, both are rated CVSS v4.0 9.2, and on 26 October CISA added both to its Known Exploited Vulnerabilities catalog.
Most Shopify apps built on Laravel or plain PHP have exactly the exposure that matters here: a public backend that accepts requests from anyone on the internet, including webhook and app proxy endpoints. The request does not need to be authenticated or even reach your application code.
Over the past week we patched PHP on client servers that run Shopify app backends. On most of them it was a routine package update. On some, the operating system was Ubuntu 20.04, and the practical fix was to upgrade the server to 22.04 first. This article covers what to patch, how to check that the fix is really running, what to do if you cannot patch today and what we learned from the upgrades.
The short version
- Update PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or install your distribution’s fixed package. Updating is the only complete fix.
- Restart everything that runs PHP: PHP-FPM, Apache with mod_php, queue workers, Octane or FrankenPHP. Updated files on disk do not replace code already loaded into memory.
- Distribution packages keep the old version number.
php -von a patched Ubuntu 22.04 server still says 8.1.2; check the package version instead. - PHP 8.1 and older get no upstream release. On Ubuntu 20.04 the fixed packages are available only through Ubuntu Pro (ESM). Otherwise you need an operating system upgrade.
- If the server was exposed before patching, check it for signs of compromise before you treat the incident as closed.
What PHP2Shell is
The two vulnerabilities are in the PHP engine itself, not in a framework or an extension:
| CVE | Issue | Notes |
|---|---|---|
| CVE-2026-109618 | Memory corruption during out-of-memory handling. A request that exceeds memory_limit while request data is only partly initialized can leave cleanup treating that data as valid. | Reachable during request parsing, for example multipart form uploads. Highest risk with Apache mod_php. |
| CVE-2026-109619 | Use-after-free through stale global state. State from one request can outlive its memory and be used by a later request on the same worker. | Triggered after a fatal error or timeout interrupts cleanup. Session upload progress is an early consumer of that state. |
The PHP project says every installation serving web requests is at risk: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. It rates mod_php as the highest-risk setup, because a demonstrated exploit runs before any application code. Command-line scripts that do not serve web requests are not the main concern, but they usually share the same packages and are updated with them anyway.
The primary sources are the PHP security advisory, the CERT-EU advisory 2026-018 and the Ubuntu notices USN-8948-1 and USN-8948-2.
Step 1: Find every PHP runtime
The first job is an inventory. Production web servers are the obvious part. These are the ones teams tend to forget:
- Staging and preview environments that are reachable from the internet.
- Docker images, including base images pinned to an old tag, and the CI images that build them.
- Separate hosts or containers for queue workers, schedulers and cron jobs.
- Laravel Octane with a FrankenPHP binary stored inside the project, which a system package update does not touch.
- Admin tools and third-party products that embed their own PHP.
On Debian and Ubuntu, this lists the installed PHP packages and their exact versions:
dpkg -l 'php*' 'libapache2-mod-php*' | awk '/^ii/{print $2, $3}'
Step 2: Pick the fix for each platform
What “patched” means depends on where PHP comes from:
| Platform | PHP branch | Fixed package or action |
|---|---|---|
| Upstream builds, Docker official images | 8.2 to 8.5 | 8.2.35, 8.3.36, 8.4.27, 8.5.12; rebuild images with a fresh pull |
| Ubuntu 26.04 LTS | 8.5 | 8.5.4-0ubuntu1.5 |
| Ubuntu 24.04 LTS | 8.3 | 8.3.6-0ubuntu0.24.04.13 |
| Ubuntu 22.04 LTS | 8.1 | 8.1.2-1ubuntu2.28 (Ubuntu backport; no upstream 8.1 release) |
| Ubuntu 20.04 LTS | 7.4 | 7.4.3-4ubuntu2.29+esm5, Ubuntu Pro (ESM) only |
| Ondřej Surý’s PPA and Debian repository | 8.2 to 8.5 | Upstream fixed versions |
Ubuntu versions are taken from USN-8948-1 and USN-8948-2. If you use another distribution, check its own advisory: distribution vendors received the patches before public release.
On Ubuntu and Debian, the update itself is ordinary:
sudo apt-get update
apt list --upgradable 2>/dev/null | grep php
sudo apt-get install --only-upgrade 'php8.1*' libapache2-mod-php8.1 # use your branch
For Docker, rebuild with a fresh base image and recreate the containers. Restarting the old container does nothing:
docker build --pull -t your-app .
docker compose up -d --force-recreate
Step 3: Do not trust php -v
This catches out both teams and scanners. Debian and Ubuntu backport security fixes into the version they shipped, so the upstream version number does not change. A fully patched Ubuntu 22.04 server reports PHP 8.1.2, and a patched 24.04 server reports 8.3.6. Remote scanners that read the version from headers will still flag them as vulnerable.
Check the package version against the table above, and confirm that the CVEs are in the package changelog:
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.1-fpm/changelog.Debian.gz
If you keep an evidence trail for Shopify or for your own customers, record the package version and the changelog check, not the php -v output.
Step 4: Restart everything that runs PHP
A package update replaces files on disk. Processes that already loaded the old PHP keep running it until they restart. In our checks, this was the most common reason a “patched” server was still running vulnerable code.
- PHP-FPM: restart the service, for example
systemctl restart php8.1-fpm. - Apache with mod_php: do a full restart of Apache. A graceful reload can keep the old module loaded.
- Laravel queue workers:
php artisan queue:restart, or restart the Supervisor or systemd units that run them. - Octane and FrankenPHP: restart the server process, and replace any FrankenPHP binary bundled with the project.
- Containers: recreate them from the rebuilt image.
Then check that no process still maps the old, deleted PHP libraries. Empty output is what you want:
sudo grep -lE ' /(usr|lib|opt).*\(deleted\)$' /proc/[0-9]*/maps
sudo needrestart -b -r l # Debian/Ubuntu: services that still need a restart
If you cannot patch today
The PHP team is clear that there are no complete mitigations. The settings below reduce exposure while you schedule the update. They must be set administratively, in php.ini, the FPM pool or the web server configuration, because ini_set() and .user.ini apply too late in the request:
session.upload_progress.enabled = 0. This targets the CVE-2026-109619 session path and also mitigates a related, lower-risk session issue, CVE-2026-115560, which PHP will fix in its next regular release.- On PHP 8.5,
fatal_error_backtraces = 0. - For CVE-2026-109618, PHP suggests
memory_limit = -1, set so applications cannot override it, together with limits on request size at the web server or proxy. This removes a safety limit on memory, so treat it strictly as a temporary measure and restore a real limit after updating. - Keep
post_max_sizesmall (8M without uploads, 32M with them), andupload_max_filesizebelow it. - Restart all PHP workers after any of these changes.
Hiding errors with display_errors or error_reporting does not help. A WAF rule from a provider such as Cloudflare is a stopgap that only covers traffic routed through it.
Check for signs of compromise
Exploitation attempts were reported before and after disclosure. If a server ran vulnerable PHP on the public internet, patching closes the hole but does not tell you whether someone already used it. For Shopify app backends, we look at:
- PHP-FPM and web server logs for worker crashes, segmentation faults and memory-limit errors, especially around unusual multipart requests.
- New or modified files in the web root, storage and cache directories, and unexpected cron entries, systemd units or SSH keys.
- Unexpected outbound connections from the web or worker processes.
- Database access and Admin API activity that does not match normal app behaviour.
If a server was being probed and you have not restarted it yet, consider capturing worker memory and logs first: a restart destroys that evidence. If you find signs of compromise, treat the app’s secrets as exposed. That includes the Shopify client secret, stored shop access tokens, APP_KEY, database credentials and third-party API keys. Rotate them, and check your obligations to merchants and to Shopify. That is the point where an incident report and an independent test become relevant.
What we learned upgrading Ubuntu 20.04 to 22.04
Ubuntu 20.04 left standard support in May 2025. For PHP2Shell it gets a fixed PHP 7.4 package only through Ubuntu Pro. That leaves two honest options:
1. Enable Ubuntu Pro and install the ESM package. This is fast, but the app stays on PHP 7.4, which upstream ended in 2022, and current Laravel releases and many Composer packages no longer support it. 2. Upgrade the server to a release with standard support, and move the app to a supported PHP branch at the same time.
Where the app could run on PHP 8.1 without major changes, we upgraded the servers to 22.04. These are the points that took the most time:
- Snapshot first, and rehearse. We ran
do-release-upgradeon a snapshot or a staging copy before touching production, and kept the snapshot until the app had run cleanly for a while. - PHP 7.4 to 8.1 is an application change, not only a package change. Run the test suite and check the error log for deprecations and type errors. Passing
nullto non-nullable parameters of internal functions is deprecated in 8.1 and shows up a lot in older code. Runcomposer updateandcomposer auditagainst the new PHP version. - Package and path names change. The
php7.4-*packages and extensions becomephp8.1-*, FPM pool files move from/etc/php/7.4/fpm/pool.d/to/etc/php/8.1/fpm/pool.d/, and the socket becomes/run/php/php8.1-fpm.sock. A web server config still pointing at the old socket gives a 502 right after the upgrade. - OpenSSL 3. Ubuntu 22.04 ships OpenSSL 3, which rejects some legacy algorithms and old keys. Check outbound integrations, client certificates and any old TLS endpoints the app calls.
- Workers and schedulers. Supervisor and systemd units, cron entries and deploy scripts often contain an explicit
php7.4path. - Verify after the upgrade. Check the fixed package version, the restarts and the deleted-library check above, plus a test of the app’s critical paths: install, OAuth, webhooks and billing.
Ubuntu 22.04 is a step, not the destination. Its standard support ends in May 2027, and it stays on PHP 8.1 with Ubuntu backports. Separately, the upstream PHP 8.2 branch loses security support on 31 December 2026. For servers we upgraded, we have planned the next move: Ubuntu 24.04 or 26.04, or PHP 8.3 or newer from a maintained repository.
Quick checklist
- Every PHP runtime is inventoried, including staging, containers, workers and bundled binaries.
- Each runtime has a fixed upstream version or a fixed distribution package, confirmed by package version and changelog.
- PHP-FPM, Apache, workers and Octane are restarted, and no process maps deleted PHP libraries.
- Temporary mitigations are recorded, and
memory_limitis restored to a real value after patching. - Logs and the file system of servers that were exposed are checked for signs of compromise.
- Servers on PHP 8.1 or older, or on Ubuntu 20.04 without Ubuntu Pro, have an upgrade plan with a date.
- The evidence is saved: versions, commands and dates.
Where we can help
If you need a second check after patching, our Shopify App Security Readiness review covers the external surface and app-level controls of your backend. If you see signs of compromise, or Shopify has asked for an incident report, start with our Shopify App Incident VAPT. If you have already fixed the issues and need independent confirmation with evidence, see the Retest & Evidence Pack.