# PHP2Shell (CVE-2026-109618 and CVE-2026-109619) Explained: Two Different Bugs, the Invariants the Patch Restores, and What Operators Should Check Today

> PHP2Shell (CVE-2026-109618 and CVE-2026-109619) explained from the official advisories and the fix commit: how the two CVEs differ, status by SAPI, fixed versions, the limits of mitigations, how to check your own exposure, and how CVE-2026-115560 relates.

- Published: 2026-11-01
- Author: 友田 陽大
- Tags: セキュリティ, 脆弱性診断, PHP, インフラ, アーキテクチャ設計, 信頼性
- URL: https://tomodahinata.com/en/blog/php2shell-cve-2026-109618-109619-rce-analysis
- Category: Security incident analysis
- Pillar guide: https://tomodahinata.com/en/blog/gmo-desktop-cloud-hack-2026-security-analysis

## Key points

- PHP2Shell is the name for two separate PHP vulnerabilities: CVE-2026-109618 (out-of-memory handling corrupts internal state) and CVE-2026-109619 (stale state from a previous request survives). Fixed versions are 8.2.35, 8.3.36, 8.4.27 and 8.5.12, released 21 October 2026. CISA lists both in its Known Exploited Vulnerabilities catalog
- The demonstrated unauthenticated RCE is mainly against Apache mod_php. PHP-FPM can be affected according to source analysis, but a complete FPM attack for 109618 has not been demonstrated. 'Can be affected' and 'demonstrated' are different, and neither means 'safe'
- The patch restores four invariants, and the two at its core are: (1) on out-of-memory, do not use a longjmp that tears through half-initialized state — handle it at the next safe point; (2) reset request-owned state at the end of every request. The change touches over 300 files and adds a large number of tests that deliberately trigger out-of-memory
- Configuration mitigations are incomplete. memory_limit=-1 closes one path for 109618, and session.upload_progress.enabled=0 closes the confirmed paths for 109619. The vendor says updating is the only complete fix. CVE-2026-115560 is a separate vulnerability for which the same setting is an interim mitigation
- Check the PHP version your web server actually runs (not the CLI), distro backports, any end-of-life PHP 8.1 or older, and signs of compromise. The C teaching code was verified with AddressSanitizer and is a conceptual model, not a reproduction of PHP2Shell

---

On 21 October 2026 the PHP project released fixes for two critical vulnerabilities that can lead to unauthenticated remote code execution (RCE). The researchers who found them nicknamed the pair "PHP2Shell". Because so much of the web runs on PHP, the potential reach is large.

This article answers three questions:

- Is my service affected, and what should I check first?
- How do the two vulnerabilities differ, and why can they allow code to run remotely?
- Which **program rule that was being broken** did the fix restore?

> **Scope.** No attack code or procedure is included — only what the official advisories and the fix commit let you understand about the mechanism, plus defenses. The C examples are **independent teaching code** for the same *class* of bug and are not a reproduction of PHP2Shell. Information reflects primary sources as of 1 November 2026 (JST).

## Key points in 30 seconds

- **There are two vulnerabilities, and they are different.** CVE-2026-109618 (out-of-memory handling corrupts internal state) and CVE-2026-109619 (stale state from a previous request survives).
- **Fixed versions**: PHP **8.2.35 / 8.3.36 / 8.4.27 / 8.5.12** (released 21 October 2026). PHP 8.1 and older are outside the PHP project's updates.
- **They are being exploited**: CISA added both to its Known Exploited Vulnerabilities catalog on 26 October.
- **Highest-risk runtime**: Apache mod_php, where an attack has been demonstrated to succeed right after a request is accepted, before any application code runs.
- **Configuration mitigations are incomplete.** The vendor states plainly that updating is the only complete fix.
- **Check**: the PHP version your web server runs (not the CLI), whether your OS vendor has taken the fix, any end-of-life version, and signs of compromise.

## Facts confirmed in official sources

