SOC Engineering Portfolio / Detection Validation

PHP2Shell: Windows IIS, FastCGI & QRadar Detection Lab

Building a Windows PHP target, engineering end-to-end process telemetry, testing exploit prerequisites, and determining what a real-world SOC can—and cannot—conclude from the results.

Windows Server 2022 CoreIIS 10PHP 8.4.26SysmonWinCollect 10IBM QRadarKali LinuxCVE-2026-109618 / 109619 PoC
ValidatedPHP → command interpreter process lineage in QRadar telemetry
ConfirmedPOST size restriction and PHP memory-exhaustion behaviour
Not demonstratedPHP2Shell remote code execution on Windows IIS
01 / Mission

Why we built this lab

The primary goal was to validate an existing IBM QRadar correlation rule intended to identify web-server processes spawning command interpreters or common system utilities—a useful signal for webshells and web application remote code execution.

PHP2Shell provided a realistic scenario. A Linux Apache/mod_php proof of concept had already succeeded in a separate lab, prompting a question: could the same behaviour be reproduced against Windows IIS with PHP FastCGI, and would the SOC rule fire?

Scope of the evidence: The recorded tests establish telemetry visibility and benign process-execution detection prerequisites. They do not establish a Windows PHP2Shell compromise, nor do they prove that a QRadar rule offense was generated.
02 / Build

Lab environment and architecture

Kali Linux192.168.1.101 · HTTP test client
→
Windows Server 2022 Core192.168.1.141 · IIS 10 / PHP 8.4.26
↓ Sysmon / Windows events / IIS W3C
IBM WinCollect 10.1.15.2Installed on Windows target
↓ Event forwarding
IBM QRadar192.168.1.188 · correlation and analysis

The lab VMs were hosted in a Proxmox environment. A separate Linux Apache/mod_php target was used for the previously successful Linux PoC; it was not the Windows IIS host shown above.

Windows web-server configuration

ComponentObserved configuration
Operating systemWindows Server 2022 Core
Web serverMicrosoft IIS 10; application pool DefaultAppPool
PHP runtimePHP 8.4.26, C:\PHP\php-cgi.exe, SAPI cgi-fcgi
Configuration fileC:\PHP\php.ini
PHP error logC:\PHP\logs\php-error.log
IIS access logC:\inetpub\logs\LogFiles\W3SVC1\u_ex261024.log
PHP web rootC:\inetpub\wwwroot
session.upload_progress.enabled = 1
session.upload_progress.cleanup = 1
session.save_handler = files
memory_limit = 128M
post_max_size = 8M
upload_max_filesize = 2M
SAPI = cgi-fcgi

The server time zone was configured as GMT Standard Time (Ireland). IIS W3C and PHP error timestamps were recorded in UTC, while local Windows event displays could show UTC+1 on the test date. This distinction matters during cross-source investigations.

03 / Visibility

From IIS and Sysmon to QRadar

WinCollect 10.1.15.2 forwarded Windows Application, System, Security, Sysmon, PowerShell, and IIS W3C data to QRadar. The IIS collector required an explicit local source configuration, with recursive monitoring of the IIS log directory.

<LocalSources Name="IIS" Type="MSIIS" xmlns="event_collection/WinCollect">
  <Source Name="IIS-W3C">
    <Parameter name="Protocol.W3C" value="true" />
    <Parameter name="MonitorSubdirectories" value="true" />
    <Parameter name="RootDirectory" value="C:\inetpub\logs\LogFiles" />
    <Parameter name="FileStaleDuration" value="0" />
  </Source>
  <Target Destination="QRadar" />
</LocalSources>

The Sysmon collector was also corrected to use a valid XPath filter. The agent configuration was backed up before changes, including copies named AgentConfig-before-IIS-fix.xml and AgentConfig-pre-deploy.xml.

IIS W3C access logs

Verified HTTP request method, URI, client IP, status, and timestamp. A distinctive request marker (SOC-IIS-TEST-20261024-105956) was located in QRadar.

Sysmon + Security events

Sysmon Event ID 1 recorded process creation and parent/child relationships; Event ID 5 recorded process termination. Windows Security 4688 provided an additional process-creation source.

Visibility limitation: Standard IIS W3C logging did not expose the multipart POST body. A successful upload-progress request and an exploit-shaped multipart request may therefore look similar at the HTTP log level.
04 / Analytics

The team's QRadar rule

The existing rule correlates the immediate parent executable with the executable being created. The custom properties must be populated from the relevant process-creation event.

Parent Process Name (custom)

php-cgi.exe, php.exe, httpd.exe, nginx.exe

AND Process Name (custom)

cmd.exe, powershell.exe, pwsh.exe, bash.exe, sh.exe,
python.exe, python3.exe, perl.exe, ruby.exe, node.exe,
curl.exe, wget.exe, nc.exe, ncat.exe, socat.exe,
certutil.exe, bitsadmin.exe, whoami.exe, hostname.exe,
ipconfig.exe

