PHP (Hypertext Preprocessor) is a widely used open-source server-side scripting language designed for web development. It has over the years gained widespread acceptance for its ease of use and programming/scripting abilities and is notably the scripting language that implements the popular WordPress application. WordPress is the most popular content management system (CMS) in the world powering approximately 42% of worldwide websites, and nearly 60% of websites that use a CMS.
On October 21st, 2026, the PHP project reported the following two CVEs and released a patch for the vulnerabilities. These CVEs are related in that they both involve memory use from the heap in PHP worker processes.
- CVE-2026-109618: Remote Code Execution via Out of Memory Condition (OOM)
When PHP experiences a memory exhaustion condition known as “out-of-memory (OOM)”, it uses the standard C functionlongjmp()to transfer execution into another function for a fatal error and exit. During multipart parsing of an HTTP request, if the OOM condition is triggered while the request state is partially initialized, the fatal exit and shutdown reads invalid memory, which can lead to remote code execution (RCE).
Susceptibility to this vulnerability is implementation dependent. The highly popular Apache “mod_php” for example unconditionally executes thelongjmp()in an OOM condition making it vulnerable. - CVE-2026-109619: Remote Code Execution via Stale Global Memory Pointer State
PHP worker processes retain a global state memory pointer to any freed request memory. It is possible that the global state is not properly reset in some scenarios or even that memory is repopulated after a reset. Fatal errors such as the above OOM condition will skip the pointer reset. This effectively translates into memory use-after-free condition and remote code execution becomes possible.
Background
Exploitability of these CVEs is non-trivial with some pre-conditions to be met and multiple possibilities/avenues of potential attack. In this blog, we will focus purely on CVE-2026-109618 to see if we can get a single HTTP POST exploitation vector.
We must first have an application configuration and components that allows for the upload of multipart form data. Within PHP, there was a feature released in version 5.4.0 that introduced a configuration switch called “session.upload_progress”. In the implementation, the switch defaults to being enabled. Below is a snippet from the PHP manual:

This feature processes multipart form data in a function called php_session_rfc1867_callback() in the PHP session handling API. All the HTTP POST request data in the session API is processed before any application code runs. The callback handler function:
- Creates temporary session state files with attacker controllable data.
- Runs on any PHP web application that accepts HTTP POST requests.
- No authentication is required during the session API handling of these requests.
Therefore, this callback function in the PHP code is an excellent candidate to allow a threat actor to trigger an out-of-memory condition and potentially gain remote code execution.
Lab Setup
To explore these vulnerabilities, we need a reliable lab setup. A docker container is the easiest way to get up and running with Apache mod_php. As you might imagine, Docker Hub publishes a number of images for the community already. Having said that, I learned through the process of banging my head against a brick wall repeatedly that the PHP project to their credit had recompiled PHP with the fixes and back ported into older versions of the docker containers. Bravo, but it was unexpected and frustrating for me while getting the lab up and running! The following setup information formed the basis of my lab:
- Libphp.so was compiled fresh directly from the php-8.4.26 source code with all of the same configuration switches that are commonly used in an Apache mod_php environment.
- The following PHP initialization/configuration directives were placed in
/usr/local/etc/php/conf.d/custom.inito create a stable controlled environment. The most important statements are making sure that the upload progress switch is enabled, and that the max memory size for HTTP posts is a little more constrained to help us trigger an out of memory condition sooner during upload.

- I created a basic
index.phpand placed it in the/var/www/htmlroot directory. All it did was print some version info and the value the upload_progress.enabled flag. - I disabled Address Space Layout Randomization (ASLR) in the docker host. More on this later.
- I used a local AI model to speed up the debugging and researching process. Specifically, I used Qwen3.8-27b running on an Nvidia RTX-4090 GPU.
- After finishing the vulnerable docker container setup using the reference image of php:8.4.26-apache, I used docker commit to save a vulnerable image as vuln_php.
- I had the AI model write a simple Python script called
oom_probe.pyto successively send larger and larger multipart form data HTTP POST requests until I observed an out of memory response from the PHP server. - I was then able to see stale session files lying around in
/tmp/sessionsas expected.
Initial Attack Path (Low Hanging Fruit)
There is another defect that allows for a reasonably easy initial attack path and that is the multipart form handler code in PHP that accepts the session cookie from the form data without checking the session.use_only_cookies setting. This gives us session fixation, which means that we will know the session identifier, and will directly map to the stale session file we leave on the server.
Notably the “name” parameter sent in the post request is stored directly in the temporary session state file. The session state file that is written when the OOM condition is triggered will not be deleted leaving the attacked supplied data in the “name” parameter within the file contents. Below is an example using my probing script of injecting data into this field and then looking at the file on the target docker container.

All we need from there is some form of local file inclusion in the PHP of the application and we get command injection ability by writing PHP code into the filename variable we post. For example, we could write the following text into the filename field that remains on disk in the stale session state files:

