php2shell.io
Independent technical resourceCVE-2026-109618 / CVE-2026-109619Reviewed 2026-10-21
VISIBILITY / REQUEST → WORKER → HOST

Detection & monitoring.

Collect the context that connects unusual requests to PHP errors, worker behavior, and host changes. Use it to investigate attempted exploitation and possible compromise.

Look for a sequence, not a single string.

Signals are leads, not proof

Some paths disclose memory or execute inside PHP without a shell process. A quiet dashboard does not establish safety. Keep patching as the recommended action.

Start with web request metadata, runtime errors, and process-creation events. Join them by time, host, backend, and worker where possible. Establish a baseline and verify that logging reaches your collector before turning a query into an alert.

Request anomalyPHP / worker eventHost investigation

These configurations and queries are starting templates. Match their fields, paths, services, and thresholds to your deployment. They have not been deployed or validated against live exploitation.

Request metadata at the web server

MONITOR / 01

Start with timestamps, request volume, response status, duration, backend identity and a multipart flag. The examples deliberately omit raw URLs, query strings, cookies, authorization headers and request bodies.

nginx / map and log_format in the existing http {} block
map $http_content_type $php2shell_is_multipart {
    default 0;
    ~*multipart/form-data 1;
}

log_format php2shell_security escape=json
    '{"ts":"$time_iso8601","request_id":"$request_id",'
    '"client":"$remote_addr","server":"$server_name",'
    '"method":"$request_method","multipart":$php2shell_is_multipart,'
    '"status":$status,"request_bytes":$request_length,'
    '"response_bytes":$body_bytes_sent,"seconds":$request_time,'
    '"upstream":"$upstream_addr","upstream_status":"$upstream_status"}';
nginx / add to each relevant existing server {} block
access_log /var/log/nginx/php2shell-security.jsonl php2shell_security;
Apache / existing VirtualHost; requires mod_log_config and mod_setenvif
SetEnvIfNoCase Content-Type "multipart/form-data" php2shell_multipart=1

LogFormat "ts=%{%Y-%m-%dT%H:%M:%S%z}t client=%a peer=%{c}a vhost=%v method=%m multipart=%{php2shell_multipart}e status=%>s response_bytes=%B duration_us=%D worker_pid=%P error_id=%L" php2shell_security
CustomLog "/var/log/apache2/php2shell-security.log" php2shell_security
  • Test syntax with nginx -t or apachectl configtest, then reload using your service manager. Check effective logging in every location and virtual host; nested access_log settings and access_log off can override nginx inheritance.
  • Configure client-IP restoration only for trusted proxies. An untrusted forwarded address is attacker-controlled. Retain transport-peer or network-flow records when investigating origin bypass.
  • The Apache multipart field is 1 for a match and normally '-' otherwise. error_id may be '-' when no corresponding error-log entry exists. worker_pid is an Apache process, not an FPM worker.
  • nginx request_bytes includes the request line, headers and body; it is not decoded multipart size or PHP memory use. A small request can still cause substantial allocation.
  • Add server-controlled route categories and a trusted correlation ID through your existing tracing pipeline if needed. Avoid logging paths that contain reset tokens or identifiers.
  • Ship these records centrally with host/container labels, clock synchronization, rotation and restricted retention. The snippets do not sanitize other logs already enabled; audit those separately.

Sources: nginx HTTP access logging · Apache HTTP Server request logging · CVE-2026-109618: out-of-memory corruption

PHP errors, memory pressure and worker exits

MONITOR / 02

Collect PHP diagnostics, FPM master logs, web-server errors and kernel/container lifecycle events together. Enable settings before requests begin; application-level ini_set() cannot recover earlier diagnostics.

PHP-FPM / add to each existing pool; adapt paths and permissions
php_admin_flag[log_errors] = on
php_admin_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/php-errors.log

; Enable E_ALL during an isolated configuration/application audit.
php_admin_value[error_reporting] = E_ALL

access.log = /var/log/php-fpm/security-access.log
access.format = "ts=%{%Y-%m-%dT%H:%M:%S%z}t pool=%n pid=%p status=%s duration_us=%{microseconds}d peak_bytes=%{bytes}M"
Linux / read-only examples; adapt paths and unit names
rg -n -i -- 'Allowed memory size of .* exhausted|Maximum execution time of .* exceeded|zend_mm_heap corrupted|PHP (Fatal|Parse) error|Uncaught' /var/log/php-fpm/php-errors.log

journalctl -u php8.4-fpm --since '1 hour ago' --no-pager