For Windows IIS, a php-cgi.exe → cmd.exe event satisfies the stated process-name conditions. This is a behavioural rule, not a signature uniquely identifying PHP2Shell.

Validation levels: (1) Sysmon generated a qualifying event; (2) QRadar ingested and parsed it; (3) the correlation rule matched and produced its configured response. The recorded session confirms (1) and general ingestion in (2), but the rule match/offense itself was not confirmed.
05 / Evidence

Experiments and observed outputs

Test 1 — Prove IIS → PHP → Windows command execution

A deliberately benign process-test.php called PHP's exec() to run a fixed marker command. The endpoint returned HTTP 200, and Sysmon exposed the resulting process lineage.

exec('cmd.exe /c echo PHP2SHELL-PROCESS-TEST-001', ...);

Observed process tree:
w3wp.exe        PID 1340
  └─ php-cgi.exe  PID 1316
       └─ cmd.exe PID 3568
            └─ cmd.exe PID 1692

Sysmon recorded command-line and parent-process information. Execution was associated with IIS APPPOOL\DefaultAppPool. A high-integrity label was observed, but this alone is not evidence of administrator-level compromise.

Rule conditions satisfied by event values

Test 2 — Confirm PHP upload-progress functionality

An upload-test.php endpoint initialized a PHP session and reported the number of uploaded files. Kali submitted a multipart request with an upload-progress field and a small file.

curl -b "PHPSESSID=soclabtest001" \
  -F "PHP_SESSION_UPLOAD_PROGRESS=SOC-UPLOAD-TEST-001" \
  -F "file=@/etc/hostname" \
  http://192.168.1.141/upload-test.php

Observed: session ID matched; Files received: 1
QRadar IIS W3C: POST /upload-test.php; client 192.168.1.101; HTTP 200
Upload processing confirmed

Test 3 — Request-level PHP memory exhaustion

A memory-test.php endpoint set memory_limit to 16 MB for the request, then repeatedly allocated memory. The client received HTTP 200 with an empty response, while PHP logged a fatal error.

[24-Oct-2026 11:11:16 UTC] PHP Fatal error:
Allowed memory size of 16777216 bytes exhausted
(tried to allocate 1052672 bytes)
in C:\inetpub\wwwroot\memory-test.php on line 11

No corresponding PHP worker PID change or Windows Application crash was observed. A PHP memory-limit failure is therefore not equivalent to a FastCGI worker crash.

Request-level OOM characterised

Test 4 — Establish normal worker recycle behaviour

The IIS application pool was intentionally recycled using appcmd. Sysmon Event ID 5 recorded termination of the earlier workers, and subsequent requests started fresh processes.

appcmd.exe recycle apppool /apppool.name:"DefaultAppPool"

Before recycle:
w3wp.exe       PID 1340
php-cgi.exe   PID 2156

After recycle and next PHP request:
w3wp.exe       PID 1648  Parent PID 2660  12:15:30 local
php-cgi.exe   PID 4060  Parent PID 1648  12:16:24 local

The recycle produced an identifiable normal-termination baseline, which can be compared with abnormal worker failure in later investigations.

Lifecycle baseline established

Test 5 — Test oversized multipart POST handling

A 9 MiB test file was posted to upload-test.php, exceeding the configured post_max_size = 8M. IIS returned HTTP 200, but PHP did not populate the uploaded-files collection.

HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: Microsoft-IIS/10.0
X-Powered-By: PHP/8.4.26

Session ID: soclabsize001
Upload progress enabled: 1
POST received: POST
Files received: 0

[24-Oct-2026 11:21:41 UTC] PHP Warning:
PHP Request Startup: POST Content-Length of 9437545 bytes
exceeds the limit of 8388608 bytes in Unknown on line 0

This demonstrates that HTTP 200 does not imply successful PHP multipart parsing. It also identifies a concrete reason why the PoC's approximately 15 MB first request would not be processed as intended under the unchanged configuration.

POST limit confirmed
06 / Research

What the PHP2Shell PoC assumed

The examined remote_cve.py described a three-part exploit chain following worker rotation: disclose a heap/chunk base, disclose a PHP library address, then corrupt PHP session-related structures to redirect a destructor callback to command execution. Its stated validated target was Apache prefork + mod_php on Debian 12, PHP 8.4.26.

The rotate() implementation first generated 1,001 form fields of 3,000 bytes each, included a null-byte PHPSESSID form field and an upload-progress parameter, attached a small file, and appended a 12 MiB field. It then sent a smaller follow-up multipart request.

# Relevant Stage 0 structure (abridged; illustrative)
parts = [{"name": f"f{i:04d}"[:5], "data": b"V" * 3000}
         for i in range(1001)]
