In October 2026, the cybersecurity world was shaken by a critical vulnerability in PHP tracked as CVE-2026-109618 (alongside its twin flaw, CVE-2026-109619)—collectively nicknamed “PHP2Shell.”

With a severity score of 9.2 to 9.8 (Critical) and affecting every version of PHP from 5.4 up to 8.5.11 (more than a decade of PHP releases), this bug allows an unauthenticated attacker on the internet to take control of a vulnerable web server just by sending specially crafted web requests.

Even scarier? Your own PHP website code can be 100% secure and bug-free, and your server is still vulnerable. That is because the flaw lives inside the PHP engine itself—specifically, in how PHP cleans up after itself when it runs out of memory.

If you have read the official security advisories and felt lost in terms like “non-local longjmp() control-flow diversion,” “uninitialized memory during OOM bailout,” and “use-after-free in stale global states,” this post is for you. We will break down exact technical details of CVE-2026-109618 in plain English and give you a step-by-step guide to fixing your servers today.


The 60-Second Summary (TL;DR)

Question Plain-English Answer
What is CVE-2026-109618? A critical bug in the core PHP engine (part of “PHP2Shell”) where running Out of Memory (OOM) at the exact wrong microsecond causes PHP’s cleanup crew to read leftover garbage data in memory and execute an attacker’s commands (Remote Code Execution / RCE).
Does it require a login? No. An attacker can trigger it remotely over the internet without logging in.
Which PHP versions are vulnerable? PHP 5.4 through PHP 8.5.11 running on Apache (mod_php), Nginx + PHP-FPM, php-cgi, FrankenPHP, or LiteSpeed.
Which versions are safe? PHP 8.5.12, PHP 8.4.27, PHP 8.3.36, and PHP 8.2.35 (or newer).
What if I run PHP 8.1 or older? PHP 8.1 and older are End-of-Life (EOL) and will not receive an official PHP.net patch (unless backported by your Linux distro like Ubuntu/Red Hat). You should upgrade to a supported branch or apply OS vendor security updates immediately.

Understanding CVE-2026-109618: The Restaurant Kitchen Analogy

Before looking at computer memory, imagine how a busy restaurant kitchen handles a customer’s dinner order:

  1. Step 1 — Setting Up the Clipboard: When a new order arrives, the manager grabs a blank clipboard and writes down which cooking stations are being used so the cleaning crew knows what to wipe down after the meal is served.
  2. Step 2 — Cooking the Meal: The chefs cook the dish.
  3. Step 3 — The Cleanup Crew: When the meal is done, the cleanup crew reads the clipboard and cleans every station listed on it.

Now, imagine the kitchen has an emergency rule: if the pantry runs completely out of ingredients mid-order, hit the red Emergency Abort button and send the cleanup crew in immediately so the kitchen is ready for the next customer.

Here Is Where the Bug Happens

Suppose a manager grabs a clipboard that still has erased/scribbled notes from the previous customer on it.

  1. The manager checks the box at the top: [x] Needs Cleanup!
  2. Before the manager can erase the old scribbles and write the real station address, the manager asks the pantry for one more ingredient—and the pantry runs out of stock (Out of Memory)!
  3. BEEP! The red Emergency Abort button fires immediately. The manager drops the pen and walks away.
  4. The Cleanup Crew walks in, sees [x] Needs Cleanup!, and blindly follows the old scribbles still sitting on the clipboard.

If a clever attacker was the previous customer and deliberately wrote “Unlock the back door and hand over the keys” on that spot of the clipboard, the cleanup crew will dutifully obey it!


Now the Real Technical Details (Made Simple)

Now let’s translate that restaurant kitchen directly into how the PHP Engine (written in C) actually works under the hood.

flowchart TD
    A["1. Attacker Sends Crafted Web Requests to Fill Memory"] --> B["2. PHP Starts Setting Up an Internal Data Structure"]
    B --> C["3. PHP Allocates Memory Before Finishing Setup — Hits Out-of-Memory (OOM)!"]
    C --> D["4. Emergency Eject: longjmp() Skips the Rest of the Setup Code"]
    D --> E["5. PHP Request Shutdown Runs to Clean Up Memory"]
    E --> F["6. Shutdown Routine Reads Uninitialized Pointer Data Left in RAM"]
    F --> G["7. Attacker Controls That RAM Data -> Remote Code Execution (RCE)"]

If your browser does not render Mermaid diagrams, here is the same flow in plain text:

+-------------------------------------------------------------------+
| 1. Attacker sends web requests designed to push PHP right up to   |
|    its memory_limit                                               |
+---------------------------------+---------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
| 2. PHP begins creating an internal structure in memory, but       |
|    hasn't finished filling in all its fields yet                  |
+---------------------------------+---------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
| 3. PHP asks for a tiny bit more memory -> OUT OF MEMORY (OOM)!    |
+---------------------------------+---------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
| 4. Emergency Eject: PHP calls longjmp() (zend_bailout) to         |
|    immediately abort the request and jump to the cleanup stage    |
+---------------------------------+---------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
| 5. The Bug: Because longjmp() skipped the lines that finished     |
|    setting up the structure, the structure still contains         |
|    leftover, uninitialized bytes from earlier requests            |
+---------------------------------+---------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
| 6. PHP's Cleanup Crew (Request Shutdown) trusts those leftover    |
|    bytes as real memory pointers -> Attacker hijacks the server!  |
+-------------------------------------------------------------------+

Let’s break down the three technical pieces mentioned in the CVE report:

1. What is longjmp() (PHP’s Emergency Eject Button)?

In the C programming language (which PHP is written in), when a normal error happens, functions return an error code step by step.

However, when PHP hits a fatal Out-of-Memory (OOM) error—meaning a request exceeded memory_limit—PHP cannot keep running normal code because there is no memory left. Instead, PHP calls an internal function named zend_bailout(), which uses a C feature called longjmp() (Long Jump).

longjmp() acts like a teleportation button: it immediately abandons whatever line of code PHP was in the middle of running and teleports straight to the Request Shutdown / Cleanup phase so the PHP worker process can survive and handle the next web visitor.

2. What Went Wrong in CVE-2026-109618?

In several places inside PHP’s core C code, PHP prepared an internal structure in the wrong order:

// ❌ VULNERABLE PATTERN (Conceptual Simplified Example)

// 1. Grab a slot in memory (still contains leftover bytes from an older request!)
my_struct = allocate_struct_slot();
Globals.active_struct = my_struct; // Marked as active BEFORE it is initialized!

// 2. Ask for memory for its contents...
//    BOOM! If Out-of-Memory happens here, longjmp() teleports away immediately!
my_struct->buffer = emalloc(size);

// 3. These lines NEVER RUN if step 2 ran out of memory!
my_struct->cleanup_callback = NULL;
my_struct->is_ready = 1;

Look closely at what happens if Step 2 runs out of memory:

  1. Globals.active_struct is already pointing to my_struct.
  2. longjmp() teleports straight to the cleanup routine, skipping Step 3.
  3. my_struct->cleanup_callback was never set to NULL—it still holds whatever raw bytes were left in that memory address from a previous request!
  4. When the cleanup routine runs, it checks Globals.active_struct, sees it is non-empty, and tries to clean up or call pointers inside my_struct—giving the attacker Remote Code Execution (RCE).

3. How Did the Patch Fix It?

The fix in the patched PHP versions is remarkably clean: never mark a structure as active (or always initialize its fields to safe defaults like NULL / 0) before calling any function that might trigger an Out-of-Memory longjmp():

// ✅ PATCHED PATTERN (Conceptual Simplified Example)

// 1. Grab the slot and IMMEDIATELY zero/null out its fields first
my_struct = allocate_struct_slot();
my_struct->cleanup_callback = NULL;
my_struct->buffer = NULL;

// 2. Now even if emalloc() hits Out-of-Memory and longjmp() teleports to cleanup,
//    every field is safely NULL!
my_struct->buffer = emalloc(size);
Globals.active_struct = my_struct;

Bonus: What About Its Twin Bug, CVE-2026-109619?

Security advisories group CVE-2026-109618 together with CVE-2026-109619 under the name PHP2Shell because they were discovered together (in August 2026, publicly disclosed in October 2026) and patched in the exact same update.

Here is the difference in one sentence:

  • CVE-2026-109618 (Uninitialized Memory on OOM): PHP jumps to cleanup during an Out-of-Memory error before finishing setup, causing the cleanup crew to read uninitialized leftover memory within the same request shutdown.
  • CVE-2026-109619 (Use-After-Free Across Requests): After a request finishes and its memory is freed, a global pointer inside the PHP worker isn’t properly reset to NULL. When the next visitor’s request arrives at that same PHP worker, it accidentally uses the old, freed memory pointer (Use-After-Free).

Both bugs are fixed together when you upgrade PHP.


How to Fix CVE-2026-109618 Step-by-Step

Step 1: Check Your Current PHP Version

First, check what version of PHP your server is running.

Run this in your terminal:

php -v

Important Tip: Sometimes the command-line php -v is different from the PHP version used by your web server (Apache or Nginx/PHP-FPM)! Check your running web processes too:

# Check running PHP-FPM processes
ps aux | grep php-fpm

# Or check via Apache module (Debian/Ubuntu)
apache2ctl -M | grep php

Compare your version against the official patched version table below:

