End-of-Life PHP, and How to Stop Running It: A Patching and Lifecycle Guide

Every time a critical PHP vulnerability makes the rounds — most recently the PHP2Shell vulnerability — the same line shows up in the advisory: end-of-life branches get no official fix. This post covers both halves of that problem: what PHP’s end-of-life process actually means and why EOL branches stay in production for years past their support window, and how to build a repeatable patch workflow so staying current isn’t a fire drill every time a new CVE lands.

What “end-of-life” means for PHP

Each PHP branch (8.4, 8.3, 8.2, and so on) goes through two support phases before it is retired:

  • Active support (2 years) — bug fixes and security fixes.
  • Security support (1 additional year) — security fixes only, no bug fixes.
  • End of life — no fixes of any kind from the PHP project, for any issue, including remotely exploitable ones.

That gives every branch roughly three years of upstream support from its initial release. After that date passes, the PHP project will not backport a patch for a new CVE to that branch, no matter how severe. The 8.1.2-1ubuntu2.28-style version strings you see in distro changelogs are downstream backports from a vendor’s own security team, not upstream PHP continuing to maintain the branch — once a branch is EOL upstream, every fix you get from then on comes from your distro, not the PHP project.

Current PHP branch status

BranchInitial releaseActive support endsSecurity support ends
8.5Nov 2026Nov 2028Nov 2029
8.4Nov 2024Nov 2026Nov 2027
8.3Nov 2023Nov 2025Nov 2026
8.2Dec 2022Dec 2024Dec 2025
8.1Nov 2021Nov 2023Nov 2024 (EOL)
8.0Nov 2020Nov 2022Nov 2023 (EOL)
7.4Nov 2019Nov 2021Nov 2022 (EOL)

Branches 7.3 and earlier, including the entire 5.x and 7.0–7.2 lines, have been EOL for years. If php -v on one of your hosts reports anything in that range, it is not receiving fixes from upstream PHP under any circumstance.

Why EOL PHP keeps running in production

EOL branches rarely survive by decision — they survive by default. The usual causes:

  • Framework and plugin lock-in. A WordPress plugin, a Laravel app on an old major version, or an in-house codebase that calls deprecated functions removed in later PHP versions blocks the upgrade path until someone budgets time to fix the calling code.
  • Distro-extended support masking the real status. Debian LTS, Ubuntu ESM, and RHEL’s extended lifecycle backport security fixes onto EOL PHP branches for years past their upstream EOL date. That buys time, but it is not the same as the branch being supported — those distro backports only cover issues the vendor chooses to patch, and coverage quietly thins out as the branch ages.
  • No ownership. On servers nobody is assigned to, PHP upgrades fall under “if it isn’t broken” until an incident like PHP2Shell makes it broken.
  • Fear of breaking changes. Each major PHP version removes deprecated functionality (e.g. mysql_* functions removed in 7.0, dynamic class properties deprecated in 8.2). Teams defer the upgrade rather than spend the time auditing for breakage.

Finding EOL PHP across your fleet

Before you can retire anything, you need an inventory. On each host:

php -v
php -i | grep -i 'php version'
rpm -qa 'php*' 2>/dev/null || dpkg -l 'php*' 2>/dev/null

If you are managing more than a handful of hosts, script the collection instead of checking each one by hand — an Ansible ad-hoc command or a one-line SSH loop over your inventory will surface every branch in use far faster than logging into each box:

ansible all -m shell -a "php -v | head -1"

A single php -v is not sufficient on hosts using Remi’s versioned packages. The remi-safe repository is designed to let multiple PHP branches coexist side by side — each versioned package (e.g. php74-php-fpm, php81-php-fpm, php82-php-fpm) installs its own binaries under /opt/remi/phpNN/ and runs as its own service, independently of whichever version /usr/bin/php points to. php -v only reports that one default binary; it says nothing about the other installed branches, and says nothing about which of them are actually enabled and serving requests. On a Remi host, enumerate every installed version and every running instance separately:

rpm -qa 'php[0-9][0-9]-php-fpm' 'php[0-9][0-9]-php-cli'
for v in /opt/remi/php*/root/usr/bin/php; do echo "$v:"; "$v" -v | head -1; done
systemctl list-units --all 'php*-fpm*'

The first command lists every versioned branch installed on the box; the second prints the actual version string for each one (don’t assume the package name’s version suffix matches the patch level); the third shows which of those FPM instances are enabled or actively running, independent of which one the default php command invokes. A branch can be both installed and actively serving traffic through its own FPM pool while remaining completely invisible to a plain php -v check.

Cross-reference every version you find — from every one of these commands, not just the default binary — against the table above. Anything past its security support end date is running without an upstream safety net, whether or not it’s the version php -v happens to report.

Getting off an EOL branch

  1. Target the nearest supported branch, not the newest. Jumping from 7.4 straight to 8.5 compounds every breaking change across five major versions at once. Move one or two majors at a time and test at each step.
  2. Run the branch’s migration guide. The PHP manual publishes a “Backward Incompatible Changes” page for every major version — read the one for each version you are passing through, not just the destination.
  3. Use static analysis to find breakage before runtime does. Tools such as PHPCompatibility (a PHP_CodeSniffer ruleset) flag calls to removed or deprecated functions against a target PHP version without executing the code.
  4. Stand up the new version in parallel, not in place. Run the target PHP version alongside the old one (separate FPM pool, separate container, or a canary vhost) and route a slice of traffic to it before cutting over fully.
  5. Re-point package management at a maintained source if your distro’s default repo lags. RHEL-family systems commonly rely on the Remi repository for current PHP versions; Debian and Ubuntu’s default repos track one branch per release, so a distro upgrade may be required to reach a newer branch.

Once you’re current: a repeatable patch workflow

Landing on a supported branch solves today’s problem. Keeping it supported is an ongoing task. The pattern that keeps a routine patch cycle from turning into an incident response is: know your branch, hear about advisories quickly, patch by a known-good procedure, and verify the fix actually landed.

Know your branch and its support window

