When the php2shell research first broke, the security community braced for a mass-exploitation event — the kind of internet-wide scramble where every vulnerable PHP instance gets popped within days. A week of hands-on testing by Tenable's research team has produced a more nuanced, and frankly more instructive, picture: php2shell does not behave like a fire-and-forget exploit. In realistic environments, turning it into reliable unauthenticated remote code execution requires an exploit built — tuned, fitted — for the specific target's PHP build, configuration, and platform.
That is simultaneously good news and a warning. The good news: the five deliberately vulnerable PHP sites Tenable exposed to the internet were not compromised, and the public proofs-of-concept circulating right now do little more than version checks — they do not reliably confirm exploitability. The warning: where systems share a single common build — most notably network appliances shipping a uniform embedded PHP stack — one well-tuned exploit can reach every device running that build. Exploitation has already been confirmed on researcher honeypots and on some network appliances.
For defenders, the takeaway is clear: you have a window. Mass exploitation is harder than the initial headlines suggested, but targeted exploitation — especially against homogenous appliance fleets and high-value PHP applications — is viable right now. This post breaks down the threat from a defender's perspective and gives you the detection content, hunting queries, and hardening guidance to use that window well.
Technical Analysis
What php2shell Actually Is
php2shell is the name given to a PHP remote code execution primitive that, under the right conditions, allows an unauthenticated attacker to coerce a target PHP interpreter into executing attacker-controlled code — typically by abusing how PHP handles attacker-influenced input in a specific configuration or build context. The critical finding from a week of testing is that reliability is build-dependent: the difference between a crash, a no-op, and a working shell often comes down to the target's exact PHP version, compile-time options, loaded extensions, OS platform, and surrounding configuration (web server integration, SAPI type, and hardening directives).
This is consistent with what we see in the field during IR engagements: memory-corruption and interpreter-abuse exploits against scripting runtimes are notoriously brittle. An exploit author must account for heap layout, binary offsets, and runtime behavior that vary across builds. A payload tuned against a stock PHP-FPM 8.x build on Debian will frequently fail against the same nominal version compiled by an appliance vendor with different flags.
Affected Exposure: Who Is Actually at Risk
Based on the research findings, risk stratifies into three tiers:
- Homogenous, shared-build systems (highest risk): Network appliances — firewalls, VPN gateways, load balancers, web application firewalls, and management interfaces — that embed a single, uniform PHP build across every shipped device. Once an exploit is tuned to that build, it works against every instance of that product everywhere. Confirmed exploitation on some network appliances has already occurred.
- Internet-facing PHP applications running common stacks (moderate risk): Stock PHP builds behind Nginx/Apache with PHP-FPM or mod_php. Exploitation is possible but requires per-target tuning, which raises attacker cost and lowers reliability — this is why the researchers' five honeypot sites survived the week.
- Custom or heavily hardened PHP deployments (lower risk): Non-standard builds, hardened configurations (disabled dangerous functions,
open_basedir, read-only filesystems, container isolation) increase the per-target effort significantly — but do not make exploitation impossible against a determined adversary.
Exploitation Status
- Public PoCs exist but, per the research, mostly stop at version fingerprinting — they do not confirm whether a host is actually exploitable. Defenders should treat external "vulnerability scan" results based on these PoCs with skepticism: a version match is not confirmation of exploitable condition.
- Confirmed exploitation: Observed on researcher honeypots and on certain network appliances. This is real-world activity, not theory — the exploit is in adversary hands and working against tuned targets.
- Attacker behavior pattern: Expect opportunistic scanning followed by targeted exploitation. The actors achieving shells are the ones investing effort in per-build tuning — which correlates with more capable adversaries, including initial access brokers who prize appliance shells for persistent footholds.
Why This Matters to Your Defense
The brittleness of the exploit creates an asymmetric defensive opportunity. Every failed exploitation attempt is noisy: crashed PHP-FPM workers, segmentation faults in interpreter processes, anomalous child process spawns from the web stack, and unusual request patterns as the attacker iterates payload variants. A SOC that is watching for exploitation attempts and failures, not just successful shells, can catch this activity early — often before a working payload is ever fitted to your build.
Detection & Response
The detections below target the observable behaviors inherent to this attack class: interpreter crash loops from failed exploit tuning, web-server/PHP processes spawning shells or command interpreters (the "shell" in php2shell), and post-exploitation command execution. These are high-fidelity behaviors — tune thresholds to your environment before production deployment.
Sigma Rules
---
title: PHP Interpreter or Web Server Process Spawning Shell
type-2
description: Detects PHP-FPM, mod_php (Apache httpd), or PHP CLI processes spawning shell or command interpreter child processes. This is the core post-exploitation behavior of php2shell-style PHP RCE, where the interpreter is coerced into executing attacker commands.
references:
- https://www.tenable.com/blog/php2shell-one-week-later-why-one-exploit-doesnt-work-everywhere
- https://attack.mitre.org/techniques/T1059/
type-2
date: 2026/01/09
type-2
- attack.t1059.004
- attack.t1190
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/php-fpm'
- '/php-fpm8.1'
- '/php-fpm8.2'
- '/php-fpm8.3'
- '/php'
- '/httpd'
- '/apache2'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/zsh'
- '/python'
- '/python3'
- '/perl'
- '/nc'
- '/ncat'
- '/socat'
- '/curl'
- '/wget'
condition: selection_parent and selection_child
falsepositives:
- PHP applications that legitimately shell out (image processing, mail sending, backup scripts) — inventory and allowlist known application workflows
- Some network appliance management functions execute system commands via the web stack — baseline per appliance type
level: high
---
title: Repeated PHP-FPM Worker Crashes Indicating Exploit Tuning Attempts
type-2
description: Detects segmentation faults and crash signals in PHP interpreter processes, a hallmark of failed exploitation attempts as an attacker iterates payload variants against a target build. php2shell exploitation is build-specific; failed attempts crash workers before a working payload is found.
references:
- https://www.tenable.com/blog/php2shell-one-week-later-why-one-exploit-doesnt-work-everywhere
- https://attack.mitre.org/techniques/T1190/
type-2
date: 2026/01/09
type-2
- attack.t1190
logsource:
category: process_creation
product: linux
detection:
selection_journald:
Message|contains:
- 'segfault at'
- 'general protection fault'
Message|contains|all:
- 'php'
selection_dmesg:
Message|contains:
- 'php-fpm'
- 'php['
Message|contains:
- 'segfault'
- 'trap '
- 'core dumped'
condition: 1 of selection_*
falsepositives:
- Buggy PHP extensions can crash under normal load — investigate frequency and source IP correlation before dismissing
- Development or staging environments with unstable code
level: medium
---
title: Web Shell Dropped to PHP Application or Appliance Web Root
type-2
description: Detects creation of PHP files in web-accessible directories by the web server or PHP interpreter account, consistent with post-exploitation persistence following php2shell-style RCE on PHP applications or network appliances.
references:
- https://www.tenable.com/blog/php2shell-one-week-later-why-one-exploit-doesnt-work-everywhere
- https://attack.mitre.org/techniques/T1505/003/
type-2
date: 2026/01/09
type-2
- attack.t1505.003
- attack.persistence
logsource:
category: file_event
product: linux
detection:
selection:
TargetFilename|endswith:
- '.php'
- '.phtml'
- '.php5'
- '.phar'
TargetFilename|contains:
- '/var/www/'
- '/srv/www/'
- '/usr/share/nginx/'
- '/htdocs/'
- '/www/'
- '/ui/'
- '/admin/'
- '/cgi-bin/'
condition: selection
falsepositives:
- Legitimate application deployments and CMS plugin installations — correlate with change windows and deployment pipelines
- Appliance firmware updates writing web UI components
level: high
KQL — Microsoft Sentinel Hunting
The following query hunts Linux estates ingested into Sentinel via Syslog/CEF for the two strongest signals: web-stack processes spawning shells, and interpreter crash patterns indicating exploit tuning. Deploy it as a scheduled analytics rule with entity mapping on Host and Account.
// Hunt: PHP/web stack spawning command interpreters or download tools (php2shell post-exploitation)
// Sources: Syslog (Linux), DeviceProcessEvents (MDE-on-Linux), CommonSecurityLog (appliance CEF)
let WebStackProcs = dynamic(["php-fpm", "php", "httpd", "apache2", "nginx"]);
let ShellProcs = dynamic(["sh", "bash", "dash", "zsh", "python", "python3", "perl", "nc", "ncat", "socat", "curl", "wget"]);
union isfuzzy=true
(Syslog
| where Facility == "user" or Facility == "daemon" or Facility == "auth"
| where SyslogMessage has_any (WebStackProcs)
| extend ChildProc = extract(@"(sh|bash|dash|python3?|perl|nc|ncat|socat|curl|wget)\s", 1, SyslogMessage)
| where isnotempty(ChildProc)
| project TimeGenerated, Computer, HostIP, ProcessName, SyslogMessage),
(DeviceProcessEvents
| where InitiatingProcessFileName in~ (WebStackProcs)
| where FileName in~ (ShellProcs)
| project TimeGenerated, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, AccountName),
(CommonSecurityLog
| where DeviceProduct has_any ("firewall", "vpn", "gateway", "waf", "proxy", "appliance")
| where Message has_any ("segfault", "core dumped", "command injection", "shell")
| project TimeGenerated, DeviceName, DeviceProduct, SourceIP, DestinationIP, Message)
| summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), EventCount = count()
by Computer, DeviceName, FileName, ProcessName, SourceIP
| where EventCount >= 1
| sort by LastSeen desc;
A companion crash-hunting query for exploit-tuning activity:
// Hunt: repeated PHP interpreter crashes from the same source — exploit payload iteration
Syslog
| where SyslogMessage has_any ("segfault", "core dumped", "general protection fault")
| where SyslogMessage has_any ("php", "php-fpm", "httpd")
| summarize CrashCount = count(), DistinctFaults = dcount(SyslogMessage),
FirstCrash = min(TimeGenerated), LastCrash = max(TimeGenerated)
by Computer, HostIP
| where CrashCount >= 3
| extend WindowMinutes = datetime_diff("minute", LastCrash, FirstCrash)
| sort by CrashCount desc;
Velociraptor VQL
Use this artifact to sweep Linux web servers and appliances for live evidence of web-stack-spawned shells and recently written PHP files in web roots — the two forensic artifacts that survive even when an attacker cleans up their session.
-- Artifact: PHP.RCE.Hunt — php2shell post-exploitation sweep
-- Hunts for: (1) web stack processes with shell/download-tool children,
-- (2) recently created PHP files in web-accessible paths,
-- (3) PHP-FPM processes with anomalous open network connections
LET webstack_regex = '(php-fpm|php$|httpd|apache2|nginx)'
LET shell_regex = '(sh|bash|dash|zsh|python|perl|nc|ncat|socat|curl|wget)$'
LET webroot_globs = '/{var/www,srv/www,usr/share/nginx,www,htdocs}/**/*.php'
SELECT * FROM foreach(
row={
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ webstack_regex OR Exe =~ webstack_regex
},
query={
SELECT Pid AS WebPid, Name AS WebProc, Username AS WebUser,
CreateTime AS WebProcStart
FROM scope()
})
UNION ALL
SELECT * FROM {
SELECT Pid, Name AS ChildProc, Exe AS ChildExe, CommandLine AS ChildCmdline,
Username, CreateTime AS ChildStart, Ppid
FROM pslist()
WHERE Name =~ shell_regex
}
UNION ALL
SELECT * FROM {
SELECT FullPath, Mtime, Ctime, Size
FROM glob(globs=webroot_globs)
WHERE Ctime > now() - 604800 -- created in last 7 days
ORDER BY Ctime DESC
}
Verification & Hardening Script
Run this on Linux PHP hosts to inventory your PHP attack surface, verify hardening directives, enumerate web-accessible PHP files with recent modifications, and check for crash evidence. It is read-only by default — review output before acting.
#!/usr/bin/env bash
# php2shell-hardening-audit.sh — inventory, verify, and audit PHP attack surface
# Run as root or via sudo. Read-only: produces a report for review.
REPORT="php2shell-audit-$(hostname)-$(date +%Y%m%d).txt"
exec > >(tee "$REPORT") 2>&1
echo "=== [1/6] PHP inventory ==="
php -v 2>/dev/null || echo "php CLI not found (check for embedded/appliance builds)"
ps aux | grep -E '[p]hp-fpm|[h]ttpd|[a]pache2' | head -20
echo ""
echo "=== [2/6] Dangerous function & config audit ==="
for ini in $(php --ini 2>/dev/null | grep -E 'Loaded|Scan' -A2 | grep -oE '/[^ ]+\.ini' | sort -u); do
echo "--- $ini ---"
grep -E '^(disable_functions|open_basedir|allow_url_include|allow_url_fopen|expose_php|display_errors)' "$ini" 2>/dev/null
done
echo "RECOMMENDED: disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec"
echo "RECOMMENDED: allow_url_include = Off, expose_php = Off, display_errors = Off"
echo ""
echo "=== [3/6] Recent PHP files in web roots (last 7 days) — review all of these ==="
for d in /var/www /srv/www /usr/share/nginx /www /htdocs /opt/*/www; do
[ -d "$d" ] && find "$d" -name '*.php' -o -name '*.phtml' -o -name '*.phar' 2>/dev/null | xargs -I{} find {} -mtime -7 2>/dev/null
done
echo ""
echo "=== [4/6] PHP interpreter crash evidence (possible exploit tuning attempts) ==="
journalctl --since "7 days ago" 2>/dev/null | grep -iE 'segfault|core dumped' | grep -iE 'php|httpd' | tail -30
command -v coredumpctl >/dev/null && coredumpctl list --since "7 days ago" 2>/dev/null | grep -iE 'php|httpd'
echo ""
echo "=== [5/6] Web stack processes with shell children (live check) ==="
for pid in $(pgrep -f 'php-fpm|httpd|apache2'); do
children=$(pgrep -P "$pid" -a 2>/dev/null | grep -E '/(sh|bash|dash|python|perl|nc|socat|curl|wget)\b')
[ -n "$children" ] && echo "ALERT: PID $pid ($(ps -p $pid -o comm=)) has shell children:" && echo "$children"
done
echo ""
echo "=== [6/6] Outbound connections from web stack (possible reverse shells) ==="
ss -tnp 2>/dev/null | grep -E 'php|httpd|apache' | grep -v ':80\|:443\|:9000' | head -20
echo ""
echo "Audit complete. Report saved to $REPORT"
Remediation
Given that exploitation is confirmed in the wild — particularly against network appliances — treat this as an active-threat remediation, not a routine patch cycle.
Immediate actions (24–48 hours):
- Inventory your PHP attack surface. Enumerate every internet-facing PHP application and every network appliance in the estate that embeds PHP in its management UI (firewalls, VPN concentrators, ADCs/load balancers, WAFs, storage and backup appliances, IPMI/DCIM consoles). Appliances are the priority — they are the homogenous-build targets where one tuned exploit scales.
- Pull appliances off the public internet. If any appliance management interface is internet-reachable, restrict it to a management VLAN or VPN immediately. This single step removes the highest-risk exposure class.
- Apply vendor updates. Check each appliance and PHP application vendor for security advisories addressing the php2shell issue and apply firmware/patch releases per vendor guidance. Monitor Tenable's research page (https://www.tenable.com/blog/php2shell-one-week-later-why-one-exploit-doesnt-work-everywhere) for updated affected-product details as vendors disclose.
- Deploy the detections above — the parent/child process rule and crash-loop rule give you coverage against both successful exploitation and tuning attempts during the patch window.
Hardening (this week):
- Disable dangerous PHP functions on anything you control:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_execinphp.ini. This breaks the most common post-exploitation path even if the RCE primitive succeeds. - Set
allow_url_include = Off,expose_php = Off,display_errors = Offand applyopen_basedirrestrictions to limit filesystem reach. - Isolate PHP workers: run PHP-FPM pools as dedicated low-privilege users, in containers or with read-only filesystem mounts where feasible. A shell that lands in a read-only, unprivileged, network-restricted container is a dramatically weaker foothold.
- Tune your vulnerability management expectations: public PoCs for this issue mostly perform version checks. Do not accept "version match = exploitable" as a scan result, and equally do not accept "PoC failed = safe." The exploit's build-sensitivity cuts both ways. Validation should be asset-inventory-driven: PHP present + internet-reachable + unpatched = remediate, regardless of PoC behavior.
Ongoing:
- Hunt retroactively. Exploitation predates your detection deployment. Run the VQL sweep and the crash-log analysis across at least the past two weeks; investigate any PHP file created in a web root outside a deployment window.
- Treat failed exploitation as a signal. Because php2shell requires per-target tuning, repeated crash loops against one host indicate a determined, targeted adversary investing effort against your specific build — escalate accordingly, and assume a follow-on attempt with a corrected payload is coming.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.