Were you hit by PHP2Shell? Which logs to check, and how
How to detect PHP2Shell (CVE-2026-109618, CVE-2026-109619) exploitation: which logs to check, what to look for, example commands and incident response steps.

The PHP2Shell patches came out on 21 October, but patching did not close the window of exposure. According to Datadog, AWS saw exploitation attempts on 17 October, four days before disclosure, and active exploitation and broad scanning have been reported since 21 October. This post explains how to tell whether your server was hit in the days before you patched: which logs to check, what to look for, and what to do if you find something.
We covered what the flaws are and how to patch them in our earlier post. If you have not updated yet, start there.
Set realistic expectations first
There is still no official public list of indicators of compromise (IP addresses, file hashes). Detection therefore relies on behaviour and indirect traces:
- The exploit fires while PHP parses the request. Your application code may never run, so do not expect traces in application logs.
- Standard access logs do not record POST bodies. Only a WAF that logs request bodies (for example the ModSecurity audit log) shows the form fields used in the attack.
- A successful exploit does not always crash a worker, and a request that returned HTTP 200 is not necessarily harmless.
The strongest evidence is a web server or PHP worker starting a process it should not. Logs help you find when, and from where, that happened.
Start with the window from 17 October to the moment you applied the patch.
1. PHP and web server error logs
This is where the side effects of the attack show up most. Places to check:
- Apache:
/var/log/apache2/error.logor/var/log/httpd/error_log - Nginx:
/var/log/nginx/error.log - PHP-FPM:
/var/log/php*-fpm.logand each pool’serror_log - On CloudPanel and similar panels, site logs usually live under
/home/<user>/logs/.
Lines to look for:
# Memory exhaustion, too many form fields, oversized POSTs
grep -E "Allowed memory size of [0-9]+ bytes exhausted|Input variables exceeded|POST Content-Length of" \
/var/log/apache2/error.log* /var/log/php*-fpm.log*
# Worker crashes (Apache and PHP-FPM)
grep -E "exit signal Segmentation fault|exited on signal (11|6)" \
/var/log/apache2/error.log* /var/log/php*-fpm.log*
# Memory corruption messages
grep -E "zend_mm_heap corrupted|corrupted double-linked list|free\(\): invalid" \
/var/log/apache2/error.log* /var/log/php*-fpm.log*
In Datadog’s examples, the memory exhaustion lines happen while the request starts up and show Unknown on line 0 as the location. Read these lines together with their timestamps, not one by one: memory errors, max_input_vars warnings and crashes in quick succession mean much more than any of them alone.
2. System and kernel logs
Crashing PHP or Apache processes also end up in the kernel log:
grep -E "segfault at .* in (libphp|php|apache2|httpd)" /var/log/syslog* /var/log/kern.log* /var/log/messages*
journalctl -k --since "2026-10-17" | grep -i segfault
journalctl -u 'php*-fpm*' --since "2026-10-17" | grep -iE "signal|exited"
3. Access logs: bursts of POST requests
Access logs do not show the body, but they do show the shape of an attack. Exploit attempts usually send many POST requests to the same URL in a short time, and some of them end in a 500 or a dropped connection.
# IPs and URLs sending the most POSTs
awk '$6=="\"POST" {print $1, $7}' /var/log/apache2/access.log* | sort | uniq -c | sort -rn | head -20
# One IP's POSTs, with time and status code
grep '"POST ' /var/log/apache2/access.log | grep '^203.0.113.50 ' | awk '{print $4, $7, $9}'
When you find a suspicious IP, compare it with the error log lines from the same minutes. Correlating by time is the most valuable step in this whole investigation. Some scanning tools arrive with their own User-Agent; note any unusual ones that are not services you know. They can be changed, so they are not proof on their own.
4. WAF and ModSecurity audit logs
If you have a WAF that records request bodies, the clearest traces are here. Signals published by Datadog:
- Form fields named
PHPSESSIDandPHP_SESSION_UPLOAD_PROGRESSwhere your application does not normally use them - Many multipart parts, very long field names, deeply nested bracketed names
- Upload filenames containing
<?phpor serialised-object fragments (|O:) Set-Cookie: PHPSESSID=responses with unusually long or non-standard values, which can point to a memory leak- Uploads that declare a large
Content-Lengthand never finish
grep -E 'name="(PHPSESSID|PHP_SESSION_UPLOAD_PROGRESS)"' /var/log/modsec_audit.log*
grep -E 'Set-Cookie: PHPSESSID=[^;]{60,}' /var/log/modsec_audit.log*
These fields also appear in legitimate upload progress bars. If your application uses that feature, separate the results carefully.
5. Processes and network connections
If an attacker managed to run code, the process tree shows it best: sh, bash, curl, wget, nc, python or perl started under apache2, php-fpm or nginx.
# Processes of the web user, with their parents
ps -eo pid,ppid,user,lstart,args --forest | grep -E -A3 "apache2|php-fpm|nginx" | grep -vE "^\s*$"
# Open outbound connections of web processes
ss -tpn | grep -E "php|apache2|nginx|httpd"
# Processes running from temporary directories
ls -l /proc/*/exe 2>/dev/null | grep -E "/tmp/|/dev/shm/|/var/tmp/"
This only shows the present moment. For history, check your EDR, auditd, or firewall and proxy egress logs.
6. File system and persistence
To keep access, attackers usually leave webshells and scheduled tasks.
# PHP files changed since 17 October, with owner
find /var/www /home/*/htdocs -name "*.php" -newermt "2026-10-17" -printf '%TY-%Tm-%Td %TH:%TM %u %p\n' 2>/dev/null
# Patterns common in webshells
grep -rlE '(eval|assert|system|passthru|shell_exec)\s*\(\s*(base64_decode|\$_(GET|POST|REQUEST|COOKIE))' /var/www /home/*/htdocs 2>/dev/null
# Files the web user dropped into temporary directories
find /tmp /var/tmp /dev/shm -user www-data -newermt "2026-10-17" -ls 2>/dev/null
# Scheduled tasks, new users and SSH keys
crontab -u www-data -l; ls -la /etc/cron.* /var/spool/cron/ 2>/dev/null
grep -vE "nologin|false" /etc/passwd; find / -name authorized_keys -newermt "2026-10-17" 2>/dev/null
The modification time (mtime) can be faked; for suspicious files also check the change time (ctime) with stat. Compare changes to application files with your deployment records.
Limitations
- “No findings” does not mean “not affected”. Without body logging, the exploit only leaves indirect traces.
- Log rotation may have removed older records; search the
.gzarchives too (zgrep). - A successful attacker may have cleaned local logs. Cross-check with remote syslog, your SIEM, or the logs of a proxy layer such as Cloudflare.
- Convert timestamps to the same time zone before comparing them.
Also: a version inventory
Alongside detection, you need to know which servers still run an affected version. Our php2shell_scan.py needs no internet connection: it finds every PHP installation on a server, checks its version and recommends an update. It runs on Python 3.6 or later with no extra packages, so it also works on closed networks such as banks.
- Download: php2shell_scan.py · SHA-256 checksum
curl -O https://twinlikee.com/downloads/php2shell_scan.py
curl -O https://twinlikee.com/downloads/php2shell_scan.py.sha256
sha256sum -c php2shell_scan.py.sha256 # check the file is intact
sudo python3 php2shell_scan.py # summary on screen
sudo python3 php2shell_scan.py --json report.json
The tool only reads; it changes no files. For a closed network, download it on a connected machine, check the checksum, then move it across.
If you find something: incident response steps
- Isolate: take the server off the network, or allow management access only. Do not power it off; memory may be evidence.
- Preserve evidence: copy the logs, suspicious files and, if possible, memory, and move them off the server.
- Patch: update PHP to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 and restart every worker.
- Remove persistence: delete webshells, cron jobs, new users and SSH keys. If in doubt, rebuild from a clean backup.
- Rotate secrets: database passwords, API keys in
.env, payment and email service keys. - Notify: if personal data was affected, you may have to notify the regulator; under GDPR and KVKK that is within 72 hours.
Conclusion
With PHP2Shell, patching is only half the job. The other half is finding out whether someone got in before you patched. Match the memory and crash traces in your error logs with POST bursts in your access logs by time, then confirm with process and file system checks. If you do not have a WAF that records request bodies, this incident is a good reason to set one up.
Our Cybersecurity team can review your logs with you, confirm a suspicious finding or run the incident response. If you want your servers kept up to date and monitored, see Server & IT Management or get in touch.
Sources: Datadog Security Labs: PHP2Shell, Tanium: Responding to PHP2Shell, Huntress: PHP2Shell. Information is current as of 25 October 2026.
Bu yazı Türkçe olarak da mevcut — Türkçe oku →
Frequently Asked Questions
Not necessarily. Standard access logs do not record POST bodies, and a successful exploit does not always leave a crash. Logs may also have been rotated or wiped. Patch PHP first, then confirm with process, file system and outbound network records.
Not on its own. Memory exhaustion is common in legitimate workloads. It becomes suspicious when it appears together with a burst of POST requests from the same IP, max_input_vars warnings and worker crashes within the same few minutes.
Isolate the server from the network, copy the logs and, if you can, the memory, then update PHP and restart the workers. Rotate passwords and API keys. If personal data was affected, you may have a legal duty to notify the regulator; under GDPR and Türkiye's KVKK that is within 72 hours.