Loading

PHP2Shell: Using Backup Snapshots to Find and Verify Vulnerable PHP Runtimes

In this article:

Executive summary
     Key risk factors
     Technical details

Recommended Actions for Cohesity Data Cloud Administrators
   
 1. Inventory All PHP Runtimes and Prioritize
     2. Protect Your Restore Path
     3. After Patching, Find Runtimes That Are Still Vulnerable
     4. Validate Immutability and Admin Controls
     5. Rehearse the Recovery and Brief Your Counterparts

YARA Rule Details: Using Snapshots to Inventory PHP and Verify Patching
     Reading the Results
     Cohesity REDLab Test Results
     Limitations

Conclusion
     References


Executive Summary

Cohesity REDLab is publishing this advisory in response to two critical vulnerabilities in the PHP runtime (CVE-2026-109618 and CVE-2026-109619) and the Cohesity Threat Library has been updated to detect vulnerable PHP runtimes in snapshots. Both vulnerabilities allow unauthenticated remote code execution on servers that use PHP to serve web requests. PHP shipped fixes on October 21, 2026 in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP rates both flaws CVSS v4.0 9.2, and NVD scores both CVSS v3.1 9.8. Because the defects are in PHP rather than in an application, updating WordPress, Drupal or a similar application does not fix them. PHP 8.1 and older receive no official fix.

For Cohesity Data Cloud administrators, this is a backup tier problem as much as a patching problem. PHP is embedded in web servers, content management systems, containers, hosting control panels and vendor appliances, and many of those systems are not on any patching team’s list. Backup snapshots give administrators a point-in-time view of every protected system without touching production. They show which PHP runtimes exist, which versions they run, and which recovery points would bring a vulnerable runtime back online after a restore.

Key Risk Factors 

Several characteristics distinguish PHP2Shell from a typical application vulnerability. The flaws sit in the PHP runtime, beneath the application. Pre-authentication remote code execution (RCE) is possible on current WordPress, Drupal and Joomla container images, and even a minimal “Hello World” application on an affected PHP version is also exposed.

PHP states that all installations serving web requests are at risk. This includes PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. For mod_php, PHP has demonstrated an exploit in which an unauthenticated request triggers the attack before any application code runs. PHP also states that no complete mitigation exists. Updating is the only safe choice and Apache, PHP-FPM and any long-running PHP workers must be restarted so the fixed runtime is actually loaded.

CVE-2026-109619 can also leak server memory. The leaked data can include passwords, tokens, API keys and certificates from other requests. Secrets can be exposed without any malicious code running. Finally, PHP is often a component of other software. Its presence may not be directly observable. CISA makes the same point in its own alert.

Technical Details 

Figure 1

Figure 1: PHP2Shell attack path and the Cohesity recovery boundary 

PHP describes the two flaws as follows. Both are memory-safety defects reachable by an unauthenticated HTTP request. 

CVE

Root cause

Confirmed impact

CVSS

CVE-2026-109618
RCE via out-of-memory corruption

An out-of-memory condition can interrupt PHP while internal request data is only partly initialized. Request cleanup then treats the invalid data as valid, corrupting memory. 

Unauthenticated remote code execution. PHP validated it over HTTP against mod_php. Source analysis suggests PHP-FPM is susceptible, though no complete FPM exploit has been demonstrated. 

PHP v4.0: 9.2

NVD v3.1: 9.8

CVE-2026-109619
RCE via stale global state 

Request-owned internal state survives after its memory has been freed. A later request handled by the same worker can use that stale state (use-after-free).

Unauthenticated heap use-after-free, memory disclosure and remote code execution with the web worker’s privileges. PHP validated session-lifecycle RCE against mod_php and PHP-FPM. 

PHP v4.0: 9.2

NVD v3.1: 9.8 

Recommended Actions for Cohesity Data Cloud Administrators 

PHP2Shell is a patchable vulnerability, but patching depends on knowing where PHP runs. Recovery points you hold in Cohesity Data Cloud may restore vulnerable systems. Therefore following actions are outlined ordered by impact:  

1. Inventory ALL PHP Runtimes and Prioritize (YARA Step 1) 

  • Run PHP_Runtime_Inventory across protected VMs, physical servers and NAS data that host web content. Include web servers, CMS and e-commerce platforms, hosting control panels and vendor appliances that ship their own PHP. 
  • Rank by exposure: internet-facing first, then internal web applications, then everything else. Share the list with web, application and SOC owners so everyone works from the same priorities. 
  • Record the PHP version, the owner and the patch plan for each system. 

