In this Sherlock, players will analyze a compromised server that was targeted using the PHP2Shell exploit. The Sherlock focuses on understanding how the exploit works and exploring all the patterns observed in the web logs.
A single host on the internal network attacked an Apache and PHP web server through index.php. It first flooded the server with stalled multipart uploads until every Apache worker was busy, so the exploit’s later requests all landed on one worker process. It then pushed that worker past the PHP memory limit and read heap addresses back out of an oversized session cookie. Armed with the leak, the chained exploit gave the attacker code execution, and the server opened a reverse shell to the attacker. The shell was used to search the filesystem for database files, and a worker segfault minutes later marked a further exploit attempt.
logs/access.log → read with grep and awk. Combined log format, UTClogs/error.log (about 500 MB) → read with grep. Verbose because mod_dumpio is on, so request and response bodies are loggedCapture.pcap → open with tshark or Wireshark. Full capture of the same sessionTimestamp note: The web logs are UTC. The pcap viewer shows UTC+11, so pcap times are eleven hours ahead of the logs. Answer in log time.
Apache prints its own startup banner in error.log, and the signature includes the PHP module version. Grep for the AH00163 line near the top of the file rather than reading the 500 MB log.
grep -m1 AH00163 "evidence/dOOMs Day"/logs/error.log
access.log is small enough to triage first: filter out ordinary browser GETs and one source, 192.168.72.1, stands out sending POSTs to index.php at the target 192.168.72.50. error.log confirms it with the mod_dumpio request body, and the first such POST there carries the same second as access.log.
grep 192.168.72.1 "evidence/dOOMs Day"/logs/access.log | head
grep -m1 "POST /index.php" "evidence/dOOMs Day"/logs/error.log
Tip:
error.logtimestamps carry milliseconds. Drop them to fit thehh:mm:ssformat.
The mod_dumpio records in error.log show each stalled POST with its Content-Type: multipart/form-data; boundary=... header. Every one of the attacker’s pool-filling requests uses the same boundary and never sends a closing part, which is what keeps the worker waiting.
grep -o "boundary=[A-Za-z0-9]*" "evidence/dOOMs Day"/logs/error.log | sort | uniq -c
From Q3 we know the boundary value. Count the dumpio requests carrying it: each one holds a worker. Apache confirms the pool was full with an AH00161 MaxRequestWorkers warning at 08:43:02, right after the last stalled request.
grep -c "boundary=ZZZZZZZZZZ" "evidence/dOOMs Day"/logs/error.log
grep AH00161 "evidence/dOOMs Day"/logs/error.log
Click to reveal answer 149
Reference knowledge, not log evidence. The exploit behaviour in the logs (worker exhaustion, out-of-memory, heap leak through a cookie) matches the Red Hat security bulletin RHSB-2026-013 (https://access.redhat.com/security/vulnerabilities/RHSB-2026-013), which names the two chained CVEs. The answer is in ascending order.
PHP logs a fatal error when a script exceeds memory_limit. Search error.log for the exhausted-memory message; it states the limit in bytes.
grep -m1 "Allowed memory size" "evidence/dOOMs Day"/logs/error.log
Look at the server’s responses to the attacker in the pcap, after the OOM. One response sets a cookie whose value is a long run of padding bytes followed by raw pointer bytes: those are the leaked heap addresses. Follow that TCP stream (the attacker’s client port is 49339) and read the Set-Cookie header.
tshark -r Capture.pcap -Y "http.set_cookie" -T fields -e frame.number -e tcp.stream -e http.set_cookie
The question says logs, so use the mod_dumpio output side in error.log, not the pcap or access.log. The dumpio_out entry for the leak response (client port 49339) logs the write of the Set-Cookie header with its byte count.
grep dumpio_out "evidence/dOOMs Day"/logs/error.log | grep "49339" | grep -i "set-cookie"
Gotcha: Three other sizes are close and all wrong: the
access.logresponse byte count, the URL-decoded cookie length, and the encoded value length measured from the pcap. Only themod_dumpiobyte count for theSet-Cookiewrite counts.
The first outbound connection from the web server to the attacker is the point where the payload ran. In the pcap the server sends a SYN to 192.168.72.1 on port 4444 at 08:43:02, the same second the worker pool filled. This ties execution to the end of the heap-leak sequence from Q7 and Q8.
tshark -r Capture.pcap -Y "ip.src==192.168.72.50 && tcp.flags.syn==1 && tcp.flags.ack==0 && ip.dst==192.168.72.1" -t ud
Gotcha: The pcap viewer is UTC+11, so the raw frame time reads 19:43:02. Convert to log time (UTC).
The SYN from Q9 shows the destination port. Following that stream (stream 810) shows an interactive shell talking to the attacker on 4444.
tshark -r Capture.pcap -q -z follow,tcp,ascii,810
Worker crashes are logged by Apache as AH00052 child segfaults. There are two: the first at 08:46:16, the second at 08:47:42. The later one is the answer.
grep AH00052 "evidence/dOOMs Day"/logs/error.log
Follow the reverse shell stream on port 4444 from Q10. The attacker types a find across the whole filesystem for common SQLite and database extensions, discarding errors.
tshark -r Capture.pcap -q -z follow,tcp,ascii,810
| Phase | Action |
|---|---|
| Initial Access | Attacker at 192.168.72.1 starts POSTing to index.php on the target |
| Impact | 149 stalled multipart uploads fill the Apache worker pool |
| Execution | A memory-exhausting request pushes a worker past the 128 MB PHP limit |
| Execution | Heap pointers leak back to the attacker in an oversized PHPSESSID cookie |
| Execution | The chained CVEs give code execution; the server calls back to the attacker |
| Command and Control | Reverse shell on port 4444 |
| Impact | A worker segfaults again as the exploit continues |
| Discovery | find across the filesystem for database files |
| Type | Value |
|---|---|
| Attacker IP | 192.168.72[.]1 |
| Target | 192.168.72[.]50 (Apache 2.4.69, PHP 8.4.26) |
| Multipart boundary | ZZZZZZZZZZ |
| Reverse shell port | 4444 |
| CVEs | CVE-2026-109618, CVE-2026-109619 |
| Technique | ID | Description |
|---|---|---|
| Exploit Public-Facing Application | T1190 | Chained PHP CVEs against index.php |
| Endpoint Denial of Service: OS Exhaustion Flood | T1499.001 | Stalled uploads fill the Apache worker pool |
| Command and Scripting Interpreter: Unix Shell | T1059.004 | Shell commands run through the reverse shell |
| Non-Application Layer Protocol | T1095 | Raw TCP shell on port 4444 |
| File and Directory Discovery | T1083 | find for database files |
Alert on worker-pool exhaustion. An AH00161 MaxRequestWorkers warning with no matching traffic spike means requests are being held open. Alert on it and on many incomplete multipart POSTs from one source, which is the setup step of this exploit.
Treat PHP out-of-memory fatals as security events. Allowed memory size exhausted followed by an oversized Set-Cookie is not normal application noise. Forward both to the SIEM and correlate them with the source IP.
Repeated child segfaults are exploitation, not instability. Two AH00052 crashes minutes apart on a web worker deserve an incident, not a restart. Patch the two chained CVEs from the vendor bulletin.
Block web servers from initiating outbound connections. The server opened a TCP session to an arbitrary host on 4444. Egress filtering on the web tier would have cut the shell off even after code execution.
Be careful with mod_dumpio in production. It made this investigation possible, but it also writes request and response bodies, cookies included, to a 500 MB log. Use it for incident capture, and protect the log.
The attacker turned a PHP memory-handling flaw pair into a working shell by controlling which worker handled the exploit, then used it to look for databases. The server’s own logs recorded every stage; the signal was in the worker-pool and memory warnings that preceded the shell.