Skip to main content
← All posts
securityYotam Cohen

Black October 2026: How Rival's Agents Triaged PHP2Shell

PHP2Shell was one of several critical, pre-auth bugs disclosed on the same day. Here is how one exploitation path works, and how our agents found and mitigated customer exposure before patching was complete.


Black October 2026: the context

“As bad as things have gotten in the past 4 days, they are going to get a lot worse before they get better.”

— Jason D. Clinton, Deputy CISO @ Anthropic, on X

PHP2Shell did not arrive in isolation. A series of vulnerabilities and attacks disclosed on the same day forced security teams to triage multiple critical issues at once, most notably:

  • PHP2Shell — unauthenticated RCE and memory disclosure in core PHP.
  • LiteLLM supply-chain compromise — a malicious 1.107.0 package pushed to PyPI and Docker carrying a credential stealer (browser, cloud, and AI secrets) plus a dormant C2 capability.
  • Node2Shell — unauthenticated RCE, crash and memory disclosure in Node.js’ Brotli decoder.
  • CVE-2026-98432 — an unauthenticated RCE vulnerability in the Linux kernel IPv4 networking stack.

Shortly after mitigations for PHP2Shell started circulating online, the PHP Foundation said that the only complete fix is the patch, and that configuration mitigations reduce but do not fully eliminate exposure.

This case study explains precisely how one PHP2Shell exploitation path works, then describes how we helped customers find their exposure and mitigate it before patching was complete.

How to exploit PHP2Shell’s CVE-2026-109618

Both PHP2Shell bugs are caused by a single weakness: stale pieces of internal state left behind when the memory area allocated for a request (called a “memory arena”) is destroyed and freed mid-run. Three facts about how PHP runs combine to make that possible:

  1. Workers are long-lived and reused, meaning global state may persist between requests.
  2. Each request gets its own memory arena, which is freed after handling the request.
  3. Some shared global pointers point into requests’ memory arenas, and aren’t cleaned up properly when some fatal error types are thrown.

The exploit relies on three main primitives: an OOM error that frees the memory, a use-after-free trigger, and attacker-crafted values in that memory.

Triggering an OOM error

Under normal conditions, PHP has a mechanism designed to prevent OOM errors — a maximum request size limit. However, there are ways to “amplify” a request using some tricks:

  • A chunked multipart request with no Content-Length header slips past the post_max_size check
  • A part can have an unlimited number of headers
  • When sending multipart form data, sending a file with filename=”” doesn't count towards the max_file_uploads limit, but PHP will still build the $_FILES array for the file

The result: a moderately sized request that passes the filters can inflate to a massive size when handled, triggering an OOM error.

Using the stale pointer

Now that an OOM error has been triggered, some global pointers haven’t been cleaned up, including the session ID and the $_SESSION array. The next step is to make sure another request uses the stale pointers before they’re reinitialized.

One feature that does this is the Session Upload Progress feature, which allows frontends to display progress bars during uploads by having the server write progress data into the session. When this feature is used, the session variables are initialized using the stale pointers from the previous request!

A simple memory disclosure can now be crafted: by putting a string structure with a large length in the session ID’s address, the PHPSESSID cookie returned in the next response can now disclose large amounts of memory!

This memory disclosure can then be used to enable remote code execution. See the Appendix for details.

Injecting into the same spot

In order to make the previous two primitives useful, attacker-controlled data needs to end up in the same spot in memory that the session pointers point to. PHP’s Zend Memory Manager makes this relatively simple: allocation sizes are determined by field length, and, for efficiency, a new allocation usually ends up in the slot most recently freed by an allocation of the same size. An attacker can craft field lengths so that values end up in the same spot in memory.

Why it was so severe

  • Reach. ~70% of websites run PHP, and the flaw is in the engine, so application-level security does not help.
  • Pre-authentication. No credentials, no user interaction; in certain PHP configurations, application code isn’t even reached during exploitation.
  • Near-zero reaction time for defenders. CVE IDs leaked ahead of the planned date, PoCs and a demo video circulated early, exploitation attempts were observed in the wild before public disclosure, and the patch arrived 17 hours after the leak.

The bug was tough; AI made it easy

The vulnerable code dates back more than a decade and the bug class was not unknown: similar vulnerabilities in PHP have been found in the past. What changed in 2026 was the difficulty of work required for exploitation. Frontier AI models made the hard part much simpler: chaining primitives and making adjustments for the exploit across PHP versions, configurations, and memory layouts went from taking months of manual research to days.

What Rival's agents actually did

Rival’s agents are equipped with the ability to autonomously use services and platforms connected by the user and combine them with information stored in the Rival Security Knowledge Graph, which contains information about infrastructure, vulnerabilities, and security findings. Agents run inside a sandbox, either on Rival infrastructure or self-hosted by the customer, where they can analyze enterprise-scale customer environments. Once the analysis is complete, they can then act accordingly — autonomously messaging relevant employees, opening pull requests to fix vulnerabilities, suggesting WAF rules, applying configurations, as well as producing detailed vulnerability reports, to help keep the organization secure.

A grid of Rival agent cards: Threat Exposure Analysis, Red Team Simulation, App Inventory, Application Inventory, Attack Surface Review, CVE Triage, SAST Fix Pipeline, and Secret Leak Response.
A selection of the agents Rival ran during PHP2Shell triage.

Hours before the CVEs could be detected using vulnerability scanners, our AI agents were already hard at work triaging PHP2Shell in customer organizations. For one customer, a triaging agent found 60 repositories containing PHP, out of which 29 were potentially vulnerable. A further check found over 30 additional hosts and workloads containing PHP code. Other customers also had multiple repos containing PHP, including production systems and a repository containing legacy code that was still deployed in production.

Agents then prioritized which services to fix based on reachability. Public, internet-exposed workloads with no authentication came first, followed by IP-restricted public services, internal services that are partly internet-reachable, services with no public IP, and lastly images and code without any running instances.

Once a list of priorities had been established, agents worked on remediation and helped customers verify that early mitigations, and later patches, landed in code.

Overall, more than 80,000 repositories were scanned in mere hours by Rival agents, saving time for defenders and keeping organizations safe.

References

Appendix — PHP2Shell RCE

In order to turn the vulnerability into an RCE, instead of overwriting the session ID, an attacker can overwrite the $_SESSION global array instead of the session ID. They create a custom HashTable whose destructor pointer points to a command execution function (for example, zif_system()), and when the request gets destroyed at the end of its lifecycle, the destructor executes a command.

However, there’s a catch — modern systems have features like Address Space Layout Randomization (ASLR), which randomizes the memory addresses programs use in each run, meaning it’s difficult to know where exactly our target function resides in memory. To defeat ASLR, knowing the memory address of any function in a library gives us an indication of where the library resides, and allows us to calculate the offset where the desired function is. To do that here, we can disclose parts of memory using the session ID, and when we find a pointer to a function (for example, a destructor pointer of a HashTable pointing to a function in libphp), we can calculate where zif_system() is relative to that function in libphp, and craft the RCE payload. This offset can change between PHP versions and compilations, which would make each attack tedious to tune, if not for AI agents.