It's Beginning to Look a Lot Like CVE Season: PHP2Shell and Beyond

It's Beginning to Look a Lot Like CVE Season: PHP2Shell and Beyond

Why patching alone isn't going to save us, and why we need to rethink how we build infrastructure.

Maybe it's because the stores are already filled with holiday-themed decorations, Christmas trees, and things nobody needs until December, but apparently the security industry has decided to get into the festive spirit early too. Except instead of presents, we're getting critical CVEs, remote code execution vulnerabilities, and kernel escapes. Somewhere, an on-call engineer is probably looking at their holiday calendar wondering whether they'll actually get to use any of that PTO.

This week's gift was PHP.

On October 21, the PHP project released emergency fixes for two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, collectively called PHP2Shell. Both have a CVSS v4.0 score of 9.2 and can lead to unauthenticated remote code execution under the right conditions.

If you operate PHP infrastructure, your week probably just got a little more exciting. Now you need to find every affected installation, figure out which applications depend on it, check whether your distribution has shipped the patches, test for regressions, rebuild images, and roll everything out. Somewhere in that inventory is probably a legacy application nobody wants to touch because it somehow requires PHP 7 and a collection of dependencies that haven't seen an update since the last decade. Good luck getting that one through regression testing.

Jokes aside, PHP has released fixes in versions 8.5.12, 8.4.27, 8.3.36, and 8.2.35. You should be updating immediately, or applying verified vendor backports where appropriate.

But there's a much larger problem here than these two vulnerabilities. We've been scrambling to patch critical infrastructure for years, and I don't see that changing anytime soon.

What Went Wrong With PHP: Inside the PHP2Shell CVEs

The two vulnerabilities are worth looking at because they sit deep inside the PHP runtime, rather than in an application or an unfortunate third-party dependency.

CVE-2026-109618 involves corruption during out-of-memory handling.

PHP uses longjmp() in certain fatal error paths. Unlike a normal function return, longjmp() transfers execution back to a previously established point, bypassing intervening stack frames and their normal cleanup flow. This is fairly delicate territory for a runtime managing complex internal state.

Imagine an allocation failing halfway through initializing a request structure. PHP jumps out of the failing operation, but some internal state has only been partially initialized. Later, the engine attempts to clean it up as though initialization had completed successfully, potentially producing memory corruption.

Researchers demonstrated unauthenticated remote code execution on Apache with mod_php, even before the application code executes. Remote code execution was also confirmed in PHP-FPM through application-level paths. Depending on the execution path, your carefully implemented application authentication and input validation may never get a chance to run.

The second vulnerability, CVE-2026-109619, involves stale global state.

A fatal error can leave internal pointers referencing memory owned by a previous request. A later request handled by the same PHP worker may dereference those stale pointers after the memory has been freed. This creates a use-after-free, with demonstrated remote code execution against an application using Apache with mod_php.

Both bugs live in the machinery responsible for memory management, request processing, and error handling, which almost every PHP application relies on regardless of what the application itself does.

The Underlying Problem Was Known in 2024

The PHP Foundation published an unusually transparent account of what happened.

The underlying out-of-memory issue was identified in 2024 during security work following an external audit. The team understood that something was wrong, but didn't yet have a demonstrated remote code execution path.

Some proposed fixes risked breaking backward compatibility. Others introduced denial-of-service concerns, and some required changes across PHP's third-party extension ecosystem. The number of people who understood the relevant internals well enough to make those decisions was small, and the work stalled repeatedly.

The Foundation acknowledged this publicly, describing it as a structural failure involving both its decision-making process and limited engineering resources.

It's easy to sit on the sidelines and ask why a vulnerability wasn't fixed sooner. It's considerably harder to maintain a runtime that supports millions of applications, preserve compatibility across an enormous ecosystem, and make fundamental changes to memory management without breaking things. I appreciate that the Foundation chose to be transparent about the technical challenges and its own shortcomings rather than pretend the entire situation was unavoidable.

By August 2026, independent researchers had demonstrated the full severity of the issue, and the fixes became a priority. Then, just as the team was coordinating the release, information started leaking. The disclosure date had to be moved forward, proof-of-concept material began circulating, and distribution maintainers had even less time to prepare. I suspect nobody involved had a particularly relaxing Tuesday.

The PHP maintainers, security researchers, and distribution engineers deserve credit for getting the fixes out. But we should also acknowledge how much of our critical infrastructure depends on relatively small groups of engineers maintaining enormous amounts of software, while the rest of the industry expects them to respond to every security issue almost instantly.