journalctl -k --since '1 hour ago' --no-pager
  • Create non-public log paths with appropriate service write access and collector read access. Validate FPM configuration, replace workers to apply startup settings, and confirm records reach the collector.
  • For mod_php use the equivalent log_errors, display_errors, error_log and audit error_reporting settings in the active web php.ini, then restart Apache workers. The FPM access directives do not apply to mod_php.
  • Look for pre-script configuration warnings/deprecations, cleanup diagnostics, fatal/parse errors and execution timeouts as well as memory exhaustion. The regex is a starting filter, not a complete signature.
  • Correlate FPM child exit signals, unexpected respawns, kernel OOM kills and container termination reasons. Service journald records may not contain PHP errors written to a file; ingest both.
  • Pool plus PID plus host/container and process start time helps distinguish a persistent worker from PID reuse. A worker that crashes may never write its normal access record.
  • Imports, bugs, deployment restarts and normal pm.max_requests recycling are common alternatives. Logging may expose input in exception messages: restrict access, scrub secret fields at collection and keep verbose auditing time-bounded.
  • Changing error-reporting or display settings does not mitigate either CVE. Historical log gaps and unexercised application paths remain blind spots.

Sources: CVE-2026-109619: stale global state · CVE-2026-109618: out-of-memory corruption · PHP-FPM pool configuration

Unexpected children of PHP workers

MONITOR / 03

Use process-creation telemetry to investigate PHP or Apache launching a shell or transfer utility. This experimental Sigma template is a behavioral lead; it does not identify a particular CVE.

Sigma / Linux process_creation template; map fields to your EDR or SIEM
title: PHP Worker Starts Shell Or Transfer Utility
status: experimental
description: Investigate unexpected child processes; not a PHP2Shell-specific signature.
author: php2shell.io
date: 2026-10-21
logsource:
    category: process_creation
    product: linux
detection:
    parent:
        ParentImage|endswith:
            - '/php-fpm'
            - '/php-fpm8.2'
            - '/php-fpm8.3'
            - '/php-fpm8.4'
            - '/php-fpm8.5'
            - '/php-cgi'
            - '/apache2'
            - '/httpd'
            - '/lsphp'
            - '/frankenphp'
    child:
        Image|endswith:
            - '/sh'
            - '/bash'
            - '/dash'
            - '/zsh'
            - '/curl'
            - '/wget'
            - '/nc'
            - '/ncat'
            - '/socat'
    condition: parent and child
fields:
    - Image
    - ParentImage
    - ProcessId
    - ParentProcessId
    - User
falsepositives:
    - Approved application subprocesses, converters and maintenance tasks
level: medium
  • Requires Linux execution events containing resolved executable and parent executable paths. Convert and validate the Sigma rule with the backend and field mappings used by your deployment; this is not a standalone executable detector.
  • Adapt executable names to inventory, including versioned CGI, LiteSpeed and embedded runtimes. Monitor php-fpm when nginx or Apache forwards to FPM; monitoring nginx descendants alone misses that runtime.
  • Investigate process ancestry, service account, deployment context and outbound connections. Use narrow, documented exceptions for legitimate behavior rather than excluding the entire web-service account.
  • This direct-parent template misses later descendants, renamed tools, shell built-ins and actions performed inside PHP. No child process is required for memory disclosure or every RCE path.
  • Command lines can expose secrets. Restrict richer forensic command-line collection rather than publishing raw argv in general dashboards. Track event loss and sensor health.

Sources: Sigma rule specification · process-watcher · Early PHP2Shell incident-response guidance

Optional HTTP inspection with Zeek

MONITOR / 04

Corelight publishes a Zeek package for suspicious form keys, multipart headers and serialized-data patterns. It can connect related requests by configured session cookies; its findings require investigation.

Zeek / local policy after installing and reviewing corelight/php2shell-detect
@load packages

# Suppress raw POST-body excerpts in detector notices.
redef PHP2SHELL::notice_body_sample_bytes = 0;
  • Requires Zeek 7.0 or later, the reviewed package installed through your normal package process, and HTTP visible to the analyzer. Encrypted traffic needs an appropriate inspection point.
  • Review PHP2SHELL notices in notice.log. PHP_Crash_Sequence means related suspicious transactions, not a confirmed operating-system crash or successful exploitation.
  • The documented defaults inspect POST bodies only within the first three HTTP transactions per connection, with an 8 MiB budget per transaction shared across MIME parts. These limits and parser differences leave gaps.
  • The package retains cookie correlation in memory; disabling its body excerpts does not sanitize other sensor logs. Review packet capture and adjacent logging policies separately.
  • Use current upstream advisories and package provenance for version verification. Response banners can be hidden, rewritten or show a version with vendor backports. Legitimate traffic can trigger these indicators.

Sources: PHP2Shell detection before disclosure · PHP2Shell detections for Zeek and Suricata

Correlate changes instead of trusting one indicator

MONITOR / 05

Compare requests with your normal workload, then connect anomalies to the same backend and worker timeline. Repeated multipart errors, cookie-shape changes and unexpected process activity become more useful when they agree.

