Security research · Lab validation · Detection engineering

PHP2Shell: From Remote Exploitation to SOC Detection

A controlled PHP 8.4.26 / Apache remote-code-execution proof of concept, with evidence collection in IBM QRadar and transferable Microsoft Sentinel hunting recommendations.

22 October 2026 · UTCSingle controlled exploit attemptRCE independently verifiedTarget stopped after validation

01 / Executive summary

On 22 October 2026, a deliberately vulnerable PHP/Apache container was tested from a separate Kali Linux VM using the PHP2Shell CVE-2026-109618/109619 research repository. A single exploit invocation disclosed memory addresses, delivered its final payload, and caused the target to write the output of the Linux id command to a dedicated proof volume. The output identified the executing account as www-data (UID 33).

At the same time, Apache/PHP syslog events were received by QRadar, including zend_mm_heap corrupted and an Apache worker abort. This provided a concrete example of how successful RCE can coexist with a normal-looking HTTP 200 response and how application logs alone may expose exploitation side effects rather than the executed command itself.

OutcomeRCE proven
Execution identitywww-data
Attempts1
SIEMIBM QRadar
Evidence boundary: The observed proof file establishes command execution in the container following the remote PoC. The exact internal heap exploitation mechanism was not independently reverse-engineered in this exercise. The two CVE identifiers are the repository's stated targets, not a separate verification of advisory scope or affected-version matrix.

02 / Lab architecture and controls

Kali Linux
192.168.1.101
→
Docker host
192.168.1.27:18088
→
PHP 8.4.26 + Apache
172.19.0.2:80
→
Syslog → QRadar
192.168.1.188:514/UDP
ComponentConfiguration / purpose
Target imageOfficial php:8.4.26-apache, Apache prefork/mod_php; single-worker configuration.
ApplicationMinimal /index.php returning OK; no intentionally vulnerable CMS, LFI, or file upload application.
Exploit sourcexuehuolll/php2shell-CVE-2026-109618-109619, pinned locally at commit 967585c.
NetworkDedicated Docker bridge cve_php2shell_isolated, subnet 172.19.0.0/16; target published only on host LAN address, TCP 18088.
StorageRead-only root filesystem, restricted mounts, temporary writable areas, dedicated named volume php2shell_output at /out.
LoggingDocker syslog driver, RFC 5424, tag php2shell-rce-lab, facility local0, QRadar UDP/514.

Containment validation

Dedicated DOCKER-USER forwarding rules allowed the intended Kali-to-target HTTP path and established return traffic while dropping other routed traffic to/from the target. A separate host INPUT rule blocked container-originated connections to the Docker host. Both rule sets were checked before exploitation; the Kali HTTP test and syslog ingestion were also verified.

Limitations: This was a temporary research environment, not a hardened production sandbox. The Docker network was non-internal so published-port access would work; isolation relied on verified host firewall policy. No claim is made that the container was protected against all kernel-escape or host-compromise scenarios. The target container was subsequently stopped; stopping it does not automatically remove the dedicated network, volumes, or firewall rules.

03 / Exploitation and evidence

The local repository revision matched on Kali and Portainer. The PoC supported a 14-byte maximum command string and defaulted to three attempts; the exercise explicitly limited execution to one attempt. The benign command id>/out/p1 produced a persistent, easily inspected artifact without requesting a shell or network callback.

Observed exploit stages

[*] attempt 1/1
[*] rotating worker
[*] leak 1/2: chunk base
[+] chunk base = 0x7f61c6600000
[*] rotating worker
[*] leak 2/2: libphp base
[+] libphp base = 0x7f61c826d000
[*] rotating worker
[*] fire: HTDATA=0x7f61c66690d8 ZIF=0x7f61c86d2370
[*] fire sent; check the proof file on the target

The client reported two address disclosures followed by a final payload delivery. Its completion message was not treated as proof of execution: the proof was checked independently inside the target container.

RCE PROOF FOUND:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Confirmed finding: A remote HTTP-triggered exploit resulted in execution of id in the PHP/Apache container as www-data. No persistence, lateral movement, data theft, or outbound command-and-control was part of the test.

Event timeline (UTC)