Check the running version and cross-reference it against the PHP supported versions page, noting the branch (the first two numbers, e.g. 8.3), not just the full version string — security fixes apply per branch, and a branch past its security support end date will not receive a fix for a new CVE regardless of how recent its last patch was:

php -v

On hosts running multiple Remi-packaged branches side by side, php -v only covers the default binary — see the fleet-inventory commands above for enumerating every installed and running version before you decide what needs patching.

Subscribe to the sources that announce PHP security releases

You want to hear about a critical PHP CVE within hours, not when a scanner flags it weeks later:

  • PHP’s own security advisories — the php.net front page and the php-announce mailing list cover every security release.
  • Your distro’s security advisory feed — Debian Security Advisories (DSA) and Long Term Support Advisories (DLA), Ubuntu Security Notices (USN), and RHEL/AlmaLinux/Rocky errata (ELSA/RHSA) all publish per-package advisories you can subscribe to or pull via dnf updateinfo.
  • CISA’s known exploited vulnerabilities catalog — flags when a CVE moves from “patch when convenient” to “patch today” because active exploitation has been confirmed.

Patching, by distribution

The commands differ by distro, but the pattern is the same: refresh metadata, upgrade the PHP packages, restart the services that embed PHP.

RHEL, AlmaLinux, Rocky Linux (8, 9, 10):

sudo dnf clean metadata
sudo dnf upgrade 'php*'
sudo systemctl restart php-fpm httpd

List pending security errata specifically, rather than upgrading everything, if you want to patch PHP in isolation:

sudo dnf updateinfo list security | grep -i php

If you installed PHP from the Remi repository (common on RHEL-family boxes to get a PHP version newer than the distro default), package names are prefixed per version, e.g. php84-php-fpm:

sudo dnf upgrade 'php84-*'
sudo systemctl restart php84-php-fpm

Debian:

sudo apt update
sudo apt install php
sudo systemctl restart php-fpm apache2

Debian ties its PHP branch to the release (bookworm ships 8.2, trixie ships 8.4). A distro upgrade, not just a package upgrade, is required to move to a newer branch. Debian LTS extends security coverage for the previous stable release; check whether your release is still inside that window.

Ubuntu:

sudo apt update
sudo apt upgrade
sudo systemctl restart php-fpm apache2

As with Debian, the PHP branch is tied to the Ubuntu release. Older LTS releases (14.04, 16.04) only continue receiving PHP security fixes under Ubuntu Pro with esm-infra enabled — verify that subscription is active if you depend on it.

Automating the check, without automating the restart

Checking for pending PHP security updates is safe to automate; applying them to a production web server mid-request is not. A reasonable split:

  • Automate detection. A daily cron job or CI check that runs dnf updateinfo list security / apt list --upgradable and alerts (Slack, email, a monitoring check) when a PHP package has a pending security update.
  • Automate staging rollout. Apply the update to a staging or canary host automatically, run your test suite against it, and only then queue the production rollout.
  • Keep production patching a deliberate, scheduled action — a maintenance window or a rolling restart across your pool — so a patch doesn’t land mid-traffic-spike and so you control the order services restart in.

Verifying a patch actually landed

Most enterprise distros backport the security fix into the existing package version rather than bumping the upstream version number, so php -v can keep showing the same string before and after you’ve patched. Don’t use the version string alone to confirm a fix:

  • Check the package’s installed version against the distro advisory’s “fixed in” version, e.g. rpm -q php or dpkg -s php8.2-fpm | grep Version compared against the advisory.
  • Read the advisory’s changelog entry for the CVE ID directly — it will name the exact package version that contains the fix.
  • For a configuration-level mitigation (rather than a package patch), confirm the live setting with php -i | grep or phpinfo() against a running worker, not just the config file on disk — a worker that hasn’t restarted is still running the old setting.

Summary

  • PHP branches get roughly three years of upstream support total, then no fixes of any kind, for any severity.
  • Distro-extended support (Debian LTS, Ubuntu ESM, RHEL ELS) can mask how old a branch really is — check the upstream EOL date, not just whether patches are still arriving.
  • Inventory every host’s PHP version, prioritize anything past security support end-of-life, and migrate one major version at a time with static analysis and parallel testing.
  • Once current, know your branch’s support window, subscribe to advisory feeds, automate detection of pending updates, and verify fixes against the distro advisory rather than an unchanged version string.
  • Treat every PHP2Shell-style incident as a forcing function, not a one-off, until EOL branches are actually gone from your fleet and patching is routine rather than reactive.

Sources

PHP2Shell: Inside CVE-2026-109618 and CVE-2026-109619, and How to Patch Your Servers

A pair of critical PHP vulnerabilities, collectively known as the PHP2Shell vulnerability, leaked ahead of their coordinated disclosure this morning — if you run anything on PHP, this one belongs at the top of your patch queue.

What happened in the PHP2Shell vulnerability

On October 21, 2026, the PHP Foundation disclosed two critical vulnerabilities behind the PHP2Shell vulnerability:

  • CVE-2026-109618
  • CVE-2026-109619

Both are heap use-after-free bugs rated CVSS 9.2 (one security vendor, watchTowr, scored it a full 10.0). Disclosure leaked roughly 26 minutes before the planned release window, and researchers report active exploitation attempts beginning within hours.

Root cause

Both CVEs share a single underlying mechanism. When PHP hits a fatal condition — exhausting memory_limit, exceeding max_execution_time, or losing the client connection — it doesn’t unwind the call stack cleanly. Instead it raises a fatal error and calls longjmp() to abandon the current execution context outright. Any C function caught mid-operation while building a data structure is simply discarded, leaving corrupted memory behind.

Because this corruption happens while PHP is still assembling internal request variables — before extension initialization and before any application code runs — every reachable PHP endpoint is attack surface, including static forms, login pages, and health checks. The most actively exploited path is session.upload_progress.

Who is affected

  • All PHP installations that serve web requests: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed.
  • A working exploit exists against mod_php configurations where an unauthenticated request can trigger the bug before application code executes.
  • End-of-life branches (PHP 8.1 and earlier, including 5.6, 7.x) will not receive an official fix. If you’re still running one of these, read the mitigation section below — it’s your only option until you migrate.

Patched versions (upstream PHP)

BranchPatched version
8.58.5.12
8.48.4.27
8.38.3.36
8.28.2.35

PHP’s own security team additionally published minimal source backports for the already-EOL 7.4, 8.0, and 8.1 branches — no official packaged release exists for them, so distro vendors (below) are the ones shipping a usable fix. Ondřej Surý’s Debian/Ubuntu PPA builds and Remi’s RHEL-family repository (see below) are currently the two most complete sources for those EOL backports; both describe theirs as a minimal mitigation patch — it disables upload-progress tracking and removes the hard memory_limit ceiling at the C level — rather than a full fix of the underlying use-after-free.

Important: most enterprise and LTS distributions backport the security fix into their existing package version rather than bumping to the upstream version number above. That means php -v may keep showing an older string (e.g. 8.2.24 or 8.1.2-1ubuntu2.21) even after you’re fully patched. Don’t use the version string to judge patch status — check your distro’s advisory/changelog instead.

How to update, by distribution

RHEL, AlmaLinux, Rocky Linux (8, 9, 10)

sudo dnf clean metadata
sudo dnf upgrade 'php*'
sudo systemctl restart php-fpm httpd

AlmaLinux shipped patched packages to its production repos (no testing repo needed) within hours of disclosure. Rocky Linux and Oracle Linux (via ELSA errata) are expected to follow the same backport pattern; check each project’s errata page directly, since the exact advisory ID wasn’t published at the time of writing — dnf updateinfo list security (AlmaLinux/RHEL) will show it once available.

If you installed PHP from Remi’s repository (common on RHEL-family boxes for non-default PHP versions), the package names are prefixed per version, e.g. php84-php-fpm:

sudo dnf upgrade 'php84-*'
sudo systemctl restart php84-php-fpm

Remi ships the same upstream 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 for those branches. For the EOL branches, Remi backported the mitigation patch into the existing package version rather than bumping it, so php -v will not show a new release number. Confirmed fixed Remi builds at the time of writing:

  • PHP 8.1 — 8.1.34-6
  • PHP 8.0 — 8.0.30-21
  • PHP 7.4 — 7.4.33-31

Remi has stated 7.2 and 7.3 backports are forthcoming. Verify any Remi-sourced package directly from the changelog rather than trusting the version string:

rpm -q --changelog php-common | grep CVE-2026-109618

Native Red Hat Software Collections / AppStream module builds (i.e. PHP installed from Red Hat’s own channels rather than Remi, EPEL, or SCL) are a separate question from Remi’s packages: at the time of writing, confirm your specific stream (7.4 on RHEL 8, 8.0/8.2/8.3 on RHEL 9) against Red Hat’s own errata before assuming it is covered, since not every native module had a published backport yet.

Debian

sudo apt update
sudo apt install php
sudo systemctl restart php-fpm apache2
  • Debian 12 (bookworm), branch 8.2 — fixed in 8.2.35-1~deb12u1 (DLA-4848-1)
  • Debian 13 (trixie), branch 8.4 — fixed in 8.4.27-1~deb13u1 (DSA-6574-1)

Ubuntu

sudo apt update
sudo apt upgrade
sudo systemctl restart php-fpm apache2

Per Ubuntu advisory USN-8948-1:

  • 14.04 (Trusty, ESM) — php5 fixed in 5.5.9+dfsg-1ubuntu4.29+esm18
  • 16.04 (Xenial, ESM) — php7.0 fixed in 7.0.33-0ubuntu0.16.04.16+esm22
  • 22.04 (Jammy) — php8.1 fixed in 8.1.2-1ubuntu2.28
  • 24.04 (Noble) — php8.3 fixed in 8.3.6-0ubuntu0.24.04.13

ESM-covered Trusty/Xenial fixes require an active Ubuntu Pro subscription with esm-infra enabled.

Mitigating the PHP2Shell vulnerability if you can’t patch immediately

The PHP project’s own guidance for the PHP2Shell vulnerability, and the approach administrators are using on unsupported hosts, comes down to two configuration changes.

1. Disable session upload progress

This closes off the most actively exploited path.

session.upload_progress.enabled = Off

2. Remove the attacker-controlled failure trigger

Force an unbounded memory limit so the longjmp()-on-OOM path can’t be forced by a crafted request. This must be set administratively so no vhost, .htaccess, or ini_set() call can override it back to a finite value.

Apache with mod_php — add to /etc/httpd/conf.d/php.conf:

php_admin_value memory_limit -1
php_admin_flag session.upload_progress.enabled Off

PHP-FPM — add to the relevant pool file (e.g. /etc/php-fpm.d/www.conf):

php_admin_value[memory_limit] = -1
php_admin_flag[session.upload_progress.enabled] = off

After editing either, restart the service so every worker picks up the change:

sudo systemctl restart httpd
# or
sudo systemctl restart php-fpm

Audit every pool, vhost, and application config for an overriding memory_limit value before considering this done — a single permissive override reopens the hole.

3. Disable fatal-error backtraces (PHP 8.5 only)

PHP 8.5’s fatal_error_backtraces feature can trigger the same use-after-free independently of upload progress. Turn it off administratively alongside the settings above:

zend.exception_ignore_args = On
fatal_error_backtraces = Off

4. Cap other parser-driven memory growth

memory_limit = -1 only removes the per-request ceiling exploited in the confirmed proof-of-concept; it does not stop a crafted request from exhausting system memory by other means. Tighten the request parser instead of leaving it unbounded:

max_input_vars = 1000
max_file_uploads = 20
max_input_nesting_level = 64

Do not substitute an OS-level cgroup or ulimit -v (RLIMIT_AS/RLIMIT_DATA) memory cap for the above. Those force the same allocation-failure-and-longjmp() path the vulnerability already relies on; an external memory ceiling can trigger the bug rather than prevent it.

Why the mitigations are not a substitute for patching

The configuration changes above are what the PHP project itself calls a stopgap, not a fix, and treating them as equivalent to patching is the most common mistake administrators are making with this incident:

  • They cover known paths, not the root cause. The underlying bug is memory corruption inside PHP’s own fatal-error teardown path (longjmp() abandoning a half-built C data structure). Disabling session.upload_progress and fatal-error backtraces closes the exploitation techniques that have been publicly demonstrated so far — it does not patch the use-after-free itself. PHP core developer Gina Peter Banyard has stated publicly that no combination of mitigations fully blocks the underlying flaw, and that the security releases are the only permanent fix.
  • Information disclosure survives every mitigation above. Independent of RCE, both CVEs can leak adjacent process memory — session tokens, API keys, database credentials, TLS private key material — without executing any attacker code. None of the configuration mitigations in this post address that leak; only the patched PHP binary does.
  • Setting memory_limit = -1 trades one incident for another. An unbounded memory limit removes the trigger condition today, but it also removes PHP’s own defense against a single runaway or malicious request consuming all available RAM. Once you’ve patched, remove the override and restore a normal, finite memory_limit — don’t leave -1 in place indefinitely.
  • The mitigation set grows as more exploitation techniques are confirmed. This post already lists four separate settings (upload progress, fatal-error backtraces, memory limit, parser limits) because researchers kept finding additional ways to reach the same corrupted-state bug after the first mitigation shipped. There is no guarantee the list is complete; a configuration-only defense is chasing a moving target.
  • Mitigations are per-host manual configuration; patches are not. A missed vhost, an .htaccess override, or a pool file that didn’t get the admin-value treatment silently reopens the hole with no error or warning. A package upgrade is idempotent and auditable with rpm -q --changelog or apt changelog in a way that five scattered ini directives are not.

Use the mitigations to buy time, confirmed by testing them against the specific exploitation techniques above — then patch on the same timeline you’d use for any other actively-exploited critical RCE.

If you’re on an unsupported PHP branch

PHP 5.6 and other end-of-life branches will never receive an official patch for this issue. The configuration mitigations above are a stopgap, not a fix. Treat this incident as the forcing function to plan migration to a supported branch (8.2.35+, 8.3.36+, 8.4.27+, or 8.5.12+).

Summary: closing out the PHP2Shell vulnerability

  • Patch to 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 if you’re on a supported branch — see our guide to keeping PHP up to date for a repeatable patch workflow.
  • If you can’t patch yet, set session.upload_progress.enabled = Off, force memory_limit = -1, disable fatal_error_backtraces, and cap parser limits administratively, then restart your PHP workers — and treat that as a stopgap, not a fix.
  • Mitigations do not stop the information-disclosure path, do not cover every exploitation technique researchers have found, and memory_limit = -1 must be reverted once you patch.
  • EOL branches get no official fix for the PHP2Shell vulnerability — mitigations only buy time until you migrate.

For background on why end-of-life PHP keeps resurfacing in incidents like this, see our post on PHP end-of-life risk.

Let us know in the comments if you need help auditing your PHP fleet for this one.

Sources

wp2shell WordPress Vulnerability: CVE-2026-63030 Explained

What Is the wp2shell WordPress Vulnerability?

On July 17, 2026, researchers disclosed the wp2shell WordPress vulnerability, a chain of two flaws that lets an unauthenticated attacker execute remote code against WordPress Core installations. Consequently, the security community tracks this chain under two identifiers: CVE-2026-60137 (critical SQL injection) and CVE-2026-63030 (high-severity REST API batch endpoint route confusion). Since then, moreover, CISA has added both to its Known Exploited Vulnerabilities catalog based on evidence of active exploitation.

Suggested image: a simple flow diagram showing “REST API batch request → route confusion → SQL injection → remote code execution.” Alt text: “wp2shell WordPress vulnerability CVE-2026-63030 attack chain diagram.”

CVE-2026-63030: WordPress REST API Batch Endpoint Vulnerability

First, this WordPress REST API batch endpoint vulnerability targets WP_REST_Server::serve_batch_request_v1(). Accordingly, researchers classify it as CWE-436 (interpretation conflict) and rate it 7.5 (High) on the CVSS v3.1 scale. Furthermore, because the affected endpoint, /wp-json/batch/v1, has shipped with WordPress since version 5.6, the vulnerable code reaches a large installed base. However, some scanning vendors report a higher combined-severity score of 9.8 when they score this CVE as part of the full wp2shell WordPress vulnerability chain rather than as the standalone route-confusion issue; even so, the underlying advisory still treats 7.5 as the rating for that standalone component.

Affected versions: WordPress versions 6.9.0 through 6.9.4 and versions 7.0.0 through 7.0.1.

Fixed in: Versions 6.9.5 and 7.0.2, which WordPress released on July 17, 2026.

CVE-2026-60137: WordPress SQL Injection Vulnerability

Meanwhile, this WordPress SQL injection vulnerability lives in the author__not_in parameter of WP_Query. As a result, researchers classify it as CWE-89 and rate it 9.1 (Critical) on the CVSS v3.1 scale, since it sits in the core class that builds most of the database queries WordPress issues.

Affected versions: WordPress 6.8.0 through 6.8.5, 6.9.0 through 6.9.4, and 7.0.0 through 7.0.1.

Fixed in: Versions 6.8.6, 6.9.5, and 7.0.2.

How the WordPress Unauthenticated RCE Chain Works

On their own, each flaw stays fairly contained. For instance, the route confusion alone remains a bounded problem, and, similarly, the SQL injection normally requires authentication before an attacker can trigger it. Together, though, they produce the WordPress unauthenticated RCE that defines wp2shell: indeed, early researchers found that CVE-2026-63030 functions as a de facto auth bypass, unblocking the authenticated SQL injection so that an attacker could then dump the target database and pivot to code execution. As a result, an unauthenticated attacker can execute code via the WordPress REST API batch endpoint, with no valid account or user interaction required, and potentially achieve complete compromise of the website and its underlying data.

This distinction also affects patch prioritization. Specifically, version 6.8.x carries the SQL injection component but does not appear on the list of branches vulnerable to the full RCE chain, since the route confusion introduced in 6.9 supplies the unauthenticated path. Consequently, a scan that checks only for CVE-2026-63030 may miss 6.8.x exposure to the SQL injection itself.

Is This WordPress RCE Vulnerability Being Exploited?

At disclosure, no evidence of exploitation existed. Since then, however, reports of in-the-wild activity have emerged, and VulnCheck Canaries observed active exploitation beginning July 20. In fact, by the early hours of the following Saturday (UTC), successful exploitation was already well underway, and within three days of disclosure, attackers were widely chaining the two vulnerabilities to attack one of the largest surfaces on the internet. Meanwhile, reports indicate hackers are deploying persistent webshells and installing malicious plugins on affected servers.

Who Discovered the wp2shell WordPress Vulnerability

To begin with, Adam Kues of Assetnote, the attack surface management arm of Searchlight Cyber, discovered the vulnerability and responsibly disclosed it through WordPress’s HackerOne program. Because, however, the researchers described the attack as having no preconditions and being exploitable by an anonymous user against a stock install, they withheld full technical details to give defenders time to patch.

How to Patch This WordPress Core Security Vulnerability

BranchAffectedFixed Version
6.8.xSQL injection only (CVE-2026-60137)6.8.6
6.9.xFull RCE chain (both CVEs)6.9.5
7.0.xFull RCE chain (both CVEs)7.0.2

Given the severity, therefore, organizations operating affected WordPress installations should prioritize upgrading immediately, since applying the WordPress-provided update remains the most effective way to remediate CVE-2026-63030. Fortunately, WordPress has already taken steps to help: maintainers stated they are forcing updates for affected installations with automatic updates enabled. Even so, administrators should still verify that each internet-facing WordPress website has successfully upgraded, since workarounds are not recommended at this time.

If you need help confirming whether the wp2shell WordPress vulnerability has already been leveraged against your site, or needs assistance applying and validating the patch, contact our support team for a review.

References

SourceLink
Rapid7 Vulnerability Analysiswp2shell: A Critical RCE Vulnerability in WordPress Core
SOCRadar CISO FAQWordPress wp2shell (CVE-2026-63030): CISO FAQ & Fix
CVE.org Official RecordCVE-2026-63030
GridinSoft BlogWordPress wp2shell CVE-2026-63030: Update to 7.0.2 Now
VulnCheck AnalysisWP2Shell Vulnerabilities: CVE-2026-60137 and CVE-2026-63030
CyCognito Threat BlogEmerging Threat: CVE-2026-63030, CVE-2026-60137
KEVIntel Exploitation TrackerCVE-2026-63030 Exploitation Observed
Penligent Technical WriteupCVE-2026-63030 wp2shell — Patch Priority and Safe Validation
Strix CVSS ReferenceCVE-2026-63030 Severity, CVSS & Fix

Root Exploit: cPanel & WHM Authentication Bypass (CVE-2026-41940)

CVE-2026-41940 is a CVSS 9.8 authentication bypass affecting every supported version of cPanel & WHM. A remote unauthenticated attacker can gain root access to the server in two HTTP requests. Exploitation in the wild has been confirmed since at least February 23, 2026. Patch immediately.


What Is the Vulnerability

The flaw lives in cpsrvd‘s session handling. On a failed login, cPanel writes a preauth session file to disk at /var/cpanel/sessions/raw/ before any authentication occurs. That file is a newline-delimited key-value store:

needs_auth=1
user=
ip_address=172.17.0.1
local_port=2087

The session data was not sanitized before being written. By injecting raw \r\n characters via a crafted Authorization header, an attacker can append arbitrary key-value pairs — including user=root — into that file. On the subsequent authenticated request using the issued cookie, cPanel reads the session file and grants root access.

The three files modified by the patch:

Cpanel/Session.pm          (saveSession — primary fix)
Cpanel/Session/Load.pm     (session loader)
Cpanel/Session/Encoder.pm  (new hex round-trip primitives)

The root cause is that filter_sessiondata() — a sanitizer that strips \r\n=, from session values — already existed, but saveSession relied on each caller to invoke it. The patch moves that call inside saveSession itself, so no caller can bypass it.


Who Is Affected

All cPanel & WHM versions after 11.40. This includes every currently supported release branch. WP Squared (cPanel’s WordPress hosting platform) is also affected.

If you are running an end-of-life version, assume you are vulnerable. No patches will be issued for unsupported builds.


Patching

Run the following as root to force an immediate update:

bash
/scripts/upcp --force

After the update completes, confirm your build version in WHM under Server Information, or:

bash
cat /usr/local/cpanel/version

Patched versions by branch:

BranchPatched version
110.0.x11.110.0.97
118.0.x11.118.0.63
126.0.x11.126.0.54
132.0.x11.132.0.29
134.0.x11.134.0.20
136.0.x11.136.0.5
WP Squared136.1.7

Interim Mitigation (Pre-Patch)

If you cannot patch immediately, block the cPanel and WHM ports at your firewall. This prevents exploitation without affecting hosted sites, email, or applications — only control panel access is interrupted.

For firewalld:

bash
firewall-cmd --permanent --add-rich-rule='rule port port="2083" protocol="tcp" reject'
firewall-cmd --permanent --add-rich-rule='rule port port="2087" protocol="tcp" reject'
firewall-cmd --permanent --add-rich-rule='rule port port="2095" protocol="tcp" reject'
firewall-cmd --permanent --add-rich-rule='rule port port="2096" protocol="tcp" reject'
firewall-cmd --reload

For iptables:

bash
iptables -I INPUT -p tcp --dport 2083 -j DROP
iptables -I INPUT -p tcp --dport 2087 -j DROP
iptables -I INPUT -p tcp --dport 2095 -j DROP
iptables -I INPUT -p tcp --dport 2096 -j DROP

Apply these rules before the patch if your server has any public exposure on those ports. Remove them once the patch is confirmed deployed.


Checking for Compromise

cPanel has published an official detection script. Run it as root:

bash
/usr/local/cpanel/scripts/check_cpanel_rpms --targets=cpanel

Additionally, inspect the raw session directory for anomalous files. Legitimate preauth sessions will not contain user=root:

bash
grep -rl 'user=root' /var/cpanel/sessions/raw/

Any result from that grep on an unpatched server is grounds for treating the host as compromised. watchTowr has also published a Detection Artifact Generator for more thorough IOC analysis.

Check your WHM and SSH access logs for root logins that did not originate from your known admin IPs:

bash
grep 'Accepted' /var/log/secure | grep root
last root | head -30

Hardening After the Patch

This is a reasonable time to enforce controls that should already be in place:

Restrict WHM (port 2087) to trusted IP addresses only. In WHM: Home > Security Center > Host Access Control, or via /etc/hosts.allow:

cpanel: 198.51.100.10 198.51.100.11

Enable two-factor authentication for all WHM accounts: Home > Security Center > Two-Factor Authentication.

Disable root login via cPanel entirely if your workflow does not require it. All administrative actions can be performed through a named reseller account with sudo-equivalent privileges, preserving an audit trail.


The Fine Print

Zero-day exploitation was confirmed as far back as February 23, 2026 — about two months before disclosure. If your server was public-facing on port 2087 during that window without IP restrictions, treat it as potentially compromised regardless of what the detection script returns. A thorough audit of cron jobs, authorized SSH keys, and installed packages is warranted:

bash
crontab -l -u root
cat /root/.ssh/authorized_keys
rpm -Va 2>/dev/null | grep -v '^......G' | head -40   # RPM-based systems
debsums -c 2>/dev/null | head -40                      # Debian-based systems

Over 2 million cPanel instances are internet-accessible. If auto-updates were disabled, those servers are still unpatched.


References: cPanel official advisory · watchTowr full disclosure · Rapid7 ETR · NVD CVE-2026-41940

-Eric

Thwarting the Terrapin SSH Attack (Mitigation)

The Terrapin Attack is the biggest SSH vulnerability that we have seen in decades.  Terrapin splices TCP sequence numbers to truncate SSH extension negotiation. While this is a concern, effective exploitation of this vulnerability is very difficult, and mitigation is very easy! The paper notes that AES-GCM is not vulnerable to the attack:

AES-GCM (RFC5647) is not affected by Terrapin as it does not use the SSH sequence numbers. Instead, AES-GCM uses the IV obtained from key derivation as its nonce, incrementing it after sending a binary packet. In a healthy connection, this results in the nonce being at a fixed offset from the sequence number.

The original Encrypt-and-MAC paradigma from RFC4253 protects the integrity of the plaintext, thus thwarting our attack, which yields one pseudorandom block during decryption.

This means you can simply use the AES-GCM cipher in SSL communication by configuring your hosts to default to that protocol. The AES-GCM cipher has been supported in SSH since version 6.2 so pretty much all supported distributions going back to at least 2014 have an easy mitigation.

Mitigating on the server

Newer Servers that are using `update-crypto-policies` (RHEL, Alma, Rocky, Oracle, newer Ubuntu and Debian)

Newer operating systems that provide the command update-crypto-policies, create a new policy file and activate it.  You can modify `DEFAULT` to something different if you are using a different policy, but “DEFAULT” works for most systems.

# cat /etc/crypto-policies/policies/modules/TERRAPIN.pmod
cipher@ssh = -CHACHA20*
ssh_etm = 0

# update-crypto-policies --set DEFAULT:TERRAPIN
Setting system policy to DEFAULT:TERRAPIN
Note: System-wide crypto policies are applied on application start-up.
It is recommended to restart the system for the change of policies
to fully take place.

Older Servers are not using `update-crypto-policies`

Simply add this to the bottom of /etc/ssh/sshd_config:

Match All
Ciphers aes256-gcm@openssh.com

Note that Match all is added in case you have other match blocks. Of course, if you prefer, you can exclude the Match all line and insert the cipher above any match blocks so that it is a global option.

Forcing aes256-gcm is a bit of a hammer, it certainly works but may be more than you need.  Ultimately only Chacha20 and ETM-based MACs need to be disabled, so modify this as you see fit.

Testing Server Mitigation

The Terrapin research team has published a scanning/test tool.  There is GitHub page to compile it yourself, or pre-compiled binaries for the most common OS’s are available on the releases page.  The result should look something like this.  “Strict key exchange support” is unnecessary if the vulnerable ciphers are disabled.  As you can see, it says “NOT VULNERABLE” in green:

Output of a successful terrapin mitigation

Mitigating on the client

This can be done per user or system-wide.  To configure it for all users on a system, add this to the bottom of /etc/ssh/ssh_config:

Ciphers aes256-gcm@openssh.com

To configure it for a single user, add this to the top of the SSH configuration in your home directory (~/.ssh/config)

Ciphers aes256-gcm@openssh.com

Mitigating Windows SSH Clients

Unfortunately, the following Windows packages do not support AES-GCM and cannot be mitigated in this way:

If you use any of these then it would be a good idea to switch to an SSH client that supports AES-GCM.

Windows clients that support AES-GCM

Here are a few Windows clients that do support AES-GCM, in alphabetical order, by second letter:

The Fine Print

The examples above are one (very simple) way of dealing with this.  Ultimately you need to disable the ETM and ChaCha20 ciphers.  You can see what is configured in ssh as follows:

]$ ssh -Q mac|grep etm
hmac-sha1-etm@openssh.com
hmac-sha1-96-etm@openssh.com
hmac-sha2-256-etm@openssh.com
hmac-sha2-512-etm@openssh.com
hmac-md5-etm@openssh.com
hmac-md5-96-etm@openssh.com
umac-64-etm@openssh.com
umac-128-etm@openssh.com
]$ ssh -Q cipher|grep -i cha
chacha20-poly1305@openssh.com

-Eric

How to reset your root password!

From time to time I am asked how to set reset the root password on a Linux server when you get locked out. Obvious for security reasons you can’t to do this remotely, but it is pretty easy to do if you have physical access.

First, reboot the system and interrupt the grub bootloader that looks like this by pressing ‘e’. In this example we are booting from CentOS, but it works for pretty much any modern Linux distribution (SuSE, Debian, Ubuntu, etc), because they all use grub:

 grub bootloader interface

The when you press ‘e’ it will display a edit screen like this.  If you are prompted for a password when you press ‘e’ then your system administrator has disabled editing the bootloader configuration.   It is still possible to reset the root password if your bootloader his password it, but you will need to boot off of a rescue disk to do it.

 grub edit menu

Arrow down to the first section/line that starts with ‘linux16’ or ‘linuxefi’ or ‘linux’. it usually looks something like this (yes, it is a big long single line):

linux16 /vmlinuz-3.10.0-1127.19.1.el7.x86_64 root=/dev/mapper/centos-root ro rd.lvm.lv=centos/swap vconsole.font=latarcyrheb-sun16 rd.lvm.lv=centos/root crashkernel=auto vconsole.keymap=us biosdevname=0 net.ifnames=0 rd.auto=1 LANG=en_US.UTF-8

Add this to the end of that line; this is a temporary change to the bootloader that will exist only for this reboot:

init=/bin/bash

It should look something like this:

linux16 ... init=/bin/bash

Then press control-x to boot and eventually it will give you a bash prompt. When it does, run the following commands:

# mount -o remount,rw /
# passwd root
<change the password>
# mount -o remount,ro /
# echo b > /proc/sysrq-trigger

The last line will reboot the system. That should do it! Now you should be able to log in with your new root password.

-Eric

Docker or KVM: Which is Right for You?

Over the years there have been many different technologies to isolate workloads. Isolation is important for security because if one workload is compromised, and they are not isolated, then others can be affected. In today’s ecosystem, there are two predominant forms of workload isolation: containers and virtual machines.

Containers

Containers are similar to chroot jail in that all of the programs running within the container are executed in a way that they believe they have their own root file system. Linux namespaces allow the container to have its own process ID space, so `init` can be process ID 1, whereas, with chroot jails, the namespace was shared, so processes in the jail could not have a process ID of 1 since the host OS `init` process was already using process ID 1.

Containers share the same kernel and they do not have direct access to hardware resources.

 

Virtual Machines

Virtual machines are an emulated hardware environment provided by KVM. They boot their own kernel, have their own disks and attach network devices. If a user has full control over

 visualization: App->OS->Virtualization stack

a virtual machine, then they can install any operating system they wish. Because the hardware is virtualized and running a separate kernel, virtual machines provide greater isolation than containers since they do not share the same kernel. The isolation is provided by hardware optimizations implemented in silicon by CPU manufacturers. This makes

it more difficult to escape a virtual machine environment than a container environment. You might ask: But what about branch prediction attacks, like Spectre?

In this case, branch prediction attacks equally affect containers and virtual machines so we can exclude that as a consideration for choosing containers or virtual machines.

Root File System

In practice, the operating systems running within these isolation technologies both operate from their own root file systems. Traditionally this was a complete distribution installation, however, that has changed in a way that hinders security and increases the difficulty of systems administration. There is a trend of “turn-key” operating system deployments, especially in Docker. If you want a particular application, let’s say, a web server running Word Press, then you simply run a few short commands and your Word Press server is up and running. This makes it easy to install for the novice user, but there is no guarantee that the Docker environment is up to date.

Further compounding container deployment security is the fact that some containers do not have a complete root file system and administrators cannot log in at all. Some would say this is good for security, but this type of monolithic container is still subject to the increasing likelihood of new attack vectors against an aging codebase. If a vulnerability does come along, then the monolithic container can become compromised. Since it can be difficult to log into this kind of container, it is harder to inspect what is happening from within the environment– and even if you can log in, the installation is so minimal that the toolset for inspecting the problem is not available, and the deployment may be so old that even if the container includes a package manager like Yum or APT, the distribution repositories may have been archived and are no longer available without additional effort.

Container intrusions can often be inspected from the outside using a privileged installation with configurable tooling, but the security issues and increased difficulty of maintenance are a counterindication for today’s containerized counter culture.

Our recommendation is always to install a long-term support release of a well known distribution in a virtual machine instead of a container. As a full virtual machine, not only do you get increased isolation, vendor updates, and a better security life cycle, but you also get increased management tooling such as live migration, full block device disks that can be cloned and mounted on other systems or snapshotted with easy rollback.

If you must use containers for your environment, then please use a normal OS distribution, configure security updates and email notifications and centralized logging. This will go a long way to making the system maintainable in the future and save you support costs.

If you are interested in learning more, then call us for a free consultation, so we can help work out what is best for your organization.

-Eric

Fixed Versions: Linux SACK Attack – Denial of Service

The recently published CVE-2019-11477 and CVE-2019-11478 attacks enable an attacker with access to a TCP port on your server (most everyone, including those with web or mail servers) to either:

  1. Slow it down severely
  2. Cause a kernel crash

See the NIST publication for more detail:

https://nvd.nist.gov/vuln/detail/CVE-2019-11477

Upstream distributions have released fixes for these as follows.  The 2019-11478 vulnerability is an issue as well, but the -11477 issue has higher impact so we are listing it here.  So far as I have seen, the fix for both is in the same package version so you only need to reference the -11477 articles:

Mitigation

You can mitigate this attack with iptables. If you are using fwtree, our latest release for el6 and el7 includes the mitigation (version 1.0.1-70 or newer). Of course it is best to update your kernel, but this provides a quick fix without rebooting:

# [ -d /etc/fwtree.d ] && yum install -y fwtree && systemctl reload fwtree && iptables-save | grep MITIGATIONS

You can also do it directly with iptables:

# iptables -I INPUT -p tcp --tcp-flags SYN SYN -m tcpmss --mss 1:500 -j DROP
# ip6tables -I INPUT -p tcp --tcp-flags SYN SYN -m tcpmss --mss 1:500 -j DROP

You can also disable TCP selective acks in sysctl:

