Security Approximate reading time: 12 min ·

PHP2Shell: A Critical PHP Vulnerability — Is Your Website at Risk?

On 21 October PHP released emergency security patches for two critical vulnerabilities that let an attacker run code on the server with no password. How to tell in a few minutes whether this affects your website, what to do first, and what to do if you cannot update immediately.

A browser window with code brackets and a warning sign — the critical PHP vulnerability PHP2Shell

On 21 October PHP released emergency security patches for two critical vulnerabilities that let an attacker run code on the server with no password. How to tell in a few minutes whether this affects your website, what to do first, and what to do if you cannot update immediately.

Every PHP version older than 8.5.12, 8.4.27, 8.3.36 and 8.2.35 in its own branch is affected, which means almost every PHP website that nobody has updated this week. Today's first job is to find out which version is serving your website, and to update it — there is no complete workaround, and PHP's own announcement says that updating is the only safe choice.

On Wednesday 21 October the PHP team released four emergency versions at once, and the pair of vulnerabilities they fix has been given the name PHP2Shell, and the name describes the result precisely: from a network request to code execution on the server. This article is about what was actually found, how to tell whether it affects you, and the order in which to act — including when you cannot update immediately.

What PHP2Shell is and what happened on 21 October

The official announcement states that 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical vulnerabilities which can let an unauthenticated attacker execute code remotely, and that every PHP user is strongly advised to update immediately. The same announcement contains the sentence that governs the rest of this article: there is no complete workaround, and updating is the only safe choice.

PHP has assigned both vulnerabilities a Common Vulnerability Scoring System (CVSS) v4.0 score of 9.2 out of 10, but they are two separate bugs with different mechanisms and, as you will see below, different degrees of confirmation — and it is that second difference which, in practice, decides which server to start with.

CVE-2026-109618 — corrupted memory after an out-of-memory (OOM) failure: when PHP runs out of memory, it uses longjmp() to abort the handling of the request. The official description says that at some moments the request's memory is reachable before it has been fully initialised; if the allocation fails at exactly that moment, the abort is not handled correctly and invalid memory remains, which the shutdown code still reads or bases a decision on.

A detail that changes who this threat applies to: the out-of-memory condition is reached with a specially crafted multipart request, and that request does not have to contain a single uploaded file. The official description mentions several problems in multipart handling — a request with no usable declared length, a part with an unbounded number of headers, empty file fields that are not counted against the limits. That means the request can be quite small and still sit inside the ordinary post_max_size and memory_limit limits.

CVE-2026-109619 — stale global state: a PHP process keeps global state between requests: data for errors, sessions, objects and extensions. The official description says this state has to be reset before the request's memory is freed. If a fatal error interrupts the reset, or if diagnostics fill the state in again at shutdown, the memory is freed but the pointer to it remains, and this class of bug is known internationally as use-after-free.

There is then a condition that makes this vulnerability harder than it sounds: the stale pointer can be used only by an operation that happens before ordinary request initialisation, and the official description names two confirmed cases of that — INI diagnostics and the handling of session upload progress. On top of that, the attacker's later request has to land in exactly the same process where the corrupted state arose, so a single attempt rarely succeeds, and the description states outright that several processes, and replacing them, reduce the odds but do not remove the condition itself.

How easy it is to exploit, and what it has been proved against

It is worth being precise here, because this is exactly where most articles on the internet overstate the case in one direction or the other.

No password is required, and nor does anyone have to press anything on the website — in the CVSS vector that appears as PR:N and UI:N. The same vector does, however, contain AC:H and AT:P: attack complexity is rated high, and the attack has additional preconditions. In practice that means repeated attempts, and success depends on the PHP build, the extensions, the behaviour of the front end and the layout of memory. So no — one ordinary request is not enough; but it costs the attacker nothing either, because nothing limits the number of attempts.

The degree of proof differs between the two bugs, and that difference matters when you are deciding how much to hurry. For CVE-2026-109618, complete remote code execution (RCE) has been confirmed against mod_php with the default limits on input and memory; of PHP-FPM, the same description says that analysis of the source considers it vulnerable, but a complete PHP-FPM exploit has not been demonstrated. FrankenPHP is mentioned there as analogous to mod_php, and LSAPI as analogous to PHP-FPM, in both cases without a verified exploit.