Baseline connectivity: Kali requested GET /index.php; HTTP 200 and OK returned. QRadar received the Apache access event.

Exploit activity: QRadar recorded POST /index.php, HTTP 200, source 192.168.1.101.

Runtime instability: zend_mm_heap corrupted and AH00161 (MaxRequestWorkers reached) were logged.

Worker abort: Apache logged AH00051, child PID 18 exited with signal 6 (Abort).

Independent verification: /out/p1 contained the www-data identity output. The target container was then stopped.

Selected QRadar evidence

2026-10-22T20:55:06Z ... 192.168.1.101 ...
"POST /index.php HTTP/1.1" 200 693 "-" "-"

2026-10-22T20:55:06Z ... zend_mm_heap corrupted

2026-10-22T20:55:06Z ... [mpm_prefork:error] ...
AH00161: server reached MaxRequestWorkers setting

2026-10-22T20:55:07Z ... [core:notice] ...
AH00051: child pid 18 exit signal Abort (6), possible coredump

Excerpted from the supplied QRadar event output. Source IP is present in the Apache access line; the crash events do not independently identify the HTTP client. Correlation relies on the common server/log source and close timestamps.

04 / IBM QRadar detection engineering

Primary hunting hypothesis: A PHP memory-corruption error or Apache worker abort occurring near unexpected HTTP POST activity on the same web server warrants investigation. Treat the runtime errors as high-value indicators of abnormal behavior, not unique signatures for PHP2Shell.

AQL — search PHP corruption and Apache worker failures

SELECT
  starttime,
  LOGSOURCENAME(logsourceid) AS LogSource,
  sourceip,
  UTF8(payload) AS RawEvent
FROM events
WHERE
  UTF8(payload) ILIKE '%zend_mm_heap corrupted%'
  OR UTF8(payload) ILIKE '%AH00051%'
  OR UTF8(payload) ILIKE '%AH00161%'
ORDER BY starttime DESC
LAST 24 HOURS

Use an appropriate time window and scope to the PHP/Apache log source or Docker syslog tag. Depending on QRadar parsing, sourceip may be the syslog sender (Docker host), not the remote HTTP client; parse the Apache access message separately.

AQL — review nearby HTTP POST requests

SELECT
  starttime,
  LOGSOURCENAME(logsourceid) AS LogSource,
  UTF8(payload) AS RawEvent
FROM events
WHERE
  UTF8(payload) ILIKE '%"POST /index.php HTTP/%'
ORDER BY starttime DESC
LAST 24 HOURS

Recommended CRE correlation logic

  1. Building block A — PHP runtime anomaly: event payload contains zend_mm_heap corrupted, optionally enriched with other PHP allocator/segfault messages.
  2. Building block B — worker failure: Apache event contains AH00051 or an equivalent worker abort/segfault event.
  3. Building block C — HTTP context: POST activity against the affected host in the preceding ~2 minutes; prefer normalized web-server destination/asset identity over generic QRadar sourceip.
  4. Offense candidate: A + B on the same web server within 2 minutes; increase confidence when C also exists. Add exploit-request bursts, unexpected user agents, and suspicious process execution where telemetry exists.

Implement with CRE event tests, building blocks, and a common asset/log-source reference; validate exact UI tests and field mappings in the installed QRadar version. Do not join solely on the syslog sender IP when multiple containers share a Docker host.

Suggested severity

High when memory corruption and worker abort coincide with suspicious web activity on an exposed PHP service. Critical when paired with confirmed Apache-spawned shell/process activity, file-write proof, or other post-exploitation telemetry.

False positives

Genuine PHP defects, buggy extensions, malformed requests, worker configuration limits, routine restarts, and load tests can produce overlapping signals. AH00161 alone is not an exploit indicator.

05 / Microsoft Sentinel detection engineering

Sentinel was not connected to this lab during the exercise. The following queries are portable detection starting points, not validated outputs from the test. They assume Apache/PHP messages are ingested into a syslog table and may need adaptation for AMA transformations, DCR mappings, custom tables, and host naming.

KQL — PHP allocator and Apache crash signals

Syslog
| where TimeGenerated > ago(24h)
| where SyslogMessage has_any (
    "zend_mm_heap corrupted", "AH00051", "AH00161")