Your PHP Branch Vulnerable Versions Safe / Patched Version Status
PHP 8.5 8.5.0 – 8.5.11 8.5.12 or higher Active — Upgrade immediately
PHP 8.4 8.4.0 – 8.4.26 8.4.27 or higher Active — Upgrade immediately
PHP 8.3 8.3.0 – 8.3.35 8.3.36 or higher Active — Upgrade immediately
PHP 8.2 8.2.0 – 8.2.34 8.2.35 or higher Security Support — Upgrade immediately
PHP 5.4 – 8.1 All releases No upstream PHP.net release End-of-Life (EOL) — Upgrade to PHP 8.2+ or apply OS vendor backports

Step 2: Update PHP on Your Operating System or Container

Choose the instructions matching your setup below:

Option A: Ubuntu / Debian (including Ondřej Surý PPA)

Update your package lists and upgrade all PHP packages:

sudo apt update
sudo apt --only-upgrade install "php*"

(Note: If you use standard Ubuntu LTS or Debian packages, Ubuntu and Debian security teams backport CVE fixes into their packaged PHP versions even if the main version number stays the same. Run apt changelog php8.3-common | grep -E "109618|109619" to verify the CVE patch is present.)

Option B: RHEL / Rocky Linux / AlmaLinux / Amazon Linux

sudo dnf update "php*"

Option C: Docker Containers

Update the base image tag in your Dockerfile to one of the patched releases and rebuild your image:

# Pick the patched version matching your branch:
FROM php:8.5.12-fpm-alpine
# or FROM php:8.4.27-fpm-alpine
# or FROM php:8.3.36-fpm-alpine
# or FROM php:8.2.35-fpm-alpine

Then pull and redeploy:

docker compose pull
docker compose up -d --build

Option D: macOS (Local Development with Homebrew)

brew update
brew upgrade php

Step 3: Restart Your PHP Web Workers (Don’t Skip This!)

A very common mistake is updating the PHP package on disk without restarting the running php-fpm or Apache processes. Until you restart the service, your server is still running the old, vulnerable PHP code in RAM!

# If you use PHP-FPM (replace 8.4 with your version, e.g., 8.3 or 8.5):
sudo systemctl restart php8.4-fpm

# If you use Apache with mod_php (Ubuntu/Debian):
sudo systemctl restart apache2

# If you use Apache on RHEL/Rocky/AlmaLinux:
sudo systemctl restart httpd

Verify that the running service restarted cleanly:

sudo systemctl status php8.4-fpm

Emergency Workarounds (If You Cannot Upgrade Today)

If you are running an older legacy application (for example, on PHP 7.4 or 8.1) and need a few days to test before upgrading to PHP 8.2+, apply these defense-in-depth mitigations right away (note: these reduce risk, but upgrading PHP is the only 100% cure):

  1. Move Away from Apache mod_php to PHP-FPM: PHP maintainers specifically noted that Apache with mod_php is at the highest risk because PHP shares memory space directly inside the Apache worker process. Switching to PHP-FPM isolates PHP workers from the web server.
  2. Limit Request Body and Variable Sizes in php.ini: To trigger CVE-2026-109618, an attacker has to force your PHP worker to run Out of Memory (memory_limit) using crafted requests. Keep incoming request payloads significantly smaller than your memory_limit so an unauthenticated visitor cannot easily exhaust worker RAM in a single request:
    ; Example defensive settings in php.ini
    memory_limit = 256M
    post_max_size = 16M
    upload_max_filesize = 16M
    max_input_vars = 1000
    
  3. Recycle PHP-FPM Workers Frequently (pm.max_requests): Heap grooming and stale-state exploitation (CVE-2026-109618 and CVE-2026-109619) rely on reusing the same worker process across many requests. Lowering pm.max_requests in your PHP-FPM pool config (www.conf) forces workers to restart cleanly with fresh memory after a set number of requests:
    ; In /etc/php/8.x/fpm/pool.d/www.conf
    pm.max_requests = 500
    
  4. Enable WAF Rules (Cloudflare / AWS WAF / ModSecurity): Major cloud WAF providers released managed signatures in October 2026 to detect and block anomalous request floods and memory-exhaustion payloads targeting CVE-2026-109618 and CVE-2026-109619.

References & Official Advisories

  1. National Vulnerability Database (NVD / NIST): Official entries for CVE-2026-109618 (Out-of-Memory longjmp() uninitialized memory RCE) and CVE-2026-109619 (Use-After-Free via stale global state).
  2. PHP.net Official Security Releases (October 2026): Upstream release announcements for PHP 8.5.12, PHP 8.4.27, PHP 8.3.36, and PHP 8.2.35.
  3. CISA & Vendor Security Bulletins: Advisories from CISA, AWS Security Bulletins, Red Hat Security, Ubuntu Security Notices (USN), and watchTowr Labs on PHP2Shell remediation across mod_php, PHP-FPM, php-cgi, FrankenPHP, and LiteSpeed.