And the number of vulnerabilities being discovered isn't exactly slowing down.

And PHP Wasn't Even the First Surprise This Week

A few days before the PHP disclosure, we had PwnEscape, CVE-2026-99999, which exposed another problem with how we think about security boundaries.

PwnEscape is a Linux kernel vulnerability affecting the networking path used by certain VM configurations, including KVM-based environments.

The problem involves CHECKSUM_PARTIAL, an optimization that allows checksum calculation to be deferred.

A guest can supply packet data and checksum metadata that the host eventually processes. Under vulnerable conditions, the host performs a checksum write into an IPv4 header after that header has already been parsed and validated. The modified header can then be interpreted differently by subsequent fragmentation code.

The researcher turned this into a repeatable host-memory disclosure primitive and, in a controlled lab environment, demonstrated a chain leading to host code execution. The production Vercel demonstration established the memory leak, not the complete host-code-execution chain, and we should be careful not to conflate the two.

The vulnerable code wasn't in KVM itself but in the Linux networking stack.

A VM can have its own kernel, its own memory, and hardware-enforced isolation, but guest-originated I/O still needs to be processed somewhere. If privileged host code handling that I/O contains a vulnerability, the isolation boundary can be attacked through a completely different subsystem.

We also had CVE-2026-19444, a Kubernetes vulnerability involving kubectl cp on Windows.

An attacker controlling the contents of a container can influence the archive data returned when an operator copies files from it. A path traversal flaw can cause files to be written outside the intended destination, using the operator's local filesystem permissions.

Here, container-controlled input crosses into the operator's workstation. It's a different vulnerability class, a different security boundary, and a different consequence from PHP2Shell or PwnEscape, but all three involve something less trusted providing input to something more privileged. Somewhere along the way, that input is handled incorrectly, and an assumption we've made about a security boundary no longer holds.

We've Become Very Good at Finding Vulnerabilities. Now What?

We have vulnerability scanners, SBOMs, automated dependency updates, security advisories, patch management platforms, and dashboards showing how many critical CVEs remain unresolved. These are useful tools, and we absolutely need them, but they only address part of the problem.

A scanner finding a vulnerable package doesn't tell us whether that package is reachable. A patched container image doesn't tell us whether the old image is still running somewhere. And a clean vulnerability report today says very little about the vulnerability someone will discover tomorrow.

Consider the time between a vulnerability being introduced and a patch reaching production.

First, someone needs to discover it, understand the root cause, develop a fix, review it, test it, and coordinate its release.

Distributions may need to backport the patch. Vendors need to rebuild packages. Platform teams need to update base images and dependencies, roll out new workloads, and verify that the vulnerable code is no longer executing.

For a kernel vulnerability, the fix might require draining nodes and rebooting machines. For a language runtime vulnerability, it may require rebuilding and redeploying every affected application image. And if the application depends on behavior changed by the security fix, there's another problem to solve before the patched version can safely reach production.

All of this takes time, even when everyone involved does their job well. And it doesn't account for the much longer period when vulnerable code exists but nobody knows it's vulnerable.

We cannot build a security model that assumes every vulnerability will be discovered, fixed, and deployed before someone exploits it.

I'd like to see us spend more time engineering systems that can tolerate vulnerabilities, rather than continuing to operate as though eliminating every vulnerability is a realistic objective.

Let's Assume the Attacker Already Has Code Execution

Take PHP2Shell.

An attacker successfully exploits the runtime and obtains code execution with the privileges of the PHP worker. That doesn't automatically mean they've compromised the host or the Kubernetes cluster, but we now need to examine everything that process is allowed to do.

If it can read database credentials, the attacker can probably read them too. If it has a Kubernetes service account token mounted into its filesystem, the attacker may be able to use that identity against the API server. If it has unrestricted network egress, the attacker can attempt to reach other services, metadata endpoints, or external infrastructure.

If the process has access to a database with permissions to read every customer's records, a beautifully isolated execution environment isn't going to prevent the attacker from using those permissions. And if the workload is privileged, has dangerous host mounts, or can reach a container runtime socket, the attacker may have a much easier path toward the host.

None of these attacks necessarily requires a container escape.

MicroVMs alone cannot solve this problem, and neither can seccomp, SELinux, network policies, or workload identity when used independently. We have to examine the complete set of capabilities inherited by a compromised process and understand which controls restrict those capabilities.

Security Needs to Be Designed Across Multiple Boundaries

Reduce What a Compromised Process Can Do

Start at the process level.

