Security advisory: PHP2Shell (CVE-2026-109618, CVE-2026-109619)
Two critical flaws in the PHP engine, disclosed on 21 October 2026, allow unauthenticated remote code execution and memory disclosure on almost every PHP version since 5.4. In this advisory we look at what is affected, how to patch, and what an enforced AppArmor profile stops until every workload is patched.
Hannes Ullman
bifrost security
Summary
Two critical flaws in the PHP engine itself, disclosed on 21 October 2026 by Wiz Research and Calif, allow unauthenticated remote code execution and memory disclosure. Almost every PHP version since 5.4 is affected, and a “Hello World” page is enough to be exploitable. PHP released fixes the same day.
PHP2Shell shows why runtime security is needed. Patching takes days or weeks: finding every image with PHP, rebuilding, redeploying. Attackers move faster, and AI-assisted exploit development makes complex bugs like these practical at scale.
No runtime control stops the memory corruption itself. An enforced allow-list profile does hinder most of what attackers do next: spawning shells, downloading tools, dropping binaries and stealing cluster credentials. Every blocked attempt also becomes a high-confidence alert.
Severity: critical (up to CVSS 9.2) · CVEs: CVE-2026-109618, CVE-2026-109619 · Affected: PHP ≥ 5.4, Apache mod_php and PHP-FPM · Fix: PHP security release of 21 October 2026
bifrost users: use the CVE prioritisation engine to find every running instance of vulnerable PHP, enforce runtime profiles on those workloads, and monitor bifrost alerts closely until every instance is patched.
About the vulnerabilities
All the bugs share one root cause: when PHP hits an out-of-memory fatal error at the wrong moment, it frees the request’s memory but leaves internal pointers to it behind. The next request on the same worker reuses that stale state. Attackers can force the out-of-memory condition with a small multipart request, below the default post_max_size, that expands into a huge number of internal allocations.
| CVE | Bug | Impact |
|---|---|---|
| CVE-2026-109618 | Session upload-progress state left dangling after a fatal error; unserializer bookkeeping use-after-free | Unauthenticated disclosure of worker memory via the PHPSESSID response cookie; remote code execution |
| CVE-2026-109619 | Fatal-error backtrace use-after-free; OPcache shared-memory type confusion | Remote code execution; corruption of OPcache state shared across workers |
Exposure depends on how PHP runs:
- Apache mod_php: the most generic chains work against arbitrary PHP scripts, with no application involvement.
- PHP-FPM (e.g. Nginx + FPM): exploitable when the application reaches
session_start(),session_decode()orunserialize(), which WordPress and phpMyAdmin do in normal operation. - Confirmed pre-auth RCE targets include WordPress, Drupal, Joomla, MediaWiki, Roundcube, Magento, phpMyAdmin, Nextcloud, Laravel and Symfony applications.
The memory disclosure works even if the application never uses sessions. Leaked memory can contain database passwords, API keys, tokens and data from other users’ requests.
Full technical detail: Wiz Research write-up, Calif write-up and the PHP Foundation announcement.
Why runtime security matters here
Between disclosure and the last patched pod, runtime enforcement is what stands between a successful exploit and a full compromise. The memory corruption happens inside the PHP worker, where no runtime control can see it. But an attacker needs the kernel for almost everything that follows: running programs, opening files, making connections, using capabilities. That is exactly where an allow-list AppArmor profile acts.
The table shows which post-exploitation steps an enforced profile hinders, and where you still depend on patching and other controls.
| Attacker goal | Enforced profile helps? | Why |
|---|---|---|
Spawn a shell, then download and run tools (sh, curl, wget) |
Yes | Exec of anything outside the learned set is denied and logged. This stops most automated, mass exploitation. |
| Drop and run a binary (miner, botnet agent) | Yes | No execute permission on writable paths. |
| Read the Kubernetes service-account token | Yes | Denied if the workload never reads it during normal operation. |
| Container escape via capabilities, mount, ptrace | Partly | Common escape primitives are denied; kernel bugs reachable through allowed syscalls remain. |
| Plant a PHP webshell in a writable directory | Partly | Only if the profile explicitly denies reading .php files in writable paths. |
| Read leaked secrets via memory disclosure | No | The leak returns over the legitimate HTTP response. |
| Run attacker code inside the PHP interpreter | No | Uses the worker’s own permissions. |
| Read app config, then abuse the app’s own access (a database, internal APIs, storage) | No | Whatever the application legitimately reaches, the attacker reaches too. An app with no database or backend access exposes far less. |
| Persist by poisoning OPcache shared memory | No | Fileless; survives until PHP or the pod restarts. |
| Connect out to command-and-control or cloud metadata | Mostly no | AppArmor network rules are coarse; use Kubernetes NetworkPolicy. |
Run in enforce mode while you patch. Enforce mode blocks these steps and also logs every denial, so it gives you both protection and detection. Detect mode (AppArmor’s complain mode) raises an alert for every step outside the profile, but the attacker’s actions still succeed. Switch exposed PHP workloads to enforce now; keep detect mode only where you have evidence the profile would break the application.
Profile quality decides the value. If the learned baseline includes /bin/sh (often because of mail() or proc_open()), writable plugin or theme directories, or broad egress, the protection is much weaker than it looks.
bifrost analyses each application’s behaviour and flags services where sh is launched as part of normal operation. Those are the workloads where a learned profile may still allow a shell, so review them first and scope the shell in a child profile. See the behaviour view in bifrost for details per service.
Detection value. Legitimate PHP workloads do not suddenly run sh or read the service-account token. These denials are near-zero-false-positive evidence that exploitation succeeded, which matters because the exploit leaves little trace in application logs.
Recommended actions
Work through these in order; steps 1 and 2 are the fix, the rest reduce risk until every workload is patched.
- Inventory every image that contains PHP. Include admin tools (phpMyAdmin, Nextcloud, Roundcube), monitoring UIs and vendor-supplied images, not only your main web applications. Note the SAPI (mod_php or PHP-FPM) for each. bifrost users already have this inventory: the CVE prioritisation engine lists every running workload with an affected PHP version right now.
- Patch properly.
- PHP is usually baked into the base image. Rebuild or re-pull images (
wordpress:latest,php:*-apache,php:*-fpmand so on) and redeploy; updating packages in a running container is not enough. - Verify the PHP version in the running pod, not in the Dockerfile.
- Restart all PHP workers and pods, even if you suspect nothing. In-memory implants and poisoned OPcache state survive until the process exits.
- PHP is usually baked into the base image. Rebuild or re-pull images (
- Apply interim PHP settings on anything not yet patched. These are partial mitigations;
memory_limit=-1removes PHP’s memory cap, so watch for memory exhaustion.- PHP-FPM:
session.upload_progress.enabled=0andmemory_limit=-1 - Apache mod_php:
memory_limit=-1
- PHP-FPM:
- Move runtime profiles to enforce mode for exposed PHP workloads, after checking their behaviour for shell launches. Where a workload launches
shin normal operation (often throughmail()orproc_open()), scope the shell in a child profile that may only run/usr/sbin/sendmail, or switch to SMTP over the network. - Restrict egress with Kubernetes NetworkPolicy: allow only the database, SMTP and known APIs, and block the cloud metadata endpoint
169.254.169.254. - Harden the pod: read-only root filesystem,
automountServiceAccountToken: falsewhere the workload does not call the Kubernetes API. - Monitor bifrost alerts until every workload is patched. Treat any denied exec, service-account token read or write to a code path from
apache2orphp-fpmas a likely compromise: isolate the pod, check integrity and rotate secrets.
What to look for
The exploit runs below the application layer; any of these signals on an unpatched workload warrants investigation.
For bifrost users, the runtime signals arrive as bifrost alerts.
- Requests: deeply nested multipart field names with empty filenames; chunked or lengthless multipart bodies; bursts of 500s; oversized or garbled
PHPSESSIDcookies (memory disclosure). - PHP logs: clustered “Allowed memory size … exhausted” fatals; worker segfaults or unexpected PHP-FPM restarts.
- Runtime (bifrost alerts): denied exec from
apache2orphp-fpm; service-account token reads; writes to code paths; unexpected outbound connections, especially to169.254.169.254. - Integrity: new or changed
.phpfiles, new admin accounts, unfamiliar scheduled tasks.
Rotate secrets
Rotate secrets held by any PHP process that was exposed before patching, even if you find no indicators. The memory disclosure is unauthenticated and leaves almost no trace, and no runtime control can stop it.
- Database credentials
- API keys and cloud credentials in environment variables or config files
- Application salts and signing keys (for example WordPress
AUTH_KEYand related salts); invalidate active user sessions - TLS private keys, if Apache or PHP held them in the same process
How bifrost helps
bifrost gives you the two things that matter most during a campaign like this: knowing where vulnerable code is running, and limiting what an attacker can do there.
- Find every running instance: the CVE prioritisation engine combines SBOM data with what is actually running in your clusters. You see every workload executing an affected PHP version, including forgotten admin tools and vendor images, ranked for patching.
- Hinder post-exploitation: in enforce mode, per-container AppArmor profiles tailored to each workload’s learned behaviour block shells, tool downloads, dropped binaries and credential theft. Move exposed PHP workloads to enforce mode now.
- Alert on success: every denial from
apache2orphp-fpmraises an alert. These have near-zero false positives and give you a signal where application logs stay silent.
Our advice to bifrost users: monitor bifrost alerts closely until every affected workload is patched, and act on any PHP-related denial as a likely compromise. Runtime security buys you time; the patch closes the hole.
Hardened before the patch. In enforce mode, the kernel blocks operations outside the profile, including those an exploit not yet discovered would need.
Contact us if you want help reviewing or tightening profiles for your PHP workloads.
References
Tags
Do you know what's exploitable in your environment?
Deploy bifrost via Helm and it reads how every container is actually deployed, cutting your CVE list before a single line of code changes.