Detection & response

Was I hit?

No single rule catches every variant, and the vulnerable code runs below your application, so app logs may show nothing. Use these signals to prioritise — not to clear a system.

Last reviewed 2026-10-22

Absence of signals is not proof of safety

The memory-disclosure path can steal secrets without ever executing code on the server. Patch regardless of what you find.

Signals worth investigating

SignalWhy it mattersWhere to look
Memory-limit fatals right after multipart POSTsBoth CVEs are reached by pushing PHP into an out-of-memory error while it parses a request.PHP / FPM error log + web access log
Bursts of HTTP 500 from one clientExploits need several attempts to adapt to the target’s memory layout.Access log
Workers dying on SIGSEGV / SIGABRT, or unusual worker churnUse-after-free attempts that miss tend to crash the worker.FPM log, Apache error log, dmesg / journal
Unusually long or percent-encoded PHPSESSID in responsesThe memory-disclosure path URL-encodes leaked heap memory into the session cookie — even on sites that never call session_start().Reverse proxy / CDN / WAF logs of Set-Cookie (not in default access logs)
New .php files in the web root, unexpected child processes of the PHP userClassic post-exploitation: web shells, curl/wget, reverse shells from www-data.File system, EDR, process accounting

A normal PHPSESSID is short and alphanumeric (by default 32 characters). Hundreds of characters or %xx byte sequences in one is not normal.

Search your logs

Paths are Debian/Ubuntu defaults. Adjust for your system; cover the period since early October 2026 at least.

shell
# 1. Memory-limit fatals (PHP's exact message)
sudo grep -rh "Allowed memory size of" /var/log/php*-fpm.log /var/log/apache2/ /var/log/httpd/ /var/log/nginx/ 2>/dev/null | tail -50

# 2. Workers killed by signals
sudo grep -rhE "exited on signal (6|11)|exit signal (Segmentation fault|Aborted)" \
  /var/log/php*-fpm.log /var/log/apache2/ /var/log/httpd/ 2>/dev/null | tail -50
sudo journalctl --since "2026-10-01" | grep -E "php.*segfault" | tail

# 3. Clients producing many 500s on POST (combined log format)
sudo awk '$6=="\"POST" && $9==500 {print $1}' /var/log/nginx/access.log* /var/log/apache2/access.log* 2>/dev/null \
  | sort | uniq -c | sort -rn | head -20

After patching, the new hard memory limit can also end workers abruptly. A worker exit on its own is not proof of an attack — look for clusters tied to the same client or request pattern.

Catch the memory leak at the proxy

nginx + shell
# Temporary: log Set-Cookie on responses to spot oversized PHPSESSID values.
# This writes session IDs to disk — restrict the file and remove it afterwards.
# In the http {} block:
log_format php2shell '$remote_addr [$time_local] "$request" $status "$sent_http_set_cookie"';
# In the server {} block:
access_log /var/log/nginx/php2shell.log php2shell;

# Then, from a shell, look for long or percent-encoded session cookies
grep -E 'PHPSESSID=[^;"]{64,}|PHPSESSID=[^;"]*%' /var/log/nginx/php2shell.log

Look for post-exploitation

shell
# PHP files changed in the web root since the bugs became known (adjust path/date)
sudo find /var/www -type f -name '*.php' -newermt '2026-08-01' -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort | tail -100

# Processes the web user is running right now (shells and downloaders are red flags)
ps -u www-data -o pid,ppid,lstart,cmd | grep -vE 'php-fpm|apache2|httpd'

# Established connections owned by PHP / Apache — review every remote address
sudo ss -tpn state established | grep -E 'php|apache|httpd'

WAF coverage

Managed rules are a stopgap while you patch. Check your provider’s PHP2Shell guidance. Cloudflare published its rule IDs — see the Cloudflare page.

Vendor detections

If you run one of these products, their write-ups describe the signatures and hunting queries they shipped.

Do not run “PoC” scripts from GitHub on your servers

Several repositories claim to be PHP2Shell exploits or scanners. Tenable could not verify that any of them work; fake PoCs are a common way to deliver malware to defenders. Use vendor tooling or the checks on this page.

If you find something

  1. Preserve evidence first: copy logs, take a disk / memory snapshot if you can, before you rebuild.
  2. Patch or isolate the host. Prefer rebuilding from a clean image over cleaning in place.
  3. Rotate everything PHP could see in memory: database passwords, API keys, OAuth client secrets, SMTP credentials, app keys (APP_KEY, WordPress salts), TLS private keys if PHP had access.
  4. Invalidate sessions so stolen cookies stop working, and ask admins to re-authenticate.
  5. Check the blast radius: what else those credentials reach (cloud accounts, internal APIs, other databases).