# Add this to /etc/sysctl.conf
net.ipv4.tcp_sack=0

Red Hat / CentOS / Scientific Linux

Vendor security article: https://access.redhat.com/security/cve/cve-2019-11477

Fixed Versions

  • el5: not vulnerable (and EOL, so upgrade already!)
  • el6: kernel-2.6.32-754.15.3.el6
  • el7:  kernel-3.10.0-957.21.3.el7

Ubuntu

Vendor security article: https://usn.ubuntu.com/4017-1/

Fixed Versions

  • Ubuntu 19.04
    • 5.0.0.1008.8
  • Ubuntu 18.10
    • 4.18.0.22.23
  • Ubuntu 18.04 LTS
    • 4.15.0-52.56
  • Ubuntu 16.04 LTS
    • 4.15.0-52.56~16.04.1
    • 4.4.0-151.178

Debian

Vendor security article: https://security-tracker.debian.org/tracker/CVE-2019-11477

Fixed Versions

  • jessie
    • 3.16.68-2
    • 4.9.168-1+deb9u3~deb8u1
  • stretch
    • 4.9.168-1+deb9u3
  • sid
    • 4.19.37-4

SuSE

Vendor security article: https://www.suse.com/security/cve/CVE-2019-11477/

Fixed Versions

For SuSE, there are too many minor version releases to list them all here. To generalize, if you are running a newer kernel than these then you are probably okay, but double-check the vendor security article for your specific release and use case:

  • Pre SLES-15:
    • 3.12.61-52.154.1
    • 4.4.121-92.114.1
    • 4.4.180-94.97.1
    • 4.12.14-95.19.1
  • SLES 15
    • 4.12.14-150.22.1
  • Leap 15
    • 4.12.14-lp150.12.64.1

Vanilla Upstream Kernel (kernel.org)

Security patch: https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=3b4929f65b0d8249f19a50245cd88ed1a2f78cff

Fixed Versions

  • 5.1.11
  • 4.19.52
  • 4.14.122
  • 4.9.182
  • 4.4.182
  • 3.16.69

Redirect Directory Trailing Slash (/) with Restricted Access

Securing Apache and Maintaining Usability

First, you should always avoid .htaccess and use it as a last resort. Still, this example holds whether or not you are using .htaccess.

Let’s say you have a directory you wish to secure so that only the index and some file (test.txt) is available. Other other content in the directory should be denied. For example:

These links should load:

  • www.example.com/foo
  • www.example.com/foo/
  • www.example.com/foo/test.txt

In addition, the link without the trailing / should redirect to the link with the trailing / (from /foo to /foo/) for ease of access for your users.

These links should give a 403:

  • www.example.com/foo/bar
  • www.example.com/foo/letmein.txt

To accomplish this, you might write a .htaccess as follows:

Apache 2.2

Order allow,deny
<Files ~ ^$|^index.html$|^test.txt$>
     Order deny,allow
</Files>

Apache 2.4

Require all denied
<Files ~ ^$|^index.html$|^test.txt$>
     Require all granted
</Files>

However, you will run into a problem: The link without a trailing / will not work (www.example.com/foo) because permissions are evaluated before the mod_dir module’s DirectorySlash functionality evaluates whether or not this is a directory. While not intuitive, we also must add the directory as a file name to be allowed as follows:

Apache 2.2

Order allow,deny
<Files ~ ^foo$|^$|^index.html$|^test.txt$>
     Order deny,allow
</Files>

Apache 2.4

Require all denied
<Files ~ ^foo$|^$|^index.html$|^test.txt$>
     Require all granted
</Files>

Hopefully this will help anyone else dealing with a similar issue because it took us a lot of troubleshooting to pin this down. Here are some search terms you might try to find this post:

  • Apache 403 does not add trailing /
  • Apache does not add trailing slash
  • .htaccess deny all breaks trailing directory slash
  • .htaccess Require all denied breaks trailing directory slash

-Eric

 

Check Authorize.net TLS 1.2 Support: tlsv1 alert protocol version

TLS v1.0 and v1.1 to be Disabled on February 28th, 2018

As you may be aware, Authorize.net is disabling TLS v1.0 and v1.1 at the end of this month.  More information about the disablement schedule is available here.

You may begin to see errors like the following if you have not already updated your system:

error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version

We can help you solve this issue as well as provide security hardening or PCI compliance service for your server. Please call or email if we may be of service!

Checking for TLS v1.2 Support

Most modern Linux releases support TLS v1.2, however, it would be best to check to avoid a surprise. These tests should work on most any Linux version including SUSE, Red Hat, CentOS, Debian, Ubuntu, and many others.

PHP

To check your server, you can use this simple PHP script. Make sure you are running this PHP code from the same PHP executable that runs your website. For example, you might have PHP compiled from source and also have it installed as a package. In some cases, one will work and the other will not:

<?php
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://apitest.authorize.net');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

if (($response = curl_exec($ch)) === false) {
 $error = curl_error($ch);
 print "$error\n";
}
else {
 $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 print "TLS OK: " . strlen($response) . " bytes received ($httpcode).\n";
}

curl_close($ch);
?>

Perl

As above, make sure that you are using the same Perl interpreter that your production site is using or you can end up with a false positive/false negative test. If you get output saying “403 – Forbidden: Access is denied” then it is working because TLS connected successfully.

# perl -MLWP::UserAgent -e 'print LWP::UserAgent->new->get("https://apitest.authorize.net")->decoded_content'
Can't connect to apitest.authorize.net:443

LWP::Protocol::https::Socket: SSL connect attempt failed with unknown errorerror:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version at /usr/lib/perl5/vendor_perl/5.10.0/LWP/Protocol/http.pm line 57.

OpenSSL/Generic

To check from the command line without PHP, you can use the following which shows a failed TLS negotiation:

# openssl s_client -connect apitest.authorize.net:443
CONNECTED(00000003)
30371:error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version:s23_clnt.c:605

Other Languages

If you use any language, we can help verify that your application is set up to work correctly.  Just let us know and we can work with you directly.  I hope this post helps, please comment below!

-Eric