Then if any other PHP script includes this state file, we have an execution of the command. However, this path depends on a local file inclusion vulnerability in the application. I dropped a simple vulnerable PHP script onto the web server with a direct file inclusion to verify this.
Higher Complexity Attack Path
An interesting observation is that the multipart form upload code in the session handling API will potentially trigger the out of memory condition before any application code runs. More specifically, the Zend engine (zend_bailout() function in zend.c of the php source code) will be executed when the Zend HashTable structure is only partially initialized. The last line of code in zend_bailout() is the LONGJMP() macro that ultimately just executes a longjmp() to transfer program execution control into the shutdown and exit function for this request.
Since all this code is written in C, any time we are about to shut down a process, developers are obligated to cleanup memory. This is commonly referred to as a destructor function, and in the C language, context must be manually implemented.
Without getting too far in the weeds, the bottom line is that the Zend HashTable structure, when initialized for an HTTP request, contains memory pointers for object destruction functions. If we can somehow influence the value of one of these memory pointers, we will be able to control execution.
From a high level, these are the parts and sequence of the exploit:
- OOM triggered by large multipart form data HTTP POST to
index.php. - Form data contains a payload designed to pad out a forged Zend object beyond the max memory limit of the PHP process.
- The
zend_bailout()function is called, which triggers the process shutdown functions that are dealing with only a partially initialized HashTable. - The forged object type of 8 in payload sends execution control to the
zend_objects_store_del()function. zend_objects_store_del()calls the destructor function handler (a memory pointer).- This object handler is from our forged object we created in payload and sends execution to a new jump target address we call POP4.
- POP4 sets the RDI register to the command string we want to execute and pops the other registers off the stack.
- POP4 then calls libc
system()to execute an O/S command.
What POP4 represents here is a return-oriented programming (ROP) gadget we are using as a springboard to get the right things put into the right registers before our system call.
The astute reader is going to recognize that to make all this work, we need some static memory addresses. This is the point in the blog where I tell you that I did all this work in a docker container after switching Address Space Layout Randomization (ASLR) off, and hard coded the memory addresses of both LIBC, and the loaded LIBPHP object. ASLR is a great defense mechanism; however, during the course of an exploit POC development there are already complexities of understanding the exact nature of the vulnerability, in this case the memory layout of the heap, and we need to break the development steps down into manageable steps minimizing complexity at each step. Once we have achieved success with a defensive mechanism like ASLR disabled, we can move forward with further development with defenses re-enabled steadily growing the exploit capability.
ASLR Enabled Attack Path
It is possible to adapt this attack path for different vulnerable versions of PHP and for a normal implementation where ASLR is not disabled. In the case where ASLR is not disabled, we would probably modify the attack to leverage the zip_system() function, which is an implementation of the system() function inside of libphp. This optionally eliminates the additional base address requirement of libc, but is not absolutely required.
We need a method to leak address information about libphp.so when attempting to adapt for a non-ASLR attack path. This is where the second CVE-2026-109619 becomes useful. In my reading and research, along with some additional AI model assistance, I discovered that we could get a vulnerable PHP server to leak memory address information using the Set-Cookie header over HTTP.
This memory address disclosure is an over-HTTP arbitrary read made possible by stale PS(id) / PS(send_cookie) state being consumed by the upload-progress session path before RINIT. It prints attacker-directed heap/libphp memory into the PHPSESSID cookie.
To achieve success with the ASLR enabled attack path, we need to be able to pin the attack to a single PHP worker process. The reason for this is that the attack will occur in stages. The initial stage will be used to leak memory addresses in the cookie headers of the response, and then the attack stage to push the exploit into the heap. Because ASLR is enabled, we need both stages of the attack to leverage the same PHP worker process. Otherwise said, the library base memory addresses will stay consistent in the same worker process.
How can we possibly know the Apache/PHP configuration on the target server? The answer to this question is . . . that we just don’t know. Thus, in the final proof of concept attack script, we introduce a parameter called —workers, which sets the target worker process count to an arbitrary number. During the attack, we use some Python threads to keep the workers – 1 count of processes busy. This is so we can create the condition whereby our attack itself will attack only one worker for all stages. In the final developed exploit, I decided to keep using the system() function from libc. Therefore, we have to leak the base memory address for libc as well as libphp.
Here is the execution sequence flow as documented by the AI model:

And finally, what you have been waiting for, here is a screenshot of the exploit actually running with the injected command string as follows:

You will of course notice that we wrote the output to a file. This is because the output from the worker process is not reflected back in the HTTP response. We used a simple PHP script to fetch the contents, which reads as follows:

I am pretty sure you can use your imagination on how to take this further. Anyway, without delaying the anticipation any longer, here is the screenshot output of a running exploit against an Apache/PHP server using 10 worker processes in its configuration.

Thoughts on AI Assistance
When developing these attack paths using the assistance of an AI model, I leveraged OpenCode as the agentic harness, a full copy of the php-8.4.26 source code, a recompiled copy of libphp.so from this source that was vulnerable as well as some example exploitation code from “CookieEngineer” that followed a similar path.
You might be thinking that I prompted the AI model once saying something like “go find a vulnerability based on this CVE,” but you would be incorrect. To get good and expedient results from the local AI model, I examined the vulnerability carefully, read lots of source code, and got very specific with a lot of my prompting. In other words, I leveraged the AI model as my intern to accelerate development paths while the real directed thinking and additional knowledge was coming from myself and my research. More to the point, I stepped the AI model through this in small discrete steps for better quality results, and for my own personal understanding.
References: