What is PHP2Shell? The PHP Vulnerability Explained Simply

Authors
What is PHP2Shell? The PHP Vulnerability Explained Simply

PHP2Shell is the name of two critical security bugs found inside PHP itself, which let an attacker take full control of a website's server just by sending it a few web requests, without any username or password.

In this blog, we will learn about PHP2Shell. We will also see what remote code execution means, how a PHP server handles a request and cleans up after it, what a use-after-free bug is, how PHP2Shell turns an "out of memory" error into a complete server takeover, how it was found and fixed, and what we must do to stay safe.

We will cover the following:

  • What is remote code execution?
  • What is PHP2Shell?
  • How a PHP server handles a request
  • What is a use-after-free bug?
  • How PHP2Shell works step by step
  • The two bugs inside PHP2Shell
  • How PHP2Shell was found
  • How PHP fixed it
  • What we must do now
  • Lessons from PHP2Shell

I am Amit Shekhar, Founder @ Outcome School, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.

I teach AI and Machine Learning at Outcome School.

Let's get started.

What is remote code execution?

Remote code execution (RCE) means an attacker sitting anywhere in the world can run their own commands on our server.

Means, the attacker does not need to touch our machine. They send something over the internet, and our server starts following their orders.

This is one of the most dangerous kinds of vulnerability. Once the attacker can run their own code, they can read our database, steal user passwords, change our website, install hidden programs to come back later, and etc.

The name PHP2Shell comes from this. A shell is a place where we type commands to control a computer. So, PHP2Shell means "from PHP to a shell". The attacker goes from a normal web request to a shell on our server.

What is PHP2Shell?

PHP2Shell is the nickname given to two critical vulnerabilities in the core of PHP, tracked as CVE-2026-109618 and CVE-2026-109619, which allow remote code execution without any login.

Here are the key facts:

  • Severity: both have a CVSS score of 9.2 out of 10, which means critical.
  • Login needed: none. The attacker does not need an account on the website.
  • Where the bug lives: inside PHP itself, not inside a plugin or a theme. So, even a correctly written website is affected.
  • Who is affected: PHP servers that handle web requests, in all the common setups, such as PHP-FPM and Apache with mod_php.
  • Reach: as per security researchers, PHP powers somewhere between 70% and 80% of all websites, including sites built on WordPress, Drupal, and Joomla.
  • Proof: the researchers took full control of the latest versions of WordPress, Drupal, and Joomla.
  • Versions: as per the researchers, PHP versions going back to PHP 5.4 are affected by at least one of the two bugs.
  • Fixed on: October 21, 2026.

Now, the question is: how can a programming language have a bug that lets anyone take over the server? In order to understand that, we must first understand how a PHP server handles a request.

How a PHP server handles a request

Before jumping into the bug, we need to see what happens when we open a PHP website.

A PHP server keeps a few helpers ready all the time. These helpers are called workers. Each worker is a small program that waits for a request, handles it, and then waits for the next one.

Let's say a restaurant has a few waiters. A customer comes in, one waiter takes the order, serves the food, and after the customer leaves, the same waiter cleans the table and serves the next customer. Workers are like these waiters. The same worker handles many requests, one after another.

To handle a request, a worker needs memory to keep things like the uploaded file, the form data, and the page it is building. There are two kinds of memory here. Request memory is used only for one request and is thrown away after it. Long-living memory stays with the worker across many requests. Think of the waiter's notebook. The notes for one customer's order are torn off after that customer leaves, but the notebook itself stays with the waiter all day.

So, every request goes through three stages:

Step 1: The worker takes some memory for this request.

Step 2: The worker uses that memory to do the work and build the response.

Step 3: The worker cleans up, gives the memory back, and gets ready for the next request.

Step 3 is very important. Cleaning up means two things. First, the memory is returned so it can be reused. Second, every note in the long-living memory that points to the thrown-away request memory is erased, so that nobody uses it again by mistake.

There is one more thing to notice. PHP has a safety limit on how much memory one request can use. This limit is set by a setting called memory_limit. If a request asks for more than this, PHP stops it immediately with an "out of memory" error. This protects the server from one heavy request using all the memory.

PHP2Shell begins at this "out of memory" stop.

Planning your move into AI?

No matter which tech domain you work in today, with your effort, you can transition to these roles:

  • AI Engineer
  • Forward Deployed Engineer
  • AI Agent Engineer
  • Harness Engineer
  • Voice AI Engineer
  • AI Architect
  • LLM Engineer
  • LLM Inference Engineer
  • Gen AI Engineer
  • MLOps Engineer
  • ML Engineer
  • Deep Learning Engineer

Our AI and Machine Learning Program at Outcome School takes you there.

What is a use-after-free bug?

A use-after-free bug happens when a program gives back a piece of memory, but still keeps a note pointing to it, and later uses that note as if the memory still belongs to it.

In programming, free means giving the memory back. So, use-after-free means using the memory after we have given it back.

The best way to learn this is by taking an example.

Suppose we stay in a hotel, in room 305. We get a key card for room 305. We check out in the morning, and the hotel gives room 305 to a new guest. But there is a mistake. The hotel forgot to deactivate our old key card.

Now, the following problems arise:

  • Our old card still opens room 305.
  • Room 305 now has someone else's bags inside it.
  • If we walk in, we will treat their bags as if they are ours.

In programs, the "key card" is called a pointer. It is the same "note" we talked about in the cleanup step. A pointer is just a note that says "the data is kept at this place in memory". When the program frees the memory but forgets to erase the pointer, we call it a dangling pointer.

Now, imagine an attacker who knows about this mistake. The attacker books room 305 right after we leave and fills it with fake items that look exactly like our items. When we come back with our old card, we pick up the fake items and use them, believing they are ours.

This is exactly how a use-after-free attack works. The attacker fills the freed memory with their own data, and the program reads that data as if it were its own trusted data. If that fake data says "run this next", the program runs the attacker's code.

We can see the idea in a small piece of code written in the C language as below:

char *note = malloc(64);   // take some memory, keep a pointer to it
strcpy(note, "hello");     // use it
free(note);                // give the memory back
                           // BUG: we forgot to set note = NULL

char *other = malloc(64);  // someone else gets the same memory
strcpy(other, "attacker data");

printf("%s", note);        // we use our old pointer

Here, malloc takes some memory, strcpy writes text into it, and free gives the memory back. We have taken memory and saved its address in note. Then, we freed the memory but did not erase note. After that, other got the same memory and wrote "attacker data" into it. Finally, when we print note, it can print "attacker data", because our old pointer is now looking at someone else's data. The program does not show any error. It simply trusts the old pointer.

This is a simplified example just for the sake of understanding. PHP itself is written in the C language, and PHP2Shell is a use-after-free of this same kind inside PHP.

Now that we have learned about workers, memory, cleanup, and use-after-free, it's time to put everything together.

How PHP2Shell works step by step

Step 1: The attacker sends a special request to the website, for example a file upload, that makes PHP use a lot of memory. The attacker designs it so that it crosses the memory_limit at a very specific moment.

Step 2: PHP sees that the request crossed the memory limit and stops it immediately with an "out of memory" error. The memory used by the request is thrown away.

Step 3: Because the request was stopped suddenly, some of the normal cleanup never runs. A few pointers in PHP's long-living memory still point to the memory that was just thrown away. These are our dangling pointers. In the hotel story, this is the old key card that still works.

Step 4: The worker does not die. It is like the waiter who moves on to the next customer. The attacker now sends a second request and tries again and again until it reaches the same worker.

Step 5: The second request carries data that the attacker has shaped. PHP places this data in memory, and it lands in exactly the spot that the dangling pointer is still pointing to. In the hotel story, this is the attacker filling room 305 with fake items.

Step 6: PHP uses the old pointer, believing it still points to its own trusted data. But it is reading the attacker's data. The fake data is shaped so that PHP jumps to a piece of code chosen by the attacker, instead of its own code.

Step 7: PHP follows the instructions, and the attacker's code runs on the server. The attacker now has a shell.

We can see the complete flow in a diagram like below:

Request 1: heavy upload
        ↓
Crosses memory_limit, PHP stops with "out of memory"
        ↓
Cleanup skipped, dangling pointer left behind
        ↓
Request 2: same worker
        ↓
Attacker data fills the freed memory
        ↓
PHP follows the old pointer
        ↓
Attacker code runs

Here, we can see that the first request leaves the dangling pointer behind, and the second request uses it. Neither request needs a username or a password.

All of this happens very early, before the website's own code even starts. That is why even a correctly written website is affected. The website's developer did nothing wrong.

A quick note for you

No matter which tech domain you work in, get familiar with these topics:

  • LLM
  • RAG
  • MCP
  • Agent
  • Fine-tuning
  • Quantization

We put it all together in one video:

AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization

No need to stop reading - bookmark it and watch later when you get time. Future you will thank you.

Now, let's get back to the topic.

The two bugs inside PHP2Shell

PHP2Shell is not one bug. It is two bugs that follow the same pattern from above: an "out of memory" stop skips cleanup, and something uses freed memory later.

CVE-2026-109618: the upload progress bug

PHP has a feature called session upload progress. When we upload a large file, this feature lets the website show a progress bar like "45% uploaded". To do this, PHP saves some information in the user's session. A session is a small storage that the website keeps for each visitor, so that it remembers things like whether we are logged in.

This feature is turned on by default, and it runs while PHP is reading the uploaded data, before the website's code starts. So, even a website that never shows a progress bar has this code running.

The bug is that when an upload is stopped by an "out of memory" error, this feature leaves behind pointers to freed memory. The next upload request on the same worker fills that memory with attacker data, and PHP reads it as real session data. That gives the attacker control.

CVE-2026-109619: the fatal error and OPcache bug

This one has two parts.

The first part is about errors. In PHP 8.5, when a serious error happens, PHP can save a list of the steps that led to the error, called a backtrace, to help developers find and fix the problem. After an "out of memory" stop, PHP forgets to erase the pointer to this saved backtrace. The next request on the same worker reads this old pointer, but the memory behind it now holds attacker data.

The second part is about OPcache. OPcache is a feature that saves an already-prepared version of PHP code in shared memory, so that PHP does not prepare the same code again and again. This makes websites much faster. Shared memory means all the workers use it together.

Think of shared memory as a row of numbered boxes. PHP keeps two counters of how many boxes are already used. One counter is private to the worker, and one is shared by all the workers. These two counters must always match.

The bug is that if PHP runs out of memory in the middle of saving code into OPcache, the two counters go out of sync. Then, another worker reuses a box thinking it holds one kind of data, while it actually holds a different kind. This mix-up is called type confusion. The attacker uses this mix-up to take over the server.

Let me tabulate the two bugs for your better understanding.

CVE-2026-109618CVE-2026-109619
WhereSession upload progressFatal error backtrace and OPcache
Kind of bugUse-after-freeUse-after-free and type confusion
Triggered by"Out of memory" during an upload"Out of memory" or a fatal error
Login neededNoNo
Severity (CVSS)9.2 Critical9.2 Critical

Now, let's see how they were found.

How PHP2Shell was found

PHP2Shell was found by Wiz Research, a cloud security company, working together with Calif and OpenAI.

The researchers used AI models heavily during the research. As per Wiz, AI helped them find the bugs, build working attacks for different PHP versions, and test the fixes. Wiz called it "the AI inflection point", which means a turning point. AI is now good enough to help humans find deep bugs that stayed hidden inside PHP for more than ten years.

Here is the timeline:

  • August 19, 2026: the first bug is found.
  • August 20 and 21, 2026: full takeover of Drupal and WordPress is confirmed.
  • August 24, 2026: the bugs are reported privately to the PHP security team.
  • September 21, 2026: the PHP team confirms and starts working on a fix.
  • October 20, 2026: PHP announces an emergency security release for the next day. Talk about the bugs spreads online, and fake attack code starts appearing on GitHub.
  • October 21, 2026: the fixed versions are released and the details are made public.

Note: Reporting a bug privately first and making it public only after the fix is ready is called responsible disclosure. It gives the software team time to fix the problem before attackers learn about it.

This time, the news started spreading one day before the fix. Cloudflare said it was already seeing early attack attempts before the patches were out, while some other security companies had not confirmed any attacks at the time of writing. The UK's National Cyber Security Centre (NCSC) asked organisations to patch immediately or take their systems offline.

How PHP fixed it

The root cause of both bugs was the same: when PHP stopped a request because of "out of memory", it skipped the cleanup.

So, the PHP team fixed the root cause itself. Instead of stopping the request suddenly in the middle of anything, PHP now marks the "out of memory" problem and stops at the next safe point. A safe point is a moment when PHP has finished its current small task and nothing is half-done. At that safe point, the normal cleanup always runs, and all the pointers are erased properly.

The PHP team also made the long-living memory reset properly at the end of every request, and made the upload progress feature handle stored session data more carefully, so that no unexpected code runs while it is being read.

Stay updated: Subscribe to our newsletter to get our latest AI and Machine Learning blogs straight to your inbox.

What we must do now

If we run any website that uses PHP, including WordPress, Drupal, or Joomla, this is what we must do.

First, update PHP immediately. The fixed versions are:

  • PHP 8.5.12
  • PHP 8.4.27
  • PHP 8.3.36
  • PHP 8.2.35

Very important: PHP 8.1 and older versions no longer get official updates. The PHP team has shared fixes in source code form for 7.4, 8.0, and 8.1, and some hosting companies and server operating system makers ship their own fixed versions. But, the right long-term step is to move to a supported version.

Then, if we cannot update today, use the temporary safety settings. The PHP team has shared a few settings that reduce the risk, for example turning off the upload progress feature, as below:

session.upload_progress.enabled = 0

Here, we are turning off the session upload progress feature, which closes the upload progress path of PHP2Shell. After changing this setting, we must restart all the PHP workers so that the change takes effect.

On PHP 8.5, we can also turn off saved error backtraces, as below:

fatal_error_backtraces = 0

Here, we are telling PHP not to save the backtrace of a fatal error, which closes the backtrace path of PHP2Shell.

For PHP-FPM, one more setting breaks the same-worker attacks in a different way, as below:

pm.max_requests = 1

Here, we are telling PHP-FPM to replace each worker after it handles just one request. Remember, the attack needs two requests on the same worker. With this setting, the second request can never reach the worker that holds the dangling pointer. But, it does not stop the OPcache part, because OPcache memory is shared by all the workers. Another downside is that the server becomes slower, because it keeps starting new workers.

Note: These settings reduce the risk, but they do not block every path, especially the OPcache part. A PHP core developer said that no setting fully protects us. Updating PHP is the only real fix.

After that, use a web firewall if possible. A web firewall is a guard that sits in front of our website and checks every request before it reaches our server. Companies like Cloudflare and Google have added rules to their web firewalls that block known PHP2Shell attack requests.

Finally, check if someone already got in. Signs to look for are many failed upload requests followed by "out of memory" errors, many server errors in a short time, and very large or strange-looking session cookies. If we see these, we must treat the server as possibly taken over.

Lessons from PHP2Shell

PHP2Shell gives us a few lessons that apply to all software, not just PHP.

  • Cleanup after errors matters as much as the normal flow. The bug was not in the normal flow where everything goes right. It was in what happens when things go wrong.
  • Memory safety is hard in languages like C. In C, the programmer manages memory by hand, and one forgotten pointer can become a critical bug. This is one big reason why newer languages like Rust are designed to prevent use-after-free in normal code.
  • AI is changing security research. AI helps defenders find bugs faster, and it can help attackers too. So, the time between a bug becoming public and attackers using it keeps getting shorter.

Now we must have understood what PHP2Shell is, how a small mistake in cleaning up memory turned into a complete server takeover, how it was found and fixed, and what we must do to keep our websites safe.

Frequently Asked Questions

Does PHP2Shell affect WordPress websites?

Yes. PHP2Shell affects WordPress, Drupal, and Joomla websites, because the bugs live inside PHP itself, not inside a plugin or a theme. The researchers took full control of the latest versions of all three. So, any WordPress website running on an affected PHP version must update PHP immediately.

Do we need to change our website code to fix PHP2Shell?

No. Our website code is not the problem. The attack happens very early, before the website's own code even starts, so even a correctly written website is affected. The fix is to update PHP to a fixed version, such as PHP 8.5.12, 8.4.27, 8.3.36, or 8.2.35.

Is turning off session upload progress enough to stay safe from PHP2Shell?

No. Turning off session upload progress closes only the upload progress path. The temporary settings reduce the risk, but they do not block every path, especially the OPcache part, which is shared by all the workers. A PHP core developer said that no setting fully protects us. Updating PHP is the only real fix.

Are old PHP versions like PHP 7.4 affected by PHP2Shell?

Yes. As per the researchers, PHP versions going back to PHP 5.4 are affected by at least one of the two bugs. PHP 8.1 and older no longer get official updates. The PHP team has shared fixes in source code form for 7.4, 8.0, and 8.1, but the right long-term step is to move to a supported version.

That's it for now.

Thanks

Amit Shekhar
Founder @ Outcome School

You can connect with me on:

Follow Outcome School on:

Read all of our high-quality blogs here.