| extend Signal = case(
    SyslogMessage contains "zend_mm_heap corrupted", "PHP heap corruption",
    SyslogMessage contains "AH00051", "Apache worker abort",
    SyslogMessage contains "AH00161", "Apache worker limit",
    "Other")
| project TimeGenerated, Computer, HostName, ProcessName,
          Signal, SyslogMessage
| order by TimeGenerated desc

KQL — time-bucket correlation with POST activity

let Lookback = 24h;
let Window = 2m;
let WebLogs = Syslog
| where TimeGenerated > ago(Lookback)
| extend Server = tostring(Computer)
| project TimeGenerated, Server, SyslogMessage;
let Crashes = WebLogs
| where SyslogMessage contains "zend_mm_heap corrupted"
    or SyslogMessage contains "AH00051"
| project CrashTime = TimeGenerated, Server,
          CrashMessage = SyslogMessage;
let Posts = WebLogs
| where SyslogMessage contains '"POST '
| project PostTime = TimeGenerated, Server,
          PostMessage = SyslogMessage;
Crashes
| join kind=inner Posts on Server
| where PostTime between ((CrashTime - Window) .. (CrashTime + Window))
| project Server, CrashTime, PostTime, CrashMessage, PostMessage
| order by CrashTime desc

This is a hunting query, not a production analytic. It can produce many-to-many matches on busy hosts. In this lab, the RFC 5424 hostname was the Docker host; production environments should extract container/service identity (for example the syslog tag) and correlate on that instead of Computer alone. Confirm your ingestion maps the Apache text to SyslogMessage.

KQL — stronger process evidence when Defender telemetry exists

DeviceProcessEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName in~ ("apache2", "httpd", "php-fpm")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "python", "perl")
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessFileName,
          InitiatingProcessCommandLine, AccountName
| order by Timestamp desc

Requires Defender for Endpoint device telemetry and suitable Linux/container visibility; this was not present or demonstrated in the PoC. A command may execute through another process chain, so this is one coverage layer, not a complete RCE detector.

Suggested Sentinel analytics

06 / SOC triage and response playbook

  1. Validate: identify the affected PHP service, version, exposure, recent changes, and whether the alert is from a real server or a known authorized test.
  2. Preserve: export raw access/error logs, SIEM events, process and network telemetry, relevant container metadata, image digests, and volatile evidence where feasible.
  3. Correlate: reconstruct client IPs, request paths, timestamps, WAF actions, worker crashes, child processes, and outbound connections. Note that HTTP 200 does not rule out compromise.
  4. Contain: if compromise is suspected, restrict external access and egress, isolate the affected workload under incident-response procedures, and preserve evidence before destructive changes.
  5. Remediate: consult authoritative vendor advisories for affected versions and fixed releases; rebuild from trusted images, rotate exposed secrets where warranted, and hunt for persistence or lateral movement.
  6. Improve: test the detections with authorized replay or controlled lab activity; record false positives, log-source blind spots, and time-to-detect.
Scope of evidence: This lab demonstrated code execution, not exfiltration or broader compromise. The absence of those behaviors in the exercise should not be interpreted as a limitation of an attacker with code execution in a real application.

07 / Lessons learned and next steps

What worked

  • Dedicated network and firewall containment verified before execution.
  • Simple application avoided conflating PHP-core behavior with CMS flaws.
  • One controlled attempt produced independently verified command output.
  • QRadar captured access logs, PHP allocator corruption, and Apache worker abort.

What to improve

  • Add host/container process execution and file-write telemetry.
  • Normalize Apache client IP and container identity at ingestion.
  • Test correlations against benign crashes and legitimate workload spikes.
  • Document immutable evidence exports, image digest, and cleanup completion.

Operational closure

The target container was stopped after validation. A separate teardown checklist should confirm removal of the lab-specific DOCKER-USER jump/chain, host INPUT rule, isolated Docker network, published port, and any no-longer-needed containers/volumes—without disturbing other Docker services. Retain evidence before deleting the proof volume.

References and provenance

Prepared as a reproducible defensive research case study. Example detection queries require environment-specific validation. IP addresses shown are private lab addresses; memory addresses are from one observed exploit run and are not reusable indicators of compromise.