For CVE-2026-109619, the session-lifecycle path has been tested against both mod_php and PHP-FPM, whereas the other path, which goes through the fatal-error backtrace, has been confirmed only against mod_php.

What follows from that, in practice, is an order: if your website runs on mod_php, you are first in line, because that is where both bugs have been confirmed, whereas with PHP-FPM and LiteSpeed the risk is real but so far only partly confirmed. In none of these cases is that a reason to wait — it is a reason to know which server to start from.

What this week looked like, hour by hour

The timeline is not a curiosity — it explains why the advice in the first hours differed from what holds now, and why some of the articles on the internet are still out of date.

On the evening of 20 October it was known that something was coming, but not exactly what: PHP had announced an emergency release to come, and the CloudLinux status page records, that same evening, that the technical details and the affected versions were scheduled to be published at the moment of the next day's release, and the technical description was not yet public at that point.

By the next day the situation had changed, and not for the better: on the morning of 21 October CloudLinux records that an exploit sample was already in public circulation, and at 09:08 UTC it distributes a web application firewall (WAF) ruleset that blocks the known exploit-request pattern. Eighteen minutes later the same page carries the sentence that describes this week best — attempts matching this pattern are already being observed, so someone was already looking for servers that do not yet have the security patch.

The PHP security patches came out at 14:00 UTC, which means that several hours passed between a publicly available exploit sample and a publicly available security patch, and during those hours the attempts were already happening.

One caveat is fair to state here: the world was not entirely without a security patch, because the PHP announcement says that Linux distributions and long-term support vendors received the patches in advance, so for some servers the update was available earlier than for the others — it was not, however, publicly available to anyone.

After that the longer part began, and it is still going on: the distribution of the packages, since the maintainers of operating systems and the hosting platforms build their packages separately, and the packages appear over hours or days, each at its own pace. A large share of shared-hosting users are in exactly this stage, which is why the question "has the security patch been released" and the question "is the security patch on my server" are two different questions this week, with different answers.

Whether your website is at risk

In the official warnings the ranges of affected versions are written like this: everything older than 8.2.35, everything older than 8.3.36, everything older than 8.4.27, and everything older than 8.5.12 — each within its own branch.

An important detail, and an easy one to get wrong: the number that php -v prints does not always tell the whole story. Linux distributions and hosting platforms often carry a security patch onto an older version without changing that version's number — only the package's release number changes. In that case 8.4.26 may already be patched, and it may also not be, so the only way to know is to look at the package's description or to ask the vendor; the version number itself does not answer the question.

Separately, for the older versions, PHP 8.1 and older no longer receive regular updates from the PHP project, but, given the severity, the security team has prepared minimal source patches for versions 7.4, 8.0 and 8.1 — for those who build from source, or who have no access to a maintainer's support. The announcement also recommends contacting your own vendor, because some distributions and long-term support vendors do carry the patches across. If your website runs on PHP 7.x, the real problem is not this week — the real problem is that next time there may be no security patch at all. And PHP 8.2, which did receive a security patch this time, reaches end of life on 31 December 2026.

How to check the version, depending on where the website lives

The check takes minutes, and it is worth doing for each website separately rather than for each server: websites on different PHP versions often live on one server, and it is usually the oldest of them, the one everyone has forgotten, that is reachable from the public internet.

Shared hosting with cPanel: the version is shown by MultiPHP Manager, where each domain has its own row; Plesk uses PHP Settings, inside each subscription, for the same thing. In both cases it is worth making sure you are looking at the domain in question, and not at the server's default.

Your own server, or a virtual private server (VPS): on the command line php -v shows it, but with one substantial caveat: the command-line PHP is often an entirely different version from the one that serves web requests. The more trustworthy answer comes from the same path the website runs on — the executable named in the pool section of the PHP-FPM configuration.

Containers: if the website runs under Docker, the image determines the version, not the host machine, so there are two steps: rebuild the image with an updated base image, and then restart the containers. An image built before 21 October does not contain the security patch, however often the container has been restarted.

If you have access to none of these, the question goes to the hosting provider, and this week that is an entirely normal question, one they will already have an answer prepared for. A wording that saves a round of correspondence: "Which PHP version serves my domain, does it contain the PHP2Shell security patch, and, if not yet, on what date will it?"