Splunk SPL / illustrative hunt over the nginx JSON fields above
index=web sourcetype=php2shell:nginx multipart=1
| bin _time span=5m
| stats count AS requests
    count(eval(status>=500 AND status<600)) AS server_errors
    values(upstream) AS backends
    by _time server client
| where requests>=10 AND server_errors>=3
  • Prerequisite: ingest the JSON fields, map ts to event time, preserve host/container identity, and replace the example index and sourcetype. Ten requests and three server errors in five minutes are illustrative tuning values, not research-derived exploit thresholds.
  • Baseline by application and traffic pattern. Bulk uploads, imports, deployment failures, bots and clients behind shared NAT can create the same pattern; distributed or low-volume activity can evade this grouping.
  • Pivot from a result into PHP error and process records before and after the interval. For stale-state paths, examine successive requests handled by the same worker, not only a single request.
  • Investigate unexpectedly large or malformed PHPSESSID response-cookie values. Prefer derived length and format flags from an approved sensor, adjusted for custom session names and expected encoding. Do not log raw Set-Cookie headers or session values; disclosure may place sensitive memory there.
  • The baseline access logs above do not collect multipart field names or response cookies. A simple grep for PHP_SESSION_UPLOAD_PROGRESS cannot find a body field that was never logged; use authorized inspection metadata rather than broadly enabling body capture.
  • Treat a rare high-impact event seriously even below the illustrative thresholds. A 2xx response, no shell process, or no matching alert does not establish that exploitation was absent.

Sources: Original PHP2Shell disclosure site · PHP2Shell technical research · CVE-2026-109619: stale global state · Splunk aggregate functions

WAF visibility and origin bypass

MONITOR / 06

Record which policy evaluated a request and whether its action was enforced. Compare edge activity with origin traffic so a quiet WAF dashboard is not mistaken for a quiet application.

Google Cloud Logs Explorer / enforced Cloud Armor denials
resource.type="http_load_balancer"
jsonPayload.enforcedSecurityPolicy.outcome="DENY"
AWS CloudWatch Logs Insights / the custom rule from Controls
fields @timestamp, action, terminatingRuleId
| filter terminatingRuleId = "BlockMultipartToUnpatchedPHP"
| stats count(*) as requests by bin(5m), action
| sort requests desc
  • Cloud Armor requires load-balancer logging on the relevant backend service. Its sampling rate controls visibility; preview matches and enforced actions are different. Filter by your policy/resource when multiple applications share a project.
  • AWS requires web ACL traffic logging to the queried CloudWatch log group. Change terminatingRuleId to your deployed rule name. The example selects terminating matches; Count matches and nested rule-group matches need their own fields.
  • Track action, rule ID, policy revision, request time, inspected-size limits and origin status. AWS oversizeFields describes components inspected above the service limit; it is not proof of malicious content.
  • Redact secrets and query strings in vendor logs. AWS log redaction does not automatically redact sampled requests or Security Lake data; review each destination separately. Cloudflare Security Events and other sampled views likewise are not a complete request archive.
  • Compare origin connection peers with the approved load-balancer/proxy path, accounting for health checks and internal traffic. Missing edge events may be sampling or delivery failure; unexpected direct-origin flows warrant investigation.
  • Check changes to policy attachments, priorities, Count/preview settings, exceptions and DNS exposure. A blocked request proves that rule matched that request, not that PHP is patched or the origin was never reached by another path.

Sources: Cloud Armor request logging · AWS WAF log fields · AWS WAF web ACL rule specification · Cloudflare request-header fields

Preserve context and investigate the host

MONITOR / 07

Use correlated evidence to decide scope and containment. Keep the runtime upgrade moving; detection and investigation cannot replace remediation.

  • Preserve relevant edge, origin, PHP, process and network records with timestamps, time zones, host/container identifiers and the affected package build or image digest. Keep original evidence access-controlled.
  • Review new or modified files in web roots, upload directories, temporary storage, PHP configuration and startup/persistence locations against trusted deployment manifests or file-integrity baselines. Expected deployments and uploads are common sources of change.
  • Investigate unexpected outbound connections, service-account activity and child-process chains alongside file changes. Fileless execution and memory disclosure may leave no new file.
  • If compromise is credible, use the incident-response process to restrict exposure and isolate affected instances while preserving evidence. Prefer rebuilding from trusted images, rotating potentially exposed credentials and invalidating affected sessions as appropriate to the findings.
  • Record whether symptoms preceded the patch, verify all replicas and workers are replaced, and retain monitoring after remediation to investigate earlier exposure.
  • Validate collection with benign requests and known test events in staging. Confirm time alignment, parser behavior, rotation and delivery; do not run public exploit code against production to check an alert.

Sources: Original PHP2Shell disclosure site · PHP2Shell technical research · CVE-2026-109618: out-of-memory corruption · CVE-2026-109619: stale global state