parts += [
  {"name": "PHPSESSID", "data": b"\x00"},
  {"name": "PHP_SESSION_UPLOAD_PROGRESS", "data": b"pwn"},
  self.file_part(),
  self.big()  # 12 MiB payload field
]
self.t.post(multipart(parts, b"S" * 17), b"S" * 17, ...)
Important nuance: The fact that this request exceeds the Windows target's 8 MB POST limit means it is blocked from normal POST parsing in the tested configuration. That does not establish that raising the limit would make the Linux exploit functional on Windows.
07 / Platform differences

Why Linux success did not translate to Windows

Exploit assumptionLinux Apache/mod_php PoCWindows IIS/FastCGI lab
PHP execution modelPHP loaded into Apache worker via mod_phpSeparate php-cgi.exe worker launched by IIS
Worker lifecyclePoC assumes Apache prefork replacement behaviourIIS FastCGI lifecycle; recycle and PHP request-level OOM tested, but exploit-style worker abort not demonstrated
Binary and symbolsLinux ELF / libphp.so symbol offsetsWindows PHP binaries; offsets and symbol assumptions are not portable
Memory layoutPoC relies on Linux-target-specific heap and address disclosuresNo Windows-specific disclosure or corruption primitives validated
Multipart request limitsPoC designed around its validated target configuration8 MB post_max_size rejects the oversized Stage 0 POST data
Observed exploitationPreviously successful in the separate Linux labNo successful Windows exploit demonstrated

Even if the HTTP inputs are platform-neutral, a memory-corruption exploit can depend heavily on runtime internals, binary layout, calling conventions, allocator behaviour, and worker lifecycle. A successful Linux PoC cannot be treated as evidence of equivalent Windows exploitability.

08 / Conclusion

What we concluded—and what we did not

Demonstrated

  • Functional Windows IIS/PHP FastCGI target.
  • IIS W3C and Sysmon telemetry forwarded into QRadar.
  • Benign PHP-to-cmd.exe execution with qualifying parent/child values.
  • Session upload handling with a small multipart request.
  • Distinct PHP OOM, IIS recycle, and POST-size-limit behaviours.

Not demonstrated

  • PHP2Shell remote code execution against Windows.
  • Successful Windows memory disclosure or corruption.
  • Portability of Linux Apache exploit offsets or worker assumptions.
  • Confirmed QRadar correlation-rule firing or offense creation.
  • Comprehensive detection coverage for every possible exploit execution path.

We chose to stop the Windows exploit attempt because the existing PoC was specifically engineered for a Linux Apache/mod_php environment. Porting it would require new Windows-specific exploit research, well beyond the SOC detection-validation objective. Continuing to tweak POST limits alone would not resolve those architectural differences.

Final assessment: The Windows lab was successful as a telemetry and behavioural detection testbed. The specific PHP2Shell Windows exploitation hypothesis remains unproven. For exploit-faithful rule validation, the known-working Linux Apache lab is the better next target.
09 / Detection engineering

Lessons for SOC analysts

  1. Separate detection logic from rule execution. A process tree that satisfies the written conditions is not the same as a confirmed QRadar rule match. Check custom property extraction, rule response and offense/event correlation.
  2. Use multiple telemetry layers. IIS access logs establish the HTTP context; Sysmon Event ID 1 shows execution; Event ID 5 provides process termination context; PHP errors explain request-level failures.
  3. Do not interpret HTTP 200 as exploit success. An oversized POST returned 200 while PHP discarded its contents and logged a warning.
  4. Know the immediate-parent limitation. The rule is strongest for direct php-cgi.exe → cmd.exe or httpd.exe → sh patterns. Indirect execution chains or in-process activity may evade it.
  5. Account for legitimate application behaviour. Some PHP applications intentionally invoke utilities. Production rules need baselines, asset context, exclusions where justified, and careful tuning.
  6. Correlate time zones. IIS/PHP UTC timestamps and Windows local display times can differ by an hour during daylight-saving periods.
  7. Validate on the platform where the exploit actually works. Use the Linux Apache PoC to confirm exploit-faithful detection, then treat Windows tests as distinct behavioural coverage.
10 / Wrap-up

Evidence preservation and close-out

Preserve representative IIS W3C entries, Sysmon Event IDs 1 and 5, Security 4688 where available, PHP error excerpts, the WinCollect configuration backup, and QRadar searches or screenshots demonstrating event parsing. Record whether the QRadar rule actually matched once verified.

Remove temporary web-accessible scripts such as process-test.php, upload-test.php, memory-test.php, and config-test.php when testing is complete; they should not remain accessible on a persistent host. Retain a lab snapshot if further controlled research is planned.

Evidence basis: the recorded lab commands, process output, configuration values, PHP error messages, and QRadar observations supplied during this testing session. Explanations of portability are engineering analysis; they are not a claim that Windows is unaffected by the underlying vulnerability. Dates shown are from the 24 October 2026 lab session.