What to do today

Today's order is this: first update PHP and restart everything that executes it, then make sure that visitors really are being served by the new version, then check whether the package repository you use has released the security patch at all, and only after that look for traces. The first step takes minutes, the last can take days, and that is exactly why they are not worth swapping around.

First, update. On managed hosting the provider does this, and your job is to ask the question and to receive a date. If you administer the server yourself, once the packages have been updated you have to restart whatever executes PHP: Apache, PHP-FPM or LSAPI. This step is skipped surprisingly often — the package has been replaced, but the old processes keep running the old code, so a check shows the new version while visitors are still being served by the old one.

Second, check that the update really has been applied — along the same path the website runs on, not through the command line.

Third, if you use a volunteer-maintained package repository, check whether it has released the security patch, and the PHP announcement carries a separate warning about rpms.remirepo.net: its users are advised to move to the distribution's packages, or to build from source.

Fourth, and only after that, check whether the website shows traces, since updating closes the door, but it does not answer the question of whether anyone has already come through it.

If you cannot update immediately

This section is worth reading in the clear knowledge that none of the steps listed here replaces the update — the PHP announcement says so directly.

Against CVE-2026-109618 the announcement mentions removing the memory limit: memory_limit=-1, and in such a way that the application cannot overwrite it — for example with php_admin_value in the PHP-FPM or Apache configuration, after making sure that no other setting puts a finite limit back. What this choice costs has to be understood: on a shared server it trades one risk for another, because a single request can now consume all of the available memory. The vulnerability description also warns against using RLIMIT_AS and RLIMIT_DATA, because they can make an allocation fail and thereby lead straight onto the vulnerable path.

Against CVE-2026-109619 the announcement mentions session.upload_progress.enabled=0 and, in the case of PHP 8.5, fatal_error_backtraces=0 — in both cases with the instruction to restart every process afterwards. The vulnerability description itself also contains pm.max_requests=1, which makes PHP-FPM replace the worker after every request; it is a PHP-FPM pool setting, and it is mentioned there with the caveat "if performance is not a factor". The same place also says what keeps this measure inside its real bounds: replacing the processes reduces the odds per request, but does not remove the condition itself.

Also useful is what is not a workaround, but does reduce the attack surface: a conservative post_max_size, a sensible depth of input nesting, and a limited upload size — good values in any week, not only in this one.

How to tell whether you have already been hit

After the update it is worth reviewing the web server's logs for the last few days, and what to look for is an unusually large number of multipart requests to one and the same address — and it is easy to look in the wrong way here: they tend to be small and to fit inside the ordinary limits, so you will not find them by size. More telling are sudden runs of 500 errors that start and stop with no visible reason: an attack that depends on running out of memory fails far more often than it succeeds, and every failed attempt leaves an error.

On the file system, what matters is the modification times: a PHP file that was changed at night, when nobody was working, has to be explained. It is also worth checking scheduled tasks and the entries that make something start automatically after a restart — for an attacker who can execute code, the first job is to entrench, so that the access survives exactly the update you have just installed.

Separately, it is worth checking the list of administrator accounts, and the application's passwords and keys, because if code execution has taken place, then everything the server knows has to be treated as having been read — including the database credentials and the API keys in the configuration file.

If any of that does not look right, the clean-up is a separate piece of work with its own order, and it is worth doing methodically rather than by deleting the suspicious files one at a time. We have described it separately — how recovery of a hacked website works — and if you want someone else to do it, hacked website recovery is a separate service with a fixed sequence: isolation, clean-up, removing the cause, and a check that the website has been taken off the search engines' warning lists.

When the event becomes a reportable incident

If there is reason to think that the attacker has reached personal data, the technical question becomes a legal one as well. The General Data Protection Regulation sets a deadline that starts not from the attack but from the moment the controller has become aware of a personal data breach: where feasible, not later than 72 hours for the notification to the supervisory authority. The duty does not arise where the breach is unlikely to result in a risk to the rights and freedoms of natural persons, and where the risk is high the persons themselves have to be informed as well — that duty, too, has its own exceptions in the regulation.

