On October 21, 2026, PHP released fixes for two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, collectively known as PHP2Shell. Both can allow unauthenticated remote code execution on affected PHP installations. Exploitation attempts were seen as early as October 17, four days before disclosure.
Exploitation of PHP2Shell began four days before PHP shipped its fixes, so a webshell planted in that window can survive the update on a fully patched server. Rubrik Threat Hunting scans existing backups with YARA rules and file hashes. Security teams can use it to find suspicious files across recovery points, identify the earliest snapshot where they appear, and pinpoint the latest clean snapshot as a candidate for recovery. Here’s how.
Patching is Step One
Patch first: Update to 8.5.12, 8.4.27, 8.3.36, 8.2.35, or your vendor's patched package.
Patching fixes the vulnerabilities but does not remove files an attacker may have left behind. A webshell installed before the update can remain accessible afterward. Once patches are applied, the investigation needs to establish whether a compromise occurred and if so, which backups may be suitable for recovery.
Hunt in Your Backups
Rubrik Threat Hunting scans your existing snapshots with YARA rules and file hashes. Scanning historical backups lets investigators look for suspicious files across recovery points without running the hunt against the production workloads. Results identify the earliest matched backup and the latest backup without matches. These findings help narrow the investigation and identify candidate recovery points.
The Rules
These rules test for PHP2Shell exposure by engine version and flag common PHP webshell patterns. They identify files and versions for investigation; they do not establish how an attacker gained access.
PHP2Shell Exposure — Unpatched Engine Version: PHP2Shell is a flaw in the PHP engine, so exposure is a version question, not a file you can fingerprint. This rule reads the engine version banner from inside the binary in a backup and matches any build below the patched floor (8.5.12, 8.4.27, 8.3.36, 8.2.35) plus the end-of-life 8.1, 8.0, and 7.x branches. A match means the snapshot held an unpatched engine; it executes nothing.
rule PHP2Shell_Exposed_Engine_Version
{
meta:
description = "PHP engine version banner below the PHP2Shell patched floor (8.5<12, 8.4<27, 8.3<36, 8.2<35) or an EOL branch (8.1/8.0/7.x)"
reference = "CVE-2026-109618, CVE-2026-109619"
strings:
$v85 = /PHP\/8\.5\.([0-9]|1[01])[^0-9]/
$v84 = /PHP\/8\.4\.([0-9]|1[0-9]|2[0-6])[^0-9]/
$v83 = /PHP\/8\.3\.([0-9]|[12][0-9]|3[0-5])[^0-9]/
$v82 = /PHP\/8\.2\.([0-9]|[12][0-9]|3[0-4])[^0-9]/
$eol81 = /PHP\/8\.1\.[0-9]/
$eol80 = /PHP\/8\.0\.[0-9]/
$eol7 = /PHP\/7\.[0-9]+\.[0-9]/
condition:
any of them
}
Potential Compromise - PHP Webshells: The next two rules identify files for investigation. The first looks for command-execution functions and web-request input references in the same PHP file. It does not establish whether that input reaches an execution function.
rule PHP_Webshell_Cmd_Exec
{
meta:
description = "Command-execution functions and request-input references in one PHP file"
strings:
$php = "<?php"
$exec = /\b(shell_exec|passthru|proc_open|pcntl_exec|popen|system|exec)\s*\(/
$src = /\$_(GET|POST|REQUEST|COOKIE)\s*\[/
condition:
$php and $exec and $src
}
The second looks for eval or assert alongside decoding functions or web-request input references. These combinations can appear in webshells, but also in legitimate code.
rule PHP_Webshell_Obfuscated_Eval
{
meta:
description = "PHP eval/assert with decoding or request-input patterns"
strings:
$php = "<?php"
$eval = /\b(eval|assert)\s*\(/
$d1 = "base64_decode"
$d2 = "gzinflate"
$d3 = "gzuncompress"
$d4 = "str_rot13"
$src = /\$_(GET|POST|REQUEST|COOKIE)\s*\[/
condition:
$php and $eval and (any of ($d*) or $src)
}
Tuning a Threat Hunt for speed without losing accuracy: A hunt spends its time reading files, so you speed it up by narrowing WHERE it looks, not WHAT it looks for. Your hashes, YARA rules, and file patterns stay identical, so detection accuracy is unchanged; you're only shrinking the haystack.
Four settings do most of the work:
1. Snapshots to scan: Scan the latest 1 for "Is this host affected right now?", or just the 2–3 around the suspected window for "When did it land?" Don't scan the whole retention history (usually the biggest single speed-up). You still cover the recovery points that matter.
2. Max file size: Match the cap to what each rule reads, and note that RSC applies one size limit per hunt, not per rule. Web shells are only a few kilobytes, but a PHP version banner sits about 4 MB inside a 5–10 MB engine binary, and a dropped Go/ELF payload runs 2–15 MB. Run the hunt with a maximum around 16 MB so the version and binary detections both work; a blanket 1 MB cap silently misses the engine version (a false “all clear”) and the dropped binary. For the version inventory, also set a minimum around 1 MB to skip the text and cache files that merely mention a version string.
3. File type/path: Point the hunt at the application's directories and script types only (for a PHP web-shell, the web folders and .php files) instead of the whole disk. Match the filter to where that threat actually lands.
4. Changed-files only: For recurring hunts, after one full baseline, scan only files modified since the last run. You catch anything new or tampered with in a fraction of the time.
Rule of thumb: keep your indicators fixed, and limit scope by snapshot range, file size, file type/path, and change date. Speed comes from scope, so accuracy stays intact.
Tip: When you first add a file path/type filter, run it once against a known-affected snapshot to confirm it still flags the artifact before you rely on it.
It’s important to treat these matches as leads for investigation. These rules can match legitimate files and will not detect every webshell. After confirming a file is malicious, use its SHA-256 hash to search other protected workloads and snapshots for exact copies. YARA rules can identify files that share the specified patterns.
Lab Results
In our lab we ran both hunts against PHP servers inside Rubrik backups. The exposure hunt read the engine version straight from the snapshot, matching the unpatched banner inside the engine binaries (/usr/bin/php8.1 and libphp8.1.so on an end-of-life PHP 8.1 host, and PHP 7.4 and 8.2 engines on other hosts). The compromise hunt identified the planted webshell (reports_export.php). Narrowing the file-size window to a 1 MB minimum and a 16 MB maximum cut one six-host run from 132 matches to 9, dropping text-file and browser-cache noise with no real detection lost.
Rubrik Security Cloud Threat Hunting: exposure hunt result — unpatched PHP engine version (PHP 8.1, end-of-life)
Rubrik Security Cloud Threat Hunting: webshell hunt result for reports_export.php
Use the snapshot results to trace where the matched files appear in the scanned backup history. The earliest matched snapshot helps establish when an artifact was present; it does not necessarily mark the start of the intrusion. The latest snapshot without matches is a candidate for recovery, subject to further validation.
A patch closes the vulnerability, but the investigation stays open until someone checks what the attacker left behind. Exploitation of PHP2Shell began four days before disclosure, so any server running vulnerable PHP in that window needs a review of its backups.
Rubrik Threat Hunting supplies the evidence: the earliest snapshot with a match, the latest snapshot without one, and the SHA-256 hash of any confirmed file for finding exact copies across other workloads. Patching secures the server going forward. Recovery depends on knowing which backup went in clean.