October 24, 2026
PHP2Shell: Understanding the Memory Corruption Behind CVE-2026–109618 and CVE-2026–109619
PHP2Shell is a vulnerability chain in PHP where an out-of-memory bailout can leave behind corrupted request state. Under the right server…
By Abhishek Adhikari
4 min read
PHP2Shell is a vulnerability chain in PHP where an out-of-memory bailout can leave behind corrupted request state. Under the right server configuration, that stale state can survive into a subsequent request, creating primitives for memory disclosure and eventually remote code execution.
PHP's session.upload_progress feature records an upload's name, size, and received bytes in $_SESSION while parsing a multipart POST request. Enabled by default, it activates when the form includes the configured progress field (default: PHP_SESSION_UPLOAD_PROGRESS), allowing another request to read the status.
1. Foothold
The key point is that PHP processes upload-progress updates while parsing a POST request before application code runs. As a result, the behavior may be reachable without logging in or calling session_start(), wherever the relevant PHP feature is enabled.
The upload_progress feature itself is not a bug, it is merely the trigger point but the actual bug is the stale state surviving in a PHP bailout( one word that php gave to abort a request that hits a fatal error such as running out of memory) . I will explain this below.
2. So what is PHP bailout ?
There is a limit to the amount of memory each single request can use, in php it is in memory_limit setting. When a request tries to exceed this limit, PHP cannot simply carry on and simply performs what the engine calls a bailout: it uses a longjump() to jump back to a safe recovery point leaving whatever it was doing. Normal execution would be like allocate —> use —> free —>clear pointer — > continue/return but bailout skips all of this and act as a emergency exit , not an orderly shutdown.
An out-of-memory bailout during a narrow cleanup window can leave a pointer referencing memory that has already been freed, creating a dangling-pointer flaw. This is a classic dangling pointer, classified as CWE-824: Access of Uninitialised Pointer.
4. The Dangling Pointer
Before free:
pointer —> [allocated memory]
After free:
pointer —> [freed memory] ←no longer valid
So, a dangling pointer in a request that has died would be harmless, because the request is over but what makes it dangerous is the server model, in apache's prefork configuration , a single worker process serves one request after another without restarting between them. The stale pointer therefore doesn't vanish and stays in the worker's memory, and the next request to land on that same worker finds the engine reaching through a pointer it never reset. An attacker who controls what gets allocated into that freed space in the second request controls what the pointer now points at.
5. So how does attacker exploit this ? The attack flow:
Before understand the attack flow , we have to understand something called Address Space Layout Randomization (ASLR). An attacker who wants to redirect execution has to know where things actually are in memory, and ASLR is meant to deny that knowledge. Basically, it randomizes the addresses in the memory allocation in each run. The first CVE-2026-109618 removes it, by turning the dangling pointer into a leak of the worker's own memory. Now, let us say the stale pointer points to the PHP session ID string, the bailout happens the memory is freed but the pointer remains. Now the same memory is allocated when the attacker requests. The attacker tries to arrange for the old memory location to contain a different string/object. so for example if stale pointer —> ['abc132'] now it points to attacker influenced string stale pointer —> [attacker-influenced-string]. Conceptually:
Actual:
[length = 10] [data = "ABCDEFGHIJ"]
Corrupted:
[length = HUGE] [data = "ABCDEFGHIJ........memory........"]
here, PHP trusts the corrupted data and then return attacker with the leaks. Why PHP does this ? Well because when the server receives the huge length inputs it thinks that the actual session is long. Because PHP thinks the session ID is much larger than it really is, it reads beyond the legitimate string. The attacker therefore receives bytes that were never supposed to be part of the cookie.
These leaks can give attacker knowledge about the process's memory layout ( where heap base lives, and the libphp binary by which the attacker knows where PHP's executable code and relevant internal structures are located in this particular worker process).
6. From Leak to the shell ( CVE-2026-109619)
So when a PHP request finishes, PHP doesn't just abandon everything. It cleans up the values it created. Some internal PHP structures contain a function pointer telling PHP what cleanup function to call. The attacker already have a dangling reference points to memory that was freed after a bailout. The exploit arranges for that memory to be reused by a forged HashTable, then sets its pDestructor (php uses this to clean) function pointer to system() and supplies command bytes as the argument. Here, forging pDestructor simply means putting the desired address into the bytes that PHP will interpret as the pDestructor field.
When PHP later cleans up the value, it follows the pointer and runs the command with the web server process's privileges. In short: the leak supplies reliable addresses, and the corrupted destructor pointer turns that knowledge into code execution.
The attacker knows the layout of the internal HashTable structure and uses the memory-corruption primitive to forge a HashTable in reused memory. They place the leaked address of system() in the memory location corresponding to the pDestructor field. When PHP later destroys the forged structure, its normal cleanup logic follows that function pointer and ends up executing system()
PHP cleanup ↓ call pDestructor ↓ CPU jumps to address stored in pDestructor ↓ that address happens to be system() ↓ system(argument) ↓ OS executes command
So the fix ? We can think about disabling the session progress feature but..
Disabling session.upload_progress.enabled blocks the demonstrated session upload-progress exploitation path, but it is only a temporary and partial mitigation.
The primary fix for PHP2Shell is to upgrade PHP to a patched release (8.5.12, 8.4.27, 8.3.36, or 8.2.35). If patching cannot be performed immediately, PHP recommends disabling session upload progress, removing the attacker-controllable memory_limit failure condition, restricting request and upload sizes, disabling fatal_error_backtraces on PHP 8.5, and recycling PHP workers. These measures reduce specific attack paths but do not replace the security update.
"Disclaimer: The learning contents are derived from Tryhackme (all the pictures in used in this article is owned by tryhackme) and the author used ai bots and tryhackme contents to learn about the vulnerability. This article reflects his understanding of the vulnerability."