2. Protect Your Restore Path (after patch and restart) 

  • Upgrade the PHP version or to a patched vendor build, and restart Apache, PHP-FPM and all long-running workers. 
  • Remember that restoring from a snapshot made before patching brings back a vulnerable runtime. Restore into an isolated network, patch and restart before reconnecting to production, and update restore runbooks accordingly. 
  • For PHP 8.1 and older – discuss ways to upgrade or backport with your security and dev team. 

3. After Patching, Find Runtimes That Are Still Vulnerable (YARA Step 2) 

  • Once patching is done, wait for new snapshots of the patched workloads, then run PHP2Shell_Vulnerable_PHP_Runtime. This rule is already updated in the Cohesity Data Cloud default threat library. It can be run using default settings or as a custom YARA scan. Review every match against the limitations above. 
  • Any match is a runtime that is still vulnerable: a system that was missed, a bundled or forgotten copy, or a restored older recovery point. Send it back to the owner for patching and restart. 
  • Route results to the SOC and to the owners of flagged systems. Repeat until a post-patch snapshot returns no matches. 

4. Validate Immutability and Admin Controls 

  • Confirm DataLock (WORM) is enabled on Protection Groups covering web and application tiers, with retention longer than your detection-to-decision window. 
  • Verify Quorum is configured for destructive operations so a single compromised administrator cannot shorten retention or delete snapshots. 
  • If you use Cohesity FortKnox, confirm recent vault copies are current. Review Helios and cluster administrator accounts for MFA, least privilege and separation from other administrative accounts. 

5. Rehearse the Recovery and Brief Your Counterparts 

  • Run a recovery drill for a web tier: restore to an isolated segment, patch and restart PHP, validate the application and only then reconnect. Measure the time it takes. 
     

YARA Rule Details: Using Snapshots to Inventory PHP and Verify Patching  

Figure 2

The first questions every administrator will be asked are where PHP runs and which systems are still vulnerable. YARA can answer both for protected data without logging in to each host. A PHP runtime embeds its version as a string in the binary, in the form X-Powered-By: PHP/8.2.34. That string is present in the PHP command line binary, php-fpm, php-cgi and the Apache module libphp*.so on Linux, and in the runtime library php8.dll or php8ts.dll on Windows (php.exe and php-cgi.exe are thin front-ends without the engine code and are not reported, so scan the DLL). Because YARA matches content rather than file names, it finds a PHP runtime wherever it was installed, including copies bundled inside applications, appliances and container filesystems, and copies renamed or moved from the standard path.

The rules below support two separate steps, to be run in this order:

  • Step 1, before patching production systems: inventory ALL PHP runtimes. Run PHP_Runtime_Inventory. It matches every PHP runtime whatever its version, so the output is your complete list of where PHP exists in protected data, with each runtime’s path and version. Use it to find every system that needs patching and to prioritize them.

  • Step 2, after patching production: find runtimes that are STILL vulnerable. This rule is already updated in the Cohesity Data Cloud default threat library. Run a default threat scan or copy the rule for a custom scan. PHP2Shell_Vulnerable_PHP_Runtime against snapshots taken after patching.  It matches only runtimes older than the fixed releases, so treat each match as a system to investigate, since distribution builds can carry a backported fix. No matches on a post-patch snapshot is a good sign that the update reached disk, not proof the system is fully remediated.

Running the rules against older snapshots as well shows when a vulnerable runtime first appeared, which helps establish the exposure window.

Every rule first confirms that the file is the PHP engine itself and not something that merely mentions a PHP version. The file must be an executable image of plausible size (1 MB to 256 MB) and must contain at least four of ten strings that only the PHP runtime carries, such as the Zend Engine v banner, the fatal-error texts “Allowed memory size of” and “Maximum execution time of”, and ini names such as zend.enable_gc and session.upload_progress. Logs, scripts, configuration files, PHP extension DLLs and tools that embed a PHP version header cannot match, and the thin Windows front-ends php.exe and php-cgi.exe are deliberately excluded because the engine lives in the DLL beside them.

Linux distributions often backport a fix without changing the version string, so the rule also looks for code that the fix itself adds to every patched runtime: the new memory_hard_limit_percent ini setting, the new “Hard memory limit of … bytes exhausted” error text and two related internal markers. A runtime with an old version string that contains that code is reported by a third rule, PHP2Shell_Backported_Fix_Old_Version_String, and is not counted as vulnerable.

Inventory (Step 1).  Copy this block to list every PHP runtime regardless of version.

/*
  Cohesity REDLab - PHP runtime inventory (Step 1). Self-contained: copy this
  block on its own. Matches every PHP engine binary (php, php-fpm, php-cgi,
  libphp*.so, php8.dll / php8ts.dll) regardless of version. 
*/
rule PHP_Runtime_Inventory
{
    meta: 
        description = "Any PHP runtime (CLI, FPM, CGI, mod_php, libphp, php8.dll/php8ts.dll). Inventory only."
        author      = "Cohesity REDLab"
        date        = "2026-10-21"
        version     = "3" 
    strings:
        // compiled-in version header: X-Powered-By: PHP/8.4.26
        $hdr   = /X-Powered-By: PHP\/[0-9]+\.[0-9]+\.[0-9]+/ ascii wide
        // strings carried only by the PHP engine (banner, fatal-error texts, ini names) 
        $php01 = "Zend Engine v" ascii wide
        $php02 = "Zend Technologies" ascii wide
        $php03 = "Allowed memory size of" ascii wide
        $php04 = "Maximum execution time of" ascii wide 
        $php06 = "__PHP_Incomplete_Class" ascii wide
        $php07 = "zend.enable_gc" ascii wide
        $php08 = "upload_max_filesize" ascii wide 
        $php09 = "session.upload_progress" ascii wide
        $php10 = "PHP_INI_SCAN_DIR" ascii wide
    condition:
        // executable image: ELF, PE, Mach-O 64-bit, Mach-O universal 
        (uint32(0) == 0x464c457f or uint16(0) == 0x5a4d or
         uint32(0) == 0xfeedfacf or uint32(0) == 0xbebafeca)
        and filesize > 1MB and filesize < 256MB
        and $hdr and 4 of ($php*) 
}

Vulnerable PHP Detection (Step 2). This rule is already updated in the Cohesity Data Cloud default threat library and users can either run a default threat scan or copy this rule to run a custom scan.

/*
  Cohesity REDLab - PHP2Shell vulnerable runtime detection (Step 2).
  CVE-2026-109618 / CVE-2026-109619. Self-contained: copy this block on its
  own; it does not depend on the inventory rule. The second rule is optional 
  and only reports distribution backports (fixed code, old version string).
*/
rule PHP2Shell_Vulnerable_PHP_Runtime
{ 
    meta:
        description = "Unpatched PHP runtime affected by PHP2Shell (CVE-2026-109618, CVE-2026-109619)"
        author      = "Cohesity REDLab"
        date        = "2026-10-21" 
        version     = "3"
        fixed_in    = "8.5.12, 8.4.27, 8.3.36, 8.2.35"
        note        = "5.x, 7.x, 8.0 and 8.1 have no official fix and are always reported"
    strings: 
        // strings carried only by the PHP engine (banner, fatal-error texts, ini names)
        $php01 = "Zend Engine v" ascii wide
        $php02 = "Zend Technologies" ascii wide
        $php03 = "Allowed memory size of" ascii wide 
        $php04 = "Maximum execution time of" ascii wide
        $php05 = "Call to undefined function" ascii wide
        $php06 = "__PHP_Incomplete_Class" ascii wide
        $php07 = "zend.enable_gc" ascii wide 
        $php08 = "upload_max_filesize" ascii wide
        $php09 = "session.upload_progress" ascii wide
        $php10 = "PHP_INI_SCAN_DIR" ascii wide
        // version header older than the fixed release of its branch 
        $old_eol = /X-Powered-By: PHP\/(5\.[0-9]+|7\.[0-4]|8\.[01])\.[0-9]+([^0-9]|$)/ ascii wide
        $old_82  = /X-Powered-By: PHP\/8\.2\.([0-9]|[12][0-9]|3[0-4])([^0-9]|$)/ ascii wide
        $old_83  = /X-Powered-By: PHP\/8\.3\.([0-9]|[12][0-9]|3[0-5])([^0-9]|$)/ ascii wide
        $old_84  = /X-Powered-By: PHP\/8\.4\.([0-9]|1[0-9]|2[0-6])([^0-9]|$)/ ascii wide 
        $old_85  = /X-Powered-By: PHP\/8\.5\.([0-9]|1[01])([^0-9]|$)/ ascii wide
        // code that the fix adds to every patched runtime (also present in distro backports)
        $fix1 = "memory_hard_limit_percent" ascii wide
        $fix2 = "Hard memory limit of" ascii wide 
        $fix3 = "ZEND_MM_MAX_ALLOCS" ascii wide
        $fix4 = "__PHP_Incomplete_Class_Serialized" ascii wide
    condition:
        // executable image: ELF, PE, Mach-O 64-bit, Mach-O universal 
        (uint32(0) == 0x464c457f or uint16(0) == 0x5a4d or
        uint32(0) == 0xfeedfacf or uint32(0) == 0xbebafeca)
        and filesize > 1MB and filesize < 256MB
        and 4 of ($php*)          // it is the PHP engine 
        and any of ($old_*)       // version predates the fix
        and none of ($fix*)       // and the fix code is absent
}  
rule PHP2Shell_Backported_Fix_Old_Version_String
{
    meta:
        description = "PHP runtime whose version predates the fix but which contains the fix code (distribution backport). Not vulnerable; inventory aid." 
        author      = "Cohesity REDLab"
        date        = "2026-10-21"
        version     = "3"
    strings: 
        $php01 = "Zend Engine v" ascii wide
        $php02 = "Zend Technologies" ascii wide
        $php03 = "Allowed memory size of" ascii wide
        $php04 = "Maximum execution time of" ascii wide 
        $php05 = "Call to undefined function" ascii wide
        $php06 = "__PHP_Incomplete_Class" ascii wide
        $php07 = "zend.enable_gc" ascii wide
        $php08 = "upload_max_filesize" ascii wide 
        $php09 = "session.upload_progress" ascii wide
        $php10 = "PHP_INI_SCAN_DIR" ascii wide
        $old_eol = /X-Powered-By: PHP\/(5\.[0-9]+|7\.[0-4]|8\.[01])\.[0-9]+([^0-9]|$)/ ascii wide
        $old_82  = /X-Powered-By: PHP\/8\.2\.([0-9]|[12][0-9]|3[0-4])([^0-9]|$)/ ascii wide 
        $old_83  = /X-Powered-By: PHP\/8\.3\.([0-9]|[12][0-9]|3[0-5])([^0-9]|$)/ ascii wide
        $old_84  = /X-Powered-By: PHP\/8\.4\.([0-9]|1[0-9]|2[0-6])([^0-9]|$)/ ascii wide
        $old_85  = /X-Powered-By: PHP\/8\.5\.([0-9]|1[01])([^0-9]|$)/ ascii wide
        $fix1 = "memory_hard_limit_percent" ascii wide 
        $fix2 = "Hard memory limit of" ascii wide
        $fix3 = "ZEND_MM_MAX_ALLOCS" ascii wide
        $fix4 = "__PHP_Incomplete_Class_Serialized" ascii wide
    condition: 
        (uint32(0) == 0x464c457f or uint16(0) == 0x5a4d or
        uint32(0) == 0xfeedfacf or uint32(0) == 0xbebafeca)
        and filesize > 1MB and filesize < 256MB
        and 4 of ($php*) and any of ($old_*) and 2 of ($fix*) 
}

Reading the Results 

Result

What it means

PHP_Runtime_Inventory 

A PHP runtime is present  

PHP2Shell_Vulnerable_PHP_Runtime 

A PHP runtime older than the fixed releases is on this protected system. 

PHP2Shell_Backported_Fix_Old_Version_String 

The version string predates the fix, but the runtime contains the fix code. (Typical of distribution security updates, for example Ubuntu 24.04 php8.3 8.3.6-0ubuntu0.24.04.13.) 

No match on a system you expect to run PHP 

The runtime may not have been scanned or does not carry the string. 

Cohesity REDLab Test Results

REDLab tested the rules against PHP builds and the table below shows the outcome against various php runtimes: 

Source

Binary tested

Version string 

Expected

Rule result

php.net Docker image php:8.2.34-cli 

php (ELF)

8.2.34

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

php.net Docker image php:8.2.35-cli

php (ELF)

8.2.35

Fixed

Inventory only

php.net Docker image php:8.3.35-fpm

php-fpm (ELF)

8.3.35 

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

php.net Docker image php:8.4.26-cli

php (ELF)

8.4.26

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

php.net Docker image php:8.4.26-apache

libphp.so mod_php (ELF)

