Exploring CVE-2026-109618 and CVE-2026-109619 through a TryHackMe lab.
Introduction
October is Cybersecurity Awareness Month, and honestly, this month has been quite interesting from a cybersecurity perspective. There have been plenty of vulnerabilities, attacks and security incidents worth paying attention to.
But while going through TryHackMe, something particularly caught my attention: PHP2Shell.
At first, I was interested in the upload exploitation technique. I mean, how does something as ordinary as a file upload eventually lead to remote code execution?
That question made me curious enough to investigate further.
On October 21, 2026, two critical vulnerabilities were disclosed in PHP: CVE-2026-109618 and CVE-2026-109619. Together, they demonstrated how an unauthenticated attacker could potentially execute operating system commands on a vulnerable PHP server through specially crafted HTTP requests.
And here's the part I found most interesting:
The application itself didn't even need to be vulnerable.
The weakness existed inside PHP's runtime, meaning that even an application with perfectly ordinary PHP code could potentially be affected.
So, I decided to explore the vulnerability through a TryHackMe lab and understand what was actually happening behind the scenes.
Let's break it down.
1. First, what exactly is PHP2Shell?
PHP2Shell is the name given to a pair of vulnerabilities affecting the PHP runtime.
| CVE | Description |
| CVE-2026-109618 | Remote code execution via out-of-memory (OOM) handling and request-state corruption |
| CVE-2026-109619 | Remote code execution via stale global state and use-after-free |
Both vulnerabilities received a CVSS v4.0 score of 9.2 (Critical).
The vulnerabilities involve PHP's internal request-processing and memory-management mechanisms.
What makes this particularly concerning is that the vulnerable code isn't necessarily part of the application developers write. It exists deeper inside the PHP engine.
So, updating your WordPress installation or fixing your Laravel application wouldn't automatically resolve the underlying problem.
To understand why, I first needed to understand the PHP feature involved in the exploitation.
2. The interesting part: PHP Upload Progress
The TryHackMe exploitation scenario explores PHP's session.upload_progress functionality as part of the attack path.
PHP provides this feature to track file uploads.
Imagine uploading a large file through a website. You might see a progress indicator showing how much of the file has been uploaded.
PHP can help provide this information by tracking details such as:
- Uploaded file name.
- Total upload size.
- Amount of data received.
- Current upload progress.
The relevant configuration directive is:
session.upload_progress.enabled = On
And the default form field responsible for activating this functionality is:
PHP_SESSION_UPLOAD_PROGRESS
Now, here's where things get interesting.
PHP processes this upload-progress information while parsing an incoming multipart/form-data request.
That happens before the application script actually starts executing.
In simpler terms, PHP is already processing the upload before the website's own PHP code gets control.
This means the relevant request-processing functionality can be reached without the target page necessarily implementing its own file-upload feature.
I found this particularly interesting because it changes how we think about the attack surface.
We normally think about vulnerabilities in terms of application code, authentication systems and user input validation.
3. Understanding the actual memory-management bug
This was probably the part that required the most attention from me.
To understand PHP2Shell, I needed to understand what happens when PHP runs out of memory.
PHP has a configuration directive called memory_limit, which restricts how much memory a request can consume.
When a request exceeds that limit, PHP can trigger something called a bailout.
Internally, the engine uses longjmp() to escape the current execution path after certain fatal errors.
Normally, software should clean up its resources properly when an operation finishes.
However, an emergency bailout can interrupt normal request processing and leave internal state in an incomplete condition.
This is where PHP2Shell becomes interesting.
CVE-2026-109618: Out-of-memory handling
In this vulnerability, an out-of-memory condition can interrupt PHP while request data is only partially initialized.
The bailout can leave PHP's internal request state inconsistent. When subsequent cleanup operations process that incomplete state, memory corruption can occur, potentially leading to remote code execution.
CVE-2026-109619: Stale global state and use-after-free
The second vulnerability involves a different memory-management problem.
Request-owned memory can be freed while stale global state continues to reference it.
If a later request is handled by the same worker before that state is properly reinitialized, PHP may access memory that is no longer valid.
This creates a use-after-free condition.
Why is a use-after-free dangerous?
Think of memory as a collection of storage locations.
A program allocates a location, stores information there and keeps a pointer to access it later.
Now imagine that the location gets freed, but the program still has the old pointer.
Another allocation might eventually reuse that same memory location.
The original pointer now references something different from what it was supposed to reference.
That's the fundamental problem behind a use-after-free scenario.
The Apache worker connection
The lab uses Apache with mod_php in a prefork configuration.
In this setup, an Apache worker can process multiple requests during its lifetime.
That means internal state left behind by one request can potentially affect a subsequent request handled by the same worker.
This is particularly relevant when understanding the stale-state issue in CVE-2026-109619.
So, the issue isn't simply that PHP encounters an error.
The problem is that internal memory and request state can remain inconsistent or stale in ways that influence subsequent processing.
And this is what makes the exploitation chain possible.
4. Understanding the two vulnerabilities
Now that we understand the different memory-management problems, let's look at how the two CVEs fit into the bigger picture.
CVE-2026-109618: RCE via Out-of-Memory Corruption
This vulnerability involves PHP's handling of an out-of-memory condition during request processing.
An OOM-related bailout can interrupt PHP while internal request data is only partially initialized.
Subsequent cleanup may then operate on invalid or incomplete state, potentially resulting in memory corruption and remote code execution.
CVE-2026-109619: RCE via Stale Global State
The second vulnerability involves stale internal state that survives after the memory backing it has been freed.
When a later request is handled by the same worker, PHP may access that stale state before it has been properly reinitialized.
This creates a use-after-free condition that can ultimately be exploited to achieve remote code execution.
Where the lab's memory-disclosure stage fits
In the TryHackMe exploitation walkthrough, the exploit first obtains information about the PHP worker's memory layout.
The interesting part is where this information gets returned.
PHP uses the following HTTP response header:
Set-Cookie: PHPSESSID
The resulting oversized session identifier can expose adjacent process memory.
That memory may contain useful information, including heap and library addresses.
Why does that matter?
Because modern operating systems use a security mechanism called Address Space Layout Randomization (ASLR).
ASLR randomizes memory locations to make exploitation more difficult.
An attacker generally needs to know where important functions and memory regions are located to reliably manipulate execution.
In the TryHackMe lab, the exploitation chain obtains two important addresses:
- The PHP heap base address.
- The libphp base address.
This information becomes useful in the next stage.
The important distinction is that this memory-disclosure stage is part of the exploitation chain demonstrated in the lab. It should not be confused with the primary vulnerability classification of CVE-2026-109618, which is remote code execution.
The HashTable and pDestructor technique
This is where things get considerably more interesting.
The lab's exploitation chain uses a forged internal PHP structure called a HashTable.
One of the important fields involved is:
pDestructor
This field is associated with the function PHP calls when destroying certain values.
The demonstrated exploitation technique manipulates this structure to redirect the cleanup process toward the system() function inside PHP's library.
Instead of executing the expected cleanup operation, PHP ends up invoking a function capable of executing operating-system commands.
And that's how the memory manipulation in the demonstrated chain can ultimately result in remote code execution.
What I found particularly interesting is that the normal cleanup process itself becomes part of the exploitation chain.
Something designed to manage resources properly can become dangerous when the underlying memory state has been corrupted.
It's worth remembering that these HashTable and pDestructor details describe the exploitation technique demonstrated in the lab, rather than a universal explanation of the root cause of both CVEs.
5. My practical investigation on TryHackMe
After understanding the concepts, I moved on to the practical investigation.
The TryHackMe room provided a vulnerable PHP server running inside a Docker container.
I connected to the machine using SSH with the provided credentials.
The first thing I wanted to do was identify the running container and check its PHP version.
I used:
docker ps --format '{{.Names}} {{.Image}} {{.Status}}'
The output showed:
php2shell-target php:8.4.26-apache Up
Next, I checked the PHP version inside the container:
docker exec php2shell-target php -v
The target was running:
PHP 8.4.26
Zend Engine v4.4.26
This was important because PHP 8.4.27 was the fixed release.
So, the lab environment was running a vulnerable version.
Inspecting the application
Next, I wanted to see what the actual website was returning.
I executed:
curl -s http://127.0.0.1:8088/index.php
The response?
OK
That's it.
I also inspected the PHP file:
docker exec php2shell-target cat /var/www/html/index.php
And found:
<?php echo "OK\n"; ?>
At this point, I found the situation particularly interesting.
There was no login system.
There was no database.
There was no application-level file-upload functionality.
The application was literally printing OK.
Yet the underlying PHP runtime was vulnerable.
This reinforced one of the biggest lessons from the room: an application doesn't necessarily need to contain vulnerable code for its underlying server to be exploitable.
6. Observing the memory disclosure
After confirming the target environment, I moved to the memory-disclosure stage.
The lab provided an existing exploit repository:
php2shell-bypass-aslr-exp
I navigated into the directory:
cd ~/php2shell-bypass-aslr-exp
The provided exploit handled the exploitation stages.
I ran it against the local target:
python3 exploit.py -u http://127.0.0.1:8088/index.php
The output showed two particularly interesting stages:
`[*] stage 1: heap leak
[+] chunk base = 0x...
[*] stage 2: libphp leak
[+] libphp base = 0x...`
These were the two memory addresses the exploitation chain needed.
The first revealed the heap location, while the second revealed the PHP library's base address.
I found this interesting because ASLR is specifically designed to make these locations unpredictable.
Instead of simply guessing the addresses, the exploitation chain obtains information about the actual memory layout.
This demonstrated how a memory disclosure stage can become a stepping stone toward something much more serious.
7. From memory disclosure to remote code execution
Now came the part I was particularly interested in: actually demonstrating command execution.
After the memory disclosure stages, the exploit proceeded to the final stage involving the manipulated HashTable structure.
The lab provided the following execution proof:
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)
And there it was.
The command had executed successfully under the www-data account, which is commonly used by web servers on Linux systems.
This confirmed that the exploitation chain had achieved remote code execution in the lab environment.
The next step was retrieving the flag.
The lab's exploit used the following command:
python3 exploit.py \
-u http://127.0.0.1:8088/index.php \
-c 'cat /f>/out/p1' \
--proof-file out/p1
The result returned the flag:
THM{b4ilout_l3aves_th3_h3ap_dang1ing}
THM{b4ilout_l3aves_th3_h3ap_dang1ing}
A pretty fitting flag for a vulnerability involving a bailout and a dangling pointer.
What made this practical exercise memorable for me was the fact that the target application itself never changed.
It continued to be a simple PHP page returning OK.
The exploitation happened because of the vulnerable runtime underneath it.
8. Detection and mitigation
Understanding how an attack works is one thing.
Knowing how to prevent or detect it is equally important.
Since PHP2Shell affects the PHP runtime, updating the application alone isn't enough.
The PHP installation itself needs to be patched.
The fixed releases identified in the security advisory are:
| PHP branch | Fixed release |
| PHP 8.5 | 8.5.12 |
| PHP 8.4 | 8.4.27 |
| PHP 8.3 | 8.3.36 |
| PHP 8.2 | 8.2.35 |
Temporary risk reduction
If immediate patching isn't possible, administrators can review their PHP request-handling configuration and apply temporary defensive measures, such as restricting request-body sizes at the reverse proxy or web-server level.
Disabling session.upload_progress may also reduce exposure to the specific upload-progress attack path demonstrated in the lab:
session.upload_progress.enabled = 0
However, these measures are only temporary risk-reduction steps. They do not fix the underlying vulnerabilities and should not be considered complete mitigations.
The only safe solution is to update PHP to a fixed release.
After updating, restart PHP-FPM workers or reload/restart Apache when using mod_php.
If PHP runs inside containers, rebuild or replace the affected containers so that no vulnerable runtime instances remain.
What should defenders monitor?
Some suspicious indicators worth investigating include:
- Unusually large PHPSESSID values in Set-Cookie response headers.
- Suspicious multipart POST requests.
- Unexpected PHP worker crashes.
- Memory exhaustion errors.
- PHP processes unexpectedly spawning shell processes.
- Unusual command execution under the web server account.
One important lesson here is that monitoring only the application layer isn't sufficient.
Because the vulnerability is triggered during PHP's internal request processing, detection and prevention need to account for activity happening before the application script executes.
Patching the runtime remains the priority.
9. What I learned from this investigation
This was more than just another TryHackMe room for me.
It helped me connect several concepts that I had previously encountered separately.
Some of the things I explored included:
- How PHP processes multipart/form-data requests.
- What session.upload_progress actually does.
- How memory allocation and dangling pointers work.
- Why an out-of-memory bailout can become dangerous.
- What ASLR does and why attackers try to bypass it.
- How memory disclosure can assist remote code execution.
- How PHP's internal cleanup mechanisms can become part of an exploitation chain.
- Why securing the application doesn't necessarily secure the runtime.
But there's one thing that stood out above everything else.
Sometimes, the vulnerability isn't in the code you wrote. It's in the code your code depends on.
As someone learning cybersecurity alongside software development and AI/ML, I find these investigations particularly valuable.
I don't want to simply learn which command executes an exploit or how to retrieve a flag.
I want to understand what happens underneath, why a vulnerability exists, how it can be abused and, most importantly, how defenders can prevent it.
Because there's a difference between knowing that something works and understanding why it works.
And I'm genuinely enjoying the process of learning that difference.
Final thoughts
PHP2Shell was a great reminder that cybersecurity isn't just about finding vulnerabilities in websites.
It's also about understanding the technologies, runtimes, libraries and systems that make those websites work in the first place.
A seemingly harmless upload-progress feature, combined with a memory-management failure, demonstrated how something deep inside a runtime could eventually lead to remote code execution.
And that's what makes cybersecurity so interesting to me.
There's always another layer to investigate, another question to ask and another mechanism to understand.
Stay curious. Keep learning. Keep breaking things in controlled environments, and most importantly, understand what you're doing.
Disclaimer: This investigation was conducted in an authorized TryHackMe lab environment. The exploit and command-execution results described above relate to that controlled environment.