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 | Memory disclosure |
| CVE-2026-109619 | Remote code execution |
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
Every vulnerability has an underlying condition that makes exploitation possible. In this case, that condition involves a feature called session.upload_progress.
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 vulnerable 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 a fatal error.
Normally, software should clean up its resources properly when an operation finishes.
However, an emergency bailout can interrupt that cleanup process.
This is where PHP2Shell becomes interesting.
Consider a situation where PHP frees a memory block but doesn't properly clear the pointer referencing it before the bailout occurs.
That pointer can remain behind even though the memory it references is no longer valid.
This is known as a dangling pointer.
In this case, the vulnerability is associated with CWE-824.
Why is a dangling pointer 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.
But PHP2Shell has another interesting component.
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 memory state left behind by one request can potentially affect a subsequent request handled by the same worker.
So, the issue isn't simply that PHP encounters an error.
The problem is that the memory state can survive long enough to influence what happens next.
And this is what makes the exploitation chain possible.
4. Understanding the two vulnerabilities
Now that we understand the memory-management problem, let's look at how the two CVEs fit together.
CVE-2026-109618: Memory Disclosure
The first vulnerability allows an attacker to retrieve information from the PHP worker's memory.
The exploitation involves manipulating the stale session-related pointer and the length of the string PHP subsequently processes.
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.
The memory disclosure vulnerability helps reveal those addresses.
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.
CVE-2026-109619: Remote Code Execution
This is where things get considerably more serious.
The second vulnerability takes the memory manipulation further.
The 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 researchers demonstrated how manipulating this structure could 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 corruption can ultimately become 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.
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 vulnerability 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 mitigation
If immediate patching isn't possible, disabling the upload-progress functionality can reduce exposure to the relevant attack path:
session.upload_progress.enabled = 0
However, this should be treated as a temporary mitigation rather than a permanent solution.
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.
Top comments (2)
Thanks for working through the lab. One small clarification so readers aren't misled: as we understand the advisories, both CVE-2026-109618 and CVE-2026-109619 are critical RCE bugs in PHP core (CVSS v4 9.2 each), and neither is only a memory disclosure. 109618 is the out-of-memory case where a longjmp leaves request memory partially initialised, for example during multipart parsing before authentication, and 109619 is a use-after-free from stale global state across requests after a fatal error. The fix is updating to 8.2.35, 8.3.36, 8.4.27 or 8.5.12 or later, since proxy body limits are only a stopgap. After updating, restart PHP-FPM or reload Apache and replace containers.
Thank you for taking the time to clarify this! I really appreciate the detailed technical correction.
You're right that my explanation oversimplified the vulnerabilities, particularly by presenting CVE-2026-109618 primarily as a memory disclosure issue.
I understand the distinction better now: CVE-2026-109618 is a critical RCE vulnerability caused by out-of-memory handling interrupting PHP while request data is only partially initialized. This can happen during request processing, including multipart parsing before authentication. CVE-2026-109619 is another critical RCE vulnerability involving stale request-owned global state and a use-after-free condition, where a subsequent request handled by the same worker may access memory that has already been freed.
Both vulnerabilities have a CVSS v4.0 score of 9.2 and can allow unauthenticated remote code execution.
I'll update the article to reflect these mechanisms accurately and include the patched PHP versions (8.2.35, 8.3.36, 8.4.27 and 8.5.12 or later). I'll also clarify that proxy body-size limits are only a temporary mitigation, not a replacement for patching, and that deployments should restart PHP-FPM workers or reload Apache as appropriate and replace affected containers after updating.
Thanks again for pointing this out. I genuinely appreciate feedbacks like this because it helps me improve both my technical understanding and the accuracy of what I publish.