8.4.26

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime 

php.net Docker image php:8.4.27-cli

php (ELF)

8.4.27

Fixed

Inventory only

php.net Docker image php:8.5.11-cli

php (ELF)

8.5.11

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

windows.php.net 8.3.35 NTS x64

php8.dll (PE)

8.3.35

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

windows.php.net 8.3.35 NTS x64

php.exe, php-cgi.exe (PE)

none

No match (front-ends, not the engine)

No match

windows.php.net 8.4.26 NTS x64

php8.dll (PE)

8.4.26

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

windows.php.net 8.4.26 NTS x64

php.exe, php-cgi.exe (PE)

none

No match (front-ends, not the engine)

No match

windows.php.net 8.4.27 NTS x64

php8.dll (PE)

8.4.27

Fixed

Inventory only

Debian 12 php8.2-cli 8.2.32-1~deb12u1

php8.2 (ELF)

8.2.32

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

Debian 12 php8.2-cli 8.2.34-1~deb12u1

php8.2 (ELF)

8.2.34

Vulnerable

PHP2Shell_Vulnerable_PHP_Runtime

Debian 12 php8.2-cli 8.2.35-1~deb12u1 (security update)

php8.2 (ELF)

8.2.35

Fixed

Inventory only

Ubuntu 24.04 php8.3-cli 8.3.6-0ubuntu0.24.04.13 (security update with backported fix)

php8.3 (ELF)

8.3.6

Fixed (backport)

PHP2Shell_Backported_Fix_Old_Version_String

Control: macOS /bin/ls

Mach-O

none

No match

No match

Control: text file containing “X-Powered-By: PHP/8.4.26”

plain text

8.4.26

No match (not an executable)

No match

Two test results are worth noting. On Windows, php-cgi.exe and php.exe are only front-end executables and do not contain the PHP engine, so they are not reported. Instead, results come from php8.dll or php8ts.dll. On Ubuntu, the rule checks for fix code rather than just the version because the package is fixed even though its version still shows 8.3.6. The changelog lists CVE-2026-109618 and CVE-2026-109619, so a version-only check would incorrectly flag it as vulnerable.

Limitations

  • Backported fixes. Linux distributions and LTS vendors often patch PHP without changing the upstream version string. The rules recognize backports that include the fix code and report them separately, which REDLab confirmed with the Ubuntu 24.04 update. A vendor backport that carries only part of the fix would still be reported as vulnerable, so check flagged distribution packages against the vendor’s advisory before concluding they are exposed. The reverse error is the dangerous one, so REDLab recommends reviewing every flag rather than ignoring them.

  • Presence is not exposure. A command line PHP used only for scripts is lower risk than one serving web requests. Use the file name and path as hints, for example php-fpm, php-cgi or libphp*.so in a web server directory.

  • Snapshots are point-in-time. A system patched after the snapshot will still be flagged, and a runtime that was upgraded without a restart will still be vulnerable in memory. Pair the scan with confirmation from the system owner.

  • Validate before relying on the rules. REDLab tested these rules against the real PHP builds listed above (official Linux and Windows releases, Debian and Ubuntu packages) covering vulnerable, fixed and backported cases. Your environment may contain builds from other vendors, so run the rules against known PHP builds from your own systems before using the results to make decisions.
     

Conclusion

PHP2Shell shows how a flaw in a shared runtime can expose a large share of the web at once, including those that run secure application code. Patching is the remedy, and it begins with an accurate inventory which backup administrators are well placed to build. They hold point-in-time copies of the systems most likely to run PHP and can scan backup data to find vulnerable runtimes without touching production. Backups are the last line of defense, and the techniques presented help ensure that a vulnerable PHP runtime is not restored into production.

References

  1. PHP Foundation: PHP security advisory, “Critical remote code execution vulnerabilities, update now”
  2. PHP GitHub advisories: GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
  3. PHP GitHub advisories: GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
  4. Wiz Research, OpenAI Codex Security, Calif: php2shell.com
  5. CISA: Critical Vulnerabilities in PHP Require Immediate Patching
  6. ENISA, European Commission, CERT-EU and partners: Joint statement on critical PHP vulnerabilities
  7. CERT-EU: Security Advisory 2026-018
  8. Cloudflare: Cloudflare deploys protections against php2shell
  9. NVD: CVE-2026-109618
  10. REDLab: https://www.cohesity.com/platform/redlab/advisories
Loading