A PHP worker generally doesn't need to run as root. It shouldn't need CAP_SYS_ADMIN, unrestricted access to /proc, writable application binaries, or arbitrary access to privileged kernel interfaces.

Use non-root identities, drop unnecessary capabilities, enforce filesystem permissions, and make application filesystems read-only where possible. Apply seccomp policies to limit the system calls exposed to the workload, and use AppArmor or SELinux to constrain filesystem and process interactions.

These mechanisms won't prevent PHP2Shell from exploiting PHP's memory management, but they can restrict the operations available after the attacker gains execution. Reducing the reachable kernel API surface may also make subsequent privilege escalation harder.

There are limits to what these restrictions can accomplish, particularly when the application legitimately needs the operation the attacker wants to abuse. If the PHP process is permitted to read customer data, filesystem restrictions cannot distinguish the application reading that data from an attacker controlling the application and doing the same thing.

We need to address the authority the workload has in the first place.

Least Privilege: Give Workloads Less Authority By Default

We talk a lot about least privilege, but in practice, application identities often accumulate permissions over time.

A service account starts with access to one Kubernetes resource. Six months later, someone adds another permission to fix a deployment issue. Then another. Eventually the application has access to things nobody remembers it needing.

The same thing happens with database credentials, cloud IAM policies, and internal APIs. An attacker with code execution can inherit these capabilities without ever leaving the original process.

Kubernetes RBAC needs to be scoped to actual application requirements. Workloads that don't need Kubernetes API access shouldn't have service account tokens automatically mounted.

Use narrowly scoped, short-lived workload credentials wherever possible, and rotate or revoke them when a compromise is suspected.

Restrict network ingress and egress. A compromised frontend shouldn't automatically be able to connect to every internal service because everything happens to live on the same cluster network.

We also need to distinguish authentication from authorization. Mutual TLS can establish which workload made a request, but it cannot establish whether the person currently controlling that workload is trustworthy. If a compromised service has permission to delete customer records, mTLS will happily authenticate it while it does exactly that.

Not particularly comforting when you think about how much authority we routinely give application identities.

Use Stronger Isolation Where the Threat Model Demands It

Traditional Linux containers are built around shared-kernel isolation.

Namespaces separate resources, cgroups control resource consumption, seccomp restricts system calls, and Linux security modules enforce additional access controls. These mechanisms provide meaningful protections, but every container on the same host still depends on the security of that host kernel.

MicroVMs change that arrangement by placing workloads behind a hypervisor-enforced boundary with separate guest kernels.

An attacker who compromises an application inside a microVM generally needs an additional vulnerability or exposed capability to compromise the host. This is useful, particularly for multi-tenant environments, untrusted code execution, and workloads with different trust requirements.

It's also part of the problem we're working on at Edera, although I wouldn't pretend microVMs, or Edera, are a magic pill. PwnEscape is a useful reminder of why we shouldn't treat any isolation mechanism as an absolute guarantee.

A hypervisor boundary depends on the code enforcing it and the privileged services surrounding it. Virtual network devices, block devices, I/O backends, memory mappings, and device emulation all create interfaces through which guest-controlled inputs may reach more privileged components.

For workloads with direct device access, the IOMMU configuration and DMA isolation matter too.

Reducing the attack surface exposed across that boundary, separating privileged services, minimizing device interfaces, and keeping the trusted computing base small are important parts of the design.

Even with strong isolation, an attacker can still use credentials or permissions already available inside the guest. MicroVMs address a particular class of isolation risk, but they aren't a substitute for application security, authorization, or network controls.

The right architecture depends on what you're trying to protect, whom you're protecting it from, and what the attacker can already control.

Detection and Recovery Are Part of the Architecture Too

Compromises will happen even with those restrictions, so we need to be able to identify suspicious behavior and respond without relying on perfect prevention.

Runtime monitoring can help identify unexpected process execution, unusual network activity, privilege changes, and access to sensitive resources. eBPF-based monitoring gives us useful visibility into kernel-mediated activity without modifying every application.

There are limitations here as well. An attacker can operate entirely within an already compromised process, reuse existing connections, or invoke legitimate APIs using stolen credentials. And if the host kernel itself is compromised, a monitoring agent that trusts that same kernel may no longer provide reliable evidence.

Externally collected logs, separated monitoring infrastructure, and good audit trails help address some of these limitations, although they still depend on how the collection and trust boundaries are implemented.

Recovery also needs to be part of the design. Can you isolate an affected workload without bringing down an entire cluster? Revoke its identities? Redeploy it from a known-good image? Determine which customer data may have been exposed?