| Item | Content | Source |
| --- | --- | --- |
| Published | 21 October 2026 (fixed releases and advisories) | php.net, GitHub Security Advisories |
| CVE-2026-109618 | "RCE via OOM corruption": out-of-memory (OOM) handling corrupts partly initialized internal state | GHSA-w8hw-w9fx-j79w |
| CVE-2026-109619 | "RCE via stale global state": internal state that survives across requests is used | GHSA-6wmf-4rv5-p3rp |
| Severity | Both GHSAs are "critical". CVSS v4.0 base score is 9.2 for both (AV:N/AC:H/AT:P/PR:N/UI:N) | both GHSAs |
| Weakness type | CWE-416 (use after free) among others; both carry the same classification | both GHSAs |
| Fixed versions | 8.2.35, 8.3.36, 8.4.27, 8.5.12 (anything earlier in that branch is affected) | both GHSAs, php.net |
| Exploitation | On CISA KEV since 2026-10-26 (due 10-29). Use in ransomware: "unknown" | CISA KEV |
| Discoverers | Researchers at Calif, OpenAI and Wiz are credited as reporters in the GHSAs | both GHSAs |

Three notes on reading these:

1. **The scores are CVSS v4.0.** In the GHSAs the author checked, no v3.x score is attached. If you see a v3.1 score elsewhere, check the source and who scored it.
2. **"High attack complexity" (AC:H) does not mean "safe".** It reflects a dependence on memory layout; demonstrations and observed exploitation both exist.
3. **How it was found**: according to the researchers' write-up, a memory-limit issue was reported in 2020 and rediscovered in a 2024 external audit, but **it was not fixed because exploitation had not been shown**. It illustrates how hard it is to decide on "fix only once exploitability is proven".

## Status by runtime (SAPI): separate "demonstrated" from "can be affected"

The most important thing in reading the two CVEs is how the situation differs by runtime. The GHSAs' statements, as a table:

| Runtime | CVE-2026-109618 (OOM) | CVE-2026-109619 (stale state) |
| --- | --- | --- |
| Apache mod_php | **Demonstrated.** Unauthenticated RCE over HTTP validated against prefork setups with near-default settings; succeeds before application code runs | **Demonstrated** (the PHP 8.5 fatal-error backtrace path and the session upload-progress path) |
| PHP-FPM | If request startup fails the child exits, so that path is hard to use. **Post-startup paths are susceptible per source analysis, but no complete FPM attack has been demonstrated** | The session upload-progress path was **validated on FPM too**, depending on an application operation. INI-setting paths are reachable conditionally, only when the frontend passes PHP_VALUE |
| FrankenPHP | Like mod_php it calls request shutdown unconditionally, so it is **likely susceptible** (source analysis) | No early reachable path found in the default configuration; that is not immunity |
| LSAPI (LiteSpeed) | Resembles FPM; application-specific attacks are **likely but unvalidated** | A conditional candidate via environment-supplied INI values |
| CLI | The advisories do not address the CLI (author's inference: no web input path, so mostly out of scope — but update it wherever the same PHP also serves the web) | same |

The PHP project says the exposed population is "all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed", and rates **mod_php as highest risk**.

## Background: how PHP handles a request

### A hotel housekeeper

A PHP worker (the process that handles requests) is like **a hotel housekeeper**.

1. A guest arrives (a request starts)
2. The housekeeper prepares a room for that guest (allocates memory for that request)
3. The guest's needs are handled (PHP code runs)
4. When the guest leaves, the room is cleaned and **reset for the next guest** (request shutdown)

The key point: **one housekeeper serves many guests in turn**. The housekeeper keeps notes (global state) that survive from one guest to the next. If a note still says "the key to room 3 is here" after room 3 has been demolished, the next guest arrives and the housekeeper acts on **a key to a room that no longer exists**.

The two PHP2Shell vulnerabilities are two different forms of this "cleanup failure":

- **109619**: after a guest leaves, a note **still points at the demolished room** and gets used before the next guest is prepared
- **109618**: an **emergency stop** hits partway through preparing a room, and the cleaner then works as if "everything is in place" in a half-furnished room

### Terms

- **Zend Engine**: the core of PHP. It interprets PHP code and also manages memory.
- **Request lifecycle**: start-up → execution → shutdown.
- **OOM (out of memory)**: in PHP this happens when `memory_limit` (default 128M) is exceeded.
- **longjmp**: a C mechanism that **jumps straight from where you are to a designated point far away**. The rest of every function in between is **not run**. PHP used it to abort processing on errors.
- **Use after free**: reading or writing freed memory as if still valid. If an attacker can influence what the memory was reused for, the program's behavior can potentially be taken over.

## Why longjmp differs from normal control flow

Normally, when a function finishes, it runs its **cleanup code** (for example "release the memory I allocated", "restore the state") before returning to its caller.

```text
function A → function B → function C
         ↑ C finishes → B cleans up → A cleans up   (normal)

function A → function B → function C ── longjmp ──▶ a safe point outside A
                          (the cleanup of B and C is skipped entirely)
```

A longjmp skips the cleanup of every function it jumps over, so the program's internal state is left **mid-way**. If something reads that mid-way state later, there is a problem.

### The invariants that were broken

An "invariant" is **a rule that should hold at every point in the program**. For example: "in a linked list, every element's `buf` is either allocated or NULL".

- **The rule 109618 broke**: "whenever request memory becomes reachable (to cleanup code), its contents are fully initialized"
- **The rule 109619 broke**: "when a request ends, every global that points into that request's memory has been reset"

For 109618 the GHSA says request memory is temporarily reachable before it is fully initialized; if an allocation fails then, the early exit is not accounted for and invalid state is left behind.

## Reading the patch: what changed and how

The PHP fix is in the `php-src` commit **`506e87fa7dcd` "Avoid state corruption via bailout"** (18 October 2026, PHP-8.4 branch). Its message has two lines:

> - Replace bailout on OOM with UnwindExit
> - Properly reset various globals that may be accessed during startup

On the PHP-8.4 branch the commit changes **over 300 files** (GitHub's comparison view caps at 300, so the author did not confirm the exact count). The parts the author actually read are the diff statistics, the allocator core (`Zend/zend_alloc.c`) and the session extension (`ext/session/session.c`). Most of the change is **tests** that deliberately trigger out-of-memory.

### Fix 1: do not longjmp on out-of-memory (`Zend/zend_alloc.c`)

Before the fix, exceeding the memory limit called `zend_mm_safe_error()`, which raised an error and called `zend_bailout()` (an exit via longjmp).

After the fix:

| Situation | Before | After |
| --- | --- | --- |
| Normal limit (`memory_limit`) exceeded | longjmp out immediately | **Delayed OOM**: record "pending", set the VM interrupt flag and **carry on** |
| Next safe point (VM interrupt check) | — | `zend_handle_delayed_oom()` leaves through a proper unwinding exception (`UnwindExit`) |
| A second, higher (hard) limit exceeded | — | Prints "Hard memory limit of … bytes exhausted" to stderr and **kills the process with `_exit(137)`** |

The point is that **"jump right here" became "record it, and stop at a safe point"**. If memory use runs away past the hard limit, the process ends with no cleanup at all — a deliberately blunt last resort to keep broken state from being read by anyone.

A new setting, `memory_hard_limit_percent`, was added as well. As far as the author read, the default is unset (`-1`), in which case the limit is about 200% of `memory_limit`, or `memory_limit` + 1 GB when `memory_limit` exceeds 1 GB. Values below 100 (other than `-1`) are rejected with a warning.

> **Operational impact (author's reading)**: on reaching the hard limit PHP does not show a normal error page — the **process exits immediately (status 137)**. With FPM a child dies and the master restarts it; with mod_php an Apache child dies. After updating, add monitoring for "Hard memory limit" in logs and for an increase in abnormal worker exits.

### Fix 2: always reset request-owned state (`ext/session/session.c` and others)

In the session extension's diff you can see:

- `PS(in_rfc1867_update) = false;` added to per-request initialization (`php_rinit_session_globals`)
- A stale TODO removed: "These should be initialized by `php_rshutdown_session_globals()` always when execution is finished"
- The upload-progress handler (which runs **during request startup**, before normal initialization) now keeps objects as incomplete classes, so no user code can run

So **what was fixed is that even if cleanup is skipped, the next request's initialization does not trust stale state.** The commit message's "Properly reset various globals that may be accessed during startup" refers to this.

### The invariants the fix protects

| # | Invariant | Mechanism |
| --- | --- | --- |
| 1 | Do not leave partway through initialization | Replace longjmp with delayed OOM and unwinding at a safe point |
| 2 | If memory use runs away, stop without letting anyone read broken state | Hard limit and `_exit(137)` |
| 3 | Do not keep references to old memory across requests | Reset every global at request start and end |
| 4 | Work that runs before initialization must not touch untrusted state | Limit work during startup and run no user code |

### Why happy-path tests are not enough

Bugs like this live in **code that runs only on error paths** (out of memory, fatal errors). Ordinary tests assume memory is available, so they never reach these paths.

The commit adds a large number of out-of-memory tests, such as `Zend/tests/oom_*.phpt`. A code comment explains that `ZEND_MM_MAX_ALLOCS` **fails the n-th allocation of a request**, so the OOM handling can be exercised at **any allocation**, not only at allocations big enough to hit the limit.

This idea carries over to ordinary applications: a test that **fails the n-th call** (fault injection) exercises error paths that are normally never run.

## A safe teaching example: watching memory lifetimes with AddressSanitizer

Here is the same *class* of bug in small C programs. To repeat: it is **not PHP code and not a reproduction of PHP2Shell**. It just lines up three ingredients — per-request memory, a global reference to it, and longjmp — in simplified form. Run it in an isolated environment of your own.

### Example 1: a global reference outlives the freed region (the 109619 class)

```c
/* Teaching code: per-request memory and a global pointer into it */
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static jmp_buf bailout;
static char *arena;        /* per-request memory */
static char *cached_name;  /* a global that outlives the request */

static void *req_alloc(size_t n, size_t limit) {
  if (n > limit) longjmp(bailout, 1);   /* "memory limit exceeded": jump out */
  return arena;
}

static void handle(const char *input, size_t limit) {
  cached_name = req_alloc(16, limit);
  strcpy(cached_name, "session-A");
  req_alloc(strlen(input) * 1000, limit);   /* may jump out here */
  cached_name = NULL;                       /* runs only on success */
}

static void request(const char *input, size_t limit) {
  arena = malloc(64);
#ifdef FIXED
  if (setjmp(bailout) == 0) handle(input, limit);
  cached_name = NULL;                       /* fix: reset at the boundary, on every path */
#else
  if (setjmp(bailout) == 0) handle(input, limit);
#endif
  free(arena);                              /* request end: release the region */
}

int main(void) {
  request("x", 1u << 20);                   /* a normal request */
  request("a-very-long-input", 64);         /* failure path: jumps out midway */
  if (cached_name) printf("next sees: %s\n", cached_name);   /* reads freed memory */
  else puts("next sees: (cleared)");
  return 0;
}
```

```bash
clang -g -O0 -fsanitize=address lifetime.c -o bug
clang -g -O0 -fsanitize=address -DFIXED lifetime.c -o fixed
./bug     # aborts, reporting a read of freed memory
./fixed   # exits normally
```

**Result of the author's run** (Apple clang 21.0.0, `-fsanitize=address`):

- Before the fix: `AddressSanitizer: heap-use-after-free`, with stack traces for **where it was freed** (`free(arena)` in `request`) and where it was allocated
- After the fix (`-DFIXED`): prints `next sees: (cleared)` and exits with nothing detected

What to take from it:

- The cause is that **the reset exists only on the success path** — `cached_name = NULL;` is not run once longjmp jumps out
- The fix is to **reset at a boundary every path goes through** (this mirrors PHP's fix 3)

### Example 2: jumping out mid-initialization, then cleanup reads broken state (the 109618 class)

```c
/* Teaching code: add to a list, then jump out before initializing the contents */
struct node { struct node *next; char *buf; };
static struct node *head;      /* the list walked at request end */

static void add_header(size_t len) {
  struct node *n = malloc(sizeof *n);    /* contents uninitialized */
  n->next = head; head = n;              /* published to the list too early */
  n->buf = req_alloc(len);               /* may jump out here */
}

static void shutdown_request(void) {     /* always runs */
  for (struct node *n = head, *next; n; n = next) {
    next = n->next;
    free(n->buf);                        /* trusts that buf is valid or NULL */
    free(n);
  }
}
```

Example 2 is an excerpt. `req_alloc` here is assumed to be a one-argument version of example 1's function, without the limit parameter. The `-DFIXED` variant and `main` are omitted, so this excerpt alone cannot reproduce the results below.

The fixed version (`-DFIXED`) **records** an allocation failure instead of jumping out on the spot, and handles it at the next safe point (the same idea as PHP's fix 1).

**Result of the author's run**:

- Before the fix: ASan reports `SEGV on unknown address` (in `shutdown_request`, at the `free(n->buf)` line)
- After the fix: prints "OOM handled at a safe point" and "shutdown completed", then exits normally

This shows another principle: **finish initializing before you publish** (for example, fill in the fields before putting the node on the list).

### Limits of the teaching code

- It is a **conceptual model**. It does not reproduce PHP's allocator (`zend_mm`) or the conditions an actual exploit needs
- Behavior changes with optimization, OS and allocator (the ASan results are observations in the author's environment)
- **Making arbitrary code run** is deliberately not reproduced here, and should not be

## For operators: checking whether you are affected

### Step 1: Identify the PHP your web server runs

`php -v` on the CLI and **the PHP serving the web can differ** (several versions side by side, containers, per-pool FPM versions).

```bash
# CLI version (may differ from the web)
php -v

# FPM version and running processes
php-fpm -v 2>/dev/null; ps aux | grep -E 'php-fpm|php-cgi|frankenphp|lsphp' | grep -v grep

# Is mod_php loaded into Apache?
apachectl -M 2>/dev/null | grep -i php
```

To know the version the web really runs, **execute PHP on that server** and print the version. Do it by an administrator-only means, and never publish a page that leaks version information.

### Step 2: Check OS-vendor backports

On Debian, Ubuntu, RHEL and similar, a fix can be folded in **without the version number rising**. Don't conclude "unpatched" from the number alone.

```bash
# Debian/Ubuntu: does the changelog name the CVE?
apt changelog php8.3-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'

# RHEL family
rpm -q --changelog php-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'
```

If nothing turns up, check your distribution's official security tracker for whether your version is fixed.

### Step 3: Look for end-of-life versions

- **PHP 8.1 and older** are outside the PHP project's updates. Upgrade, or use a vendor LTS with backports
- Given the severity, the PHP project provided **minimal source backports** for PHP 7.4, 8.0 and 8.1 (a stopgap if you build from source or have no LTS source)
- Cisco Talos notes that PHP 8.2 security support ends on 31 December 2026. Use this as the occasion to plan a move to 8.3 or later

### Step 4: Interim mitigations if you cannot update yet, and their limits

Updating comes first; **the following only buys time**. The vendor too says they reduce exposure to specific paths and do not make an installation safe.

| Goal | Setting | Caveat |
| --- | --- | --- |
| Reduce 109618 paths | `memory_limit=-1` (set where applications can't override it), a low `post_max_size`, size and field-count limits at the proxy | `memory_limit=-1` is **not complete**: if the underlying allocation fails, the vulnerable path remains. **Do not use `RLIMIT_AS` or `RLIMIT_DATA`** (they induce allocation failures). Also, `memory_limit=-1` removes the per-request memory ceiling, so one request can exhaust the host's memory; pair it with a cgroup or container limit and treat it as temporary until you update |
| Close the confirmed 109619 path | `session.upload_progress.enabled=0` (php.ini, FPM pool or server config; restart all workers). **Don't rely on `ini_set()` or `.user.ini`** | Does not close other paths |
| 109619 (PHP 8.5) | `fatal_error_backtraces=0`, restart all workers | Only the one confirmed path |
| Reduce what is left of 109619 | Remove attacker-reachable fatal errors; `pm.max_requests=1` if you can give up performance | The vendor states that no complete configuration mitigation **exists** |
| CVE-2026-115560 | `session.upload_progress.enabled=0` | The same setting as the 109619 mitigation |

Verify a setting against **what the web workers actually read**:

```bash
# CLI settings (may differ from the web workers)
php -i | grep -E 'session.upload_progress.enabled|memory_limit|fatal_error_backtraces'

# php.ini values FPM actually loads (closest to the web workers)
php-fpm -i | grep -E 'session.upload_progress.enabled|memory_limit|fatal_error_backtraces'

# Per-pool overrides (php_value / php_admin_value). php.ini values do not appear in -tt
php-fpm -tt 2>&1 | grep -E 'php_(admin_)?(value|flag)'
```

### Step 5: Look for signs of compromise

JPCERT/CC, citing the discoverers, lists the following to check. **No single detection rule is guaranteed to catch every attack.**

- Past HTTP responses that set a `PHPSESSID` cookie with an unnaturally long or malformed value
- Suspicious `multipart/form-data` requests, and PHP memory-limit errors or HTTP 500s around them
- PHP workers exiting unexpectedly or restarting frequently

The **absence** of a trace in logs is not proof of safety; the vendor likewise says log audits only cover the paths that were exercised.

In its 29 October update JPCERT/CC conveys reports that, in an investigation of attack activity, traces suggesting successful compromise of a specific version of a **firewall product** were found; that the attacker appears to have combined the PHP vulnerability with a Linux kernel privilege-escalation vulnerability; and that attempts at lateral movement into internal networks are suggested. **Appliances and products that embed PHP**, not only websites, are in scope.

## For executives: what to decide, in what order

For non-engineers, the order of decisions:

1. **Today**: list every server and service that runs PHP (ask staff or contractors). The less the IT department knows about something — an old admin screen, a contractor-built site, an appliance's embedded web UI — the more dangerous it is.
2. **This week**: set update priorities. Start with what is **internet-facing, on an old PHP version and handling customer data**. Anything run via mod_php comes first.
3. **At the same time**: get **written** confirmation of patch status from contractors and hosting providers. On shared hosting you depend on the provider.
4. **After**: investigate for compromise **together with** the update. The update closes the hole, but **an intruder who is already in stays in**.
5. **Medium term**: put the migration off PHP 8.1 and older, and off 8.2 as its support ends, in the budget.

"We only run WordPress, so we're fine" does not hold: **if the PHP runtime is unpatched, you can be affected even when WordPress itself has no vulnerability**.

## Frameworks: Laravel and WordPress

| Point | Position |
| --- | --- |
| Where the flaw is | The PHP runtime, not Laravel's or WordPress's code |
| Impact | An app on that PHP can be affected regardless of any framework vulnerability |
| Fix | Update PHP to a fixed version. Updating the framework does not fix this |
| Separate matter | Framework- or plugin-specific vulnerabilities are tracked separately |

Because 109619 notes that fatal errors can be part of the path, fatal errors caused by plugins or extensions (for example in upload, import, error-page or shutdown handling) also matter, so **reducing the fatal errors an attacker can reach** remains a useful layer of defense after updating.

## How CVE-2026-115560 relates

- It is a **separate vulnerability**, disclosed by php.net on 23 October as an informational advisory
- It applies only in an **unusual combination**: `session.serialize_handler` configured as `php_binary` or `php_serialize`, the app switching to `php` with `ini_set()` at run time and calling `session_start()`, with session upload progress enabled
- php.net calls this setup unlikely in practice, will fix it in the **regular release cycle**, and promises a full advisory with the next release
- When the author checked on 31 October 2026, **no CVE record was public** at cve.org or NVD (a "does not exist" response)
- **Whether this release (8.2.35 and the others) fixes it is not stated as "fixed" in the vendor's text.** The vendor's interim mitigation is `session.upload_progress.enabled=0`

Do not treat the two PHP2Shell CVEs and 115560 as "the same vulnerability". Nor can you say they are "unrelated": both involve session upload progress. **Until a new advisory appears, keep the configuration mitigation in place.**

## The author's view: what to learn from this

The following is the author's reading of the published facts.

1. **"Fix only once exploitability is proven" is a dangerous criterion.** The researchers say an issue reported in 2020 and flagged in 2024 stayed unfixed until exploitation was shown. **Corruption of state on error paths** can look harmless alone and become severe in combination with other bugs.
2. **Happy-path tests do not find this kind of bug.** What the fix added were **tests that inject failure**. In your own code, tests that "fail the n-th external call" or "fail a memory allocation" run the cleanup paths that normally never execute.
3. **The speed of updating decides the size of the damage.** With exploitation observed, "wait and see" is not an option. Practising updates **in peacetime** determines the loss on days like this.
4. **PHP versions typically differ from server to server.** Organizations without a complete asset inventory are the ones who miss things.

## Conclusion and what to watch next

- **What can be said as fact**: two separate CVEs were fixed in PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Both are on KEV and exploitation has been observed. The demonstrated RCE is mainly on mod_php, with conditional paths validated on FPM.
- **What cannot yet be said**: whether attacks succeed on every runtime, and the real scale of damage. Whether a complete attack on 109618 is possible on FPM. The details of CVE-2026-115560.
- **To watch**: the 115560 advisory and CVE, the next PHP release, distribution backport status, and updates from JPCERT/CC and IPA.

## If you want a vulnerability assessment or code audit of your own service

The author assesses the security of web applications and cloud environments from production experience with Next.js, Python, Go and AWS. **Updating PHP closes these holes, but problems left in the application itself — authorization flaws, misconfiguration, secret handling — need separate review.**

What I can and cannot do, up front:

- **I can**: perform source-and-configuration-based web application vulnerability assessments and code audits, with a prioritized fix plan and re-verification after fixes
- **I cannot**: perform **forensic investigation** after a compromise, or discover vulnerabilities inside PHP itself. If you suspect compromise, contact a specialist incident-response firm first
- I cannot promise you will be "completely secure". I report what was found and what was not examined, as it is

See the scope and pricing through the button below.