The practical point of this section is a single one: the moment something suspicious has been found in the logs is not the moment the 72 hours start to run — it is the moment to establish whether a breach has happened at all. An isolated run of 500 errors, which, as described above, is most often a failed attempt, is not in itself a personal data breach. But that is exactly why this establishing is not worth putting off until "when there is less to do", and exactly why the logs have to be kept before rotation overwrites them: they are the only evidence both of what happened and of what did not.

If you look after several websites

For agencies and for companies with several websites, this week is less a technical task than an organisational one, and the order that works is simple: first the list, then the versions, then updating in order of priority.

Priority is set not by how important a website is, but by whether it can be reached and by what serves it. At the top are publicly reachable websites on mod_php, because that is where both vulnerabilities have been confirmed; then everything else that is public, and only after that the internal systems behind a firewall. Test and development environments, which everyone forgets, have to be on the list without exception — they often run on an older PHP version, and they are often reachable from the internet, because at some point that was more convenient.

Websites that someone else administers stay on the list, but with a different column: who the question was sent to, and on what date the answer was received. That column later turns out to be more valuable than the others, because it is the one that shows which provider responds and which does not.

What managed hosting meant in this case

This week shows clearly where the boundary is between the platform and the application, so it is fair to say how it looked from our side.

Our servers run on CloudLinux, and Imunify360 is built into them, and when the exploit sample began to circulate in public, CloudLinux recorded on its status page that the firewall ruleset which blocks the known exploit pattern had been distributed at 09:08 UTC — several hours before PHP released the security patch itself at 14:00, and before the entry appeared on the same page saying that attempts were already being observed. During the day two further additions followed, the second of which blocks the exploit tool that was already publicly available. All of this happened automatically, without the client taking part, and in the industry a measure of this kind is called virtual patching: a web application firewall rule blocks exploitation of the vulnerability without changing the vulnerable code itself.

And now what a sentence like that does not mean, because this is exactly where the industry sold the most exaggeration this week. A firewall rule blocks known attack patterns — CloudLinux uses that same word, and the same page calls this measure a temporary solution, not a complete fix. It is not invulnerability, and CloudLinux itself goes on recommending that PHP be updated as soon as the packages are available. The difference between "protected" and "patched" is entirely concrete this week, and to sell one as the other would be dishonest.

What the security patch itself looks like is a good illustration of what we warned about above, and our servers received a new build of ea-php84 on the evening of that same day, a few hours after the PHP release. The version number did not change — php -v still prints 8.4.26, not 8.4.27, and the only thing visible from outside that shows a change is the build date, which jumped from 9 October to 21 October. That is exactly what a security patch carried onto an older version looks like, and that is why a question to the hosting provider is more useful than the number the screen shows.

So the value that managed hosting bought here is not invulnerability, but a narrower gap: the hours in which the exploit sample was already circulating, and attempts were already happening, were covered by blocking the known pattern, rather than waited out. And afterwards, that someone follows the packages and restarts the processes, rather than waiting until the client notices. Our managed hosting starts at €45 a month, and weeks of exactly this kind are what that sum pays for.

If your website currently runs somewhere where you cannot get an answer to the question about the PHP version and the date of the update, that in itself is an answer. Let us talk about moving it — a migration can usually be done with no downtime.

After the update, restart more than it seems

A process that stays alive between requests is nothing exotic — that is exactly how an ordinary PHP-FPM pool and mod_php work, and it is exactly against those that code execution has been confirmed. So CVE-2026-109619 is not some threat that only special configurations face; it is a problem of the ordinary configuration.

What follows from that is the step most often skipped: replacing the package does not affect a process that is already running. After the update, every process that executes PHP has to be restarted, and that includes the ones nobody usually touches. Long-running PHP processes tend not to be restarted for weeks, so after the update it is worth listing them separately and making sure that each of them has been brought up again.

The right question to check is therefore not "what does php -v show", but "was every process that executes PHP started after the package had been replaced".

Why "we do not upload anything" is not enough

A common objection runs roughly like this: our website is a brochure site, it has neither an upload form nor user accounts, so this does not apply to us. Unfortunately it does, and the reason is exactly what was described above: the attack does not need an uploaded file, a suitably crafted multipart request is enough, and it can be sent to any address that answers with PHP. A form being present on the page is not a precondition, because the attacker does not fill a form in.