Can you restore backups that the compromised workload had no permission to modify?

A patch fixes vulnerable code, but it doesn't necessarily remove persistence, undo stolen data, or invalidate credentials an attacker copied before the patch was applied. We need to account for all of that when designing our response and recovery capabilities.

Maybe It’s Time To Measure Blast Radius Alongside Patch Times

We measure mean time to detect, mean time to remediate, vulnerability counts, and patch compliance. We should continue measuring them, but I'd also like us to measure what happens when remediation doesn't arrive in time.

Instead of stopping a security review at whether a vulnerability can lead to remote code execution, follow the attack path.

If this process is compromised, which credentials can it read? Which identities can it assume? Which services can it reach? Which kernel interfaces can it invoke? Which other tenants or workloads share its failure domains?

And which boundary actually stops the attacker?

Don't assume the controls work because the YAML says they exist. Run a controlled compromise exercise with production-equivalent permissions and verify the results.

Check whether denied network paths are actually denied. Verify that service identities cannot access unrelated resources. Test whether seccomp and mandatory access control policies block the operations they're supposed to block.

For VM-based isolation, examine the interfaces exposed to guests and how host-side services handle malformed inputs. Exercise credential revocation, workload quarantine, and recovery.

A low vulnerability count is nice, but I'd find considerably more comfort in knowing that one compromised application cannot read another customer's database, reach the cluster control plane, or overwrite our backups.

And Please, Let's Remember the People Maintaining All This

One detail from the PHP Foundation's disclosure is worth spending more time on.

The Foundation described the technical difficulty of fixing these issues alongside the growing number of security reports its team has to handle. It reported publishing 15 CVEs since July 1 and investigating more than 100 reports that were invalid, duplicates, or couldn't be reproduced.

AI-assisted vulnerability research is making it easier to find bugs and generate reports. That can be enormously helpful when the reports are valid, but every report still needs triage, reproduction, root-cause analysis, and engineering judgment.

Automating bug discovery doesn't automatically create more engineers capable of safely modifying a language runtime. Someone still needs to understand the code well enough to fix the vulnerability without breaking everything else, and the number of people with that expertise is often much smaller than we'd like to believe.

The PHP Foundation has been investing in dedicated security engineers, better tooling, and improved decision-making processes. I'm glad to see that, but organizations depending on critical open source projects also have a responsibility to support them.

If your business depends on PHP, Linux, Kubernetes, or any other foundational project, contributing engineering time and funding their long-term maintenance is part of taking security seriously. These projects are doing an extraordinary amount of work on behalf of all of us, often with nowhere near the resources that commercial software teams have.

We shouldn't need a critical CVE to remind us of that.

A Little More #HugOps This Holiday (and CVE) Season

I don't want this to become another article telling engineers they should have patched faster.

The people maintaining PHP have been doing difficult work under enormous pressure. The researchers identifying these bugs are helping make the ecosystem safer. Distribution maintainers are packaging fixes, and operations teams are trying to deploy them without breaking production.

I'd much rather see us support the people doing that work than pile on another lecture about why vulnerabilities exist. And frankly, our on-call engineers deserve a holiday that doesn't involve SSH sessions from the dinner table.

We're going to keep having vulnerabilities, regardless of how much technology we throw at the problem.

Memory-safe languages can remove entire classes of memory corruption, but they can't eliminate broken authorization or compromised credentials. Containers can restrict workloads, but shared kernels remain a common dependency. MicroVMs can provide stronger isolation, but they still rely on hypervisors, virtual devices, and privileged infrastructure services.

Network controls can restrict reachability, but they can't necessarily prevent an attacker from abusing an authorized connection. Detection helps us respond, but it won't catch every malicious action. And patching remains essential, although it can only address known vulnerabilities after someone discovers them, writes the fix, and gets it into production.

We need all of these controls working together, and we need to test the boundaries between them rather than assume that deploying a particular technology somehow makes the problem disappear.

So yes, patch PHP. Patch your kernels. Update your Kubernetes tools. Fund your maintainers. Improve your vulnerability management.

While we're doing all of that, we should also be building systems that limit privileges, restrict access, isolate failures, and can recover when something inevitably goes wrong. Our security strategy cannot depend on every maintainer finding every bug and every operations team patching every system before an attacker gets there.

The holidays are approaching, and I can almost guarantee the CVEs aren't going to take time off. Maybe it's time we built infrastructure that lets our engineers do so.

Cute cartoon axolotl with a light blue segmented body, big eyes, and dark gray external gills.

You know you wanna

Let’s solve this together