---
title: php2shell (CVE-2026-109618): how to check if your site is vulnerable
description: php2shell is a critical remote code execution bug in PHP itself, fixed on Oct 21, 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Here's how to check which PHP version your site really runs, from outside and on the server, why Debian, Ubuntu and Red Hat builds need a second look, and how to patch without missing the restart.
canonical: https://watchfor.io/blog/php2shell-check-if-your-site-is-vulnerable
---

# php2shell (CVE-2026-109618): how to check if your site is vulnerable

2026-10-22 · WatchFor Team · Guides · guides, security, php, vulnerabilities

php2shell is a critical remote code execution bug in PHP itself, fixed on Oct 21, 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Here's how to check which PHP version your site really runs, from outside and on the server, why Debian, Ubuntu and Red Hat builds need a second look, and how to patch without missing the restart.

**php2shell** is the name for CVE-2026-109618 and CVE-2026-109619: two
memory-safety bugs in PHP's request handling that let an unauthenticated
attacker run code on your server. The PHP project announced them on
Oct 20, 2026 and shipped the fix the next day in **PHP 8.5.12, 8.4.27,
8.3.36 and 8.2.35**. A public proof of concept exists. (New to the term? Here is
[what a CVE is](/blog/what-is-a-cve).) To know whether you are exposed, you
need one fact: the exact PHP version, and build, that
answers your web requests.

The free [PHP Version Checker](/php-version-checker) reads that version from
the outside, the way a scanner would, and grades it A+ to F against
php2shell, the other known CVEs and PHP's support calendar. No signup, and
it only sends plain GET requests.

## What php2shell is

Both CVEs are in PHP's core, not in WordPress, Laravel or any extension:

- **CVE-2026-109618**: PHP leaves the current request with `longjmp()` when
  it runs out of memory. Some code paths don't expect that and leave request
  memory uninitialized; request cleanup then reads it.
- **CVE-2026-109619**: some global state keeps pointing into request memory
  after it is freed, and a later request reads it.

Both end in remote code execution. The PHP project scores them CVSS 9.2
(v4.0); NVD's own v3.1 score is 9.8. PHP's advisory says every installation
serving web requests is at risk, and since the trigger is an ordinary
request, there is no "only if you use feature X" escape. The fix is out,
so from here on the window is however long your servers take to get it.

## Which versions are affected

NVD lists the same ranges for both CVEs:

| Branch | Affected | First fixed release |
| --- | --- | --- |
| PHP 8.5 | 8.5.0 – 8.5.11 | **8.5.12** |
| PHP 8.4 | 8.4.0 – 8.4.26 | **8.4.27** |
| PHP 8.3 | 8.3.0 – 8.3.35 | **8.3.36** |
| PHP 8.2 | 8.2.0 – 8.2.34 | **8.2.35** |
| PHP 8.1 and older | every release | no fix (end of life) |

PHP 8.1 reached end of life on Dec 31, 2025 and PHP 7.4 in Nov 2022, so
neither got a patch. PHP 8.2 itself stops receiving security fixes on
Dec 31, 2026: if you install 8.2.35 today, plan the next upgrade now. The
[branch table on the checker's page](/php-version-checker) lists every
branch with its dates and the days it has left.

## Check from the outside

In the [PHP Version Checker](/php-version-checker), enter your site and press
**Check PHP version**. A probe near you fetches the page once and reads two
headers:

- `X-Powered-By: PHP/8.3.14`, which PHP adds itself while `expose_php` is on;
- `Server: Apache/2.4.62 (Debian) PHP/8.2.20`, which Apache with mod_php can
  send.

If the page shows no version, the checker makes exactly one more request, to
`/index.php` on the same site, and stops there. A `PHPSESSID` cookie proves
PHP runs even when the version is hidden.

### A real example: a site that hides its version

We checked `https://bugs.php.net/`, the PHP project's own bug tracker. The
run came from the Vilnius probe:

- `GET https://bugs.php.net/` answered **200** with no `X-Powered-By` and no
  PHP in `Server` (it sits behind a CDN), but set a `PHPSESSID` cookie;
- `GET https://bugs.php.net/index.php` answered **200**, again without a
  version.

The verdict: **PHP, version hidden, not graded.** That is the right answer.
Hiding the version is good practice, and from outside nobody — us included —
can tell which release runs there. The result still shows the fixed-release
table, so whoever owns the server can compare it with `php -v`.

### Reading an affected result

When a version is visible, the result leads with the grade and the first
thing to fix. A server announcing `PHP/8.3.14` gets an **F**: the php2shell
card says *Affected*, names **8.3.36** as the fix for its branch, and the
branch card shows that 8.3 now gets security fixes only and is **22 patch
releases behind**. Other known CVEs for that exact version are listed
underneath, worst first, with a **KEV** badge where CISA has seen them
exploited.

Every grade is a cap, and the reasons are listed: php2shell or any critical
or exploited CVE is F, a branch past end of life D (F after two years), a
high-severity CVE C. The full rule table is under the tool.

## Check on the server

The header shows what the web server announces. On the box itself:

```bash
php -v                     # the CLI's PHP — not always the one serving requests
php-fpm8.3 -v              # PHP-FPM (binary name varies: php-fpm, php-fpm8.2…)
apache2ctl -M | grep php   # mod_php loaded? Then Apache's PHP is the one that counts
```

The CLI and the web server can run different builds, especially with
several versions installed side by side. The surest check is a one-line file
in the web root:

```php
<?php echo PHP_VERSION;
```

Open it once, note the version, **delete it**. Never leave `phpinfo()` or a
version file public: it hands out exactly the information you are trying to
hide. On WordPress, **Tools → Site Health → Info → Server** shows the same.

## Debian, Ubuntu and Red Hat: the version can lie

Distributions backport security fixes into their own packages and keep the
upstream version number. Ubuntu 22.04's PHP says `8.1.2` for its whole life,
and Red Hat supports some PHP streams for longer than the PHP project does. A
header like `X-Powered-By: PHP/8.1.2-1ubuntu2.20` or
`Server: Apache/2.4.37 (Red Hat Enterprise Linux)` is in php2shell's range by
number, and may still be patched.

So the checker never fails such a build on the version alone: it caps it at
**C**, marks php2shell **verify**, and links the vendor's page for each CVE.
Then you verify — the package changelog is the only source that knows:

```bash
# Debian / Ubuntu
apt changelog php8.1-fpm | grep -E 'CVE-2026-10961(8|9)'
apt policy php8.1-fpm      # the installed vs available package version

# RHEL / Rocky / Alma
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961(8|9)'
```

Compare with the fixed package version in the trackers:
[Debian](https://security-tracker.debian.org/tracker/CVE-2026-109618),
[Ubuntu](https://ubuntu.com/security/CVE-2026-109618),
[Red Hat](https://access.redhat.com/security/cve/CVE-2026-109618). If your
release no longer gets PHP updates at all, the version number is the truth,
and the server is affected.

## Patch it

The fix is the new release. We know of no configuration switch that removes
the bug, so don't count on a WAF rule alone.

- **Debian / Ubuntu**: `sudo apt update && sudo apt install --only-upgrade 'php8.3*'`
  (adjust the version; the `ppa:ondrej/php` / packages.sury.org builds
  follow upstream releases).
- **RHEL family**: `sudo dnf upgrade 'php*'` once your vendor or the Remi
  repository ships the fixed build.
- **Docker**: the official `php` images are rebuilt for each release, but
  your image isn't. `docker pull php:8.3-fpm`, rebuild, redeploy. Pin a
  branch tag rather than a patch release so every rebuild picks up fixes.
- **Shared hosting**: pick the newest PHP version in the control panel; if
  the newest offered is still affected, ask the host when it lands.

Then **restart PHP**. The package manager replaces files on disk; the
running PHP-FPM master keeps the old code in memory until
`systemctl restart php8.3-fpm` (or `php-fpm` on Red Hat), and mod_php needs
an Apache restart. Run the [PHP Version Checker](/php-version-checker) again
and confirm the announced version changed. With several servers behind one
name, run it a few times: one forgotten pool member shows up as a different
answer.

## Hide the version (after patching)

Hiding the version stops casual fingerprinting. It does not fix anything —
attackers try exploits blind, too — so do it second:

```ini
; php.ini
expose_php = Off
```

```nginx
server_tokens off;
fastcgi_hide_header X-Powered-By;   # PHP-FPM behind fastcgi_pass
proxy_hide_header X-Powered-By;     # PHP behind proxy_pass
```

```apache
ServerTokens Prod
Header unset X-Powered-By
Header always unset X-Powered-By
```

## Keep watching

php2shell won't be the last PHP advisory. Two things help next time:

- An [API monitor](/docs/monitors/api) on your home page with a header
  assertion — `X-Powered-By` **does not exist** — tells you when an upgrade
  or a new server quietly turns version disclosure back on.
- [Page integrity monitoring](/page-integrity-monitoring) alerts when a page
  starts loading a script from a new source or a form begins posting
  elsewhere — what a compromised PHP site typically looks like from the
  outside.

And when the next advisory lands, the [PHP Version Checker](/php-version-checker)
answers "are we affected?" for every public hostname in a minute — or from a
terminal: `curl https://watchfor.io/php/example.com`.

---

Canonical page: https://watchfor.io/blog/php2shell-check-if-your-site-is-vulnerable · All articles: https://watchfor.io/blog/all.md · Site guide: https://watchfor.io/llms.txt