Nor does it matter whether the website is small, because attacks against vulnerabilities of this kind happen across a wide network, and automatically, and nobody selects by the number of visitors. On the day of disclosure the Palo Alto Networks unit Unit 42 reported more than 22.6 million PHP instances reachable on the internet that are potentially affected; not one of them was chosen in particular.

The second objection is about WordPress and plugins, and that one does not hold this time either, only in the opposite direction: the vulnerability is in PHP itself, not in WordPress, Laravel or some plugin. That means updating plugins does not solve this problem, and, at the same time, that every PHP website is affected regardless of what it is built on.

What is worth keeping from this week

For those who were already in order, what worked in their favour was not a fast reaction but three things that had been arranged earlier: it was known which website runs on which PHP version, it was known who is responsible for updating, and there were backups that had actually been tested, confirming the website can be restored. None of those three can be done on the day the warning has already been published, and that is exactly why this week was half an hour of work for some teams and two days for others.

If one task is to be taken from this week so that the next time is easier, it is a list: every website, its PHP version, the way it is executed, its hosting provider, and the person who is allowed to press "update". The next warning of this kind will come, and the only question then is how long it will take to answer the question "does this affect us".

FAQ

Frequently asked questions.

What is PHP2Shell?

PHP2Shell is the name given to two critical PHP vulnerabilities (CVE-2026-109618 and CVE-2026-109619), published on 21 October 2026. Both can let an unauthenticated attacker achieve code execution on the server, and PHP has assigned both a CVSS v4.0 score of 9.2 out of 10. The name describes the result: from a network request to a shell on the server.

Which PHP version is safe?

The safe versions are 8.5.12, 8.4.27, 8.3.36 and 8.2.35, or newer. Everything older than these, within its own branch, is affected. Hosting platforms often carry the security patch onto an older version without changing its number, so on shared hosting the number itself does not always answer the question — the provider then confirms it.

I am on PHP 8.1. What should I do?

PHP 8.1 and older versions no longer receive regular updates from the PHP project. Given the severity, the security team has prepared minimal source patches for versions 7.4, 8.0 and 8.1, for those who build from source or who have no access to a maintainer's support. In practice there are two paths: ask the hosting provider whether they have carried the security patch across, or move to a supported version. The second path is the only one that also solves it the next time.

Does a firewall protect me while I update?

Partly, and only for a while. A web application firewall (WAF) blocks known attack patterns, and in this case several vendors distributed rules of that kind before the security patch itself. That is useful, but it is not a security patch, and the vendors themselves call it a temporary solution. The PHP announcement says that there is no complete workaround and that updating is the only safe choice.

Has my website already been hacked?

The version number does not answer that. After the update it is worth reviewing the web server's logs for the last few days, paying attention to an unusual number of multipart requests to a single address and to sudden runs of 500 errors, checking the modification times of PHP files, and looking at the scheduled tasks. If something does not look right, the clean-up is a separate, methodical piece of work.

Is it enough to update WordPress and the plugins?

No. The vulnerability is in PHP itself, not in WordPress or in a plugin, so updating plugins does not remove it. The same reason means that every PHP website is affected, whether it is built on WordPress, on Laravel or on something else.

Our website has no upload form. Does that protect us?

No. The attack does not need an uploaded file — a suitably crafted multipart request is enough, and it can be sent to any address that answers with PHP. A form being present on the page is not a precondition. Nor is a website's traffic a defence: vulnerabilities of this kind are exploited automatically, and across a wide network.

Can the website stop working after a PHP update?

The security patches were released as updates inside the branch, for example from 8.4.26 to 8.4.27. Those do not usually change how the language behaves at application level. The fix for CVE-2026-109618 does, however, change how PHP acts at the moment it runs out of memory. The larger risk arises when the move is onto another branch at the same time, for example from 8.1 to 8.3 — that is worth testing in a test environment first. Putting the update off because of this risk is still not a balanced choice: a website that has not been updated is exposed to a known vulnerability that has been described in public.

RELATED SERVICE
Hacked website recovery

WordPress or Laravel site hacked? Need it recovered after a breach? We recover it, clean it and harden it — usually in 24–72 hours, with the cause found and the Google warning removed.

Learn more