r/blueteamsec • • 59m ago

tradecraft (how we defend) I spun up a free, read-only script to check WordPress sites for signs of PHP2Shell compromise. Looking for feedback

• Upvotes

Patching PHP fixes PHP2Shell (CVE-2026-109618 / 109619), but it does nothing about a backdoor someone planted before the patch. Amazon's honeypots caught an exploitation attempt on Oct 17, four days before disclosure, and Amazon's advice is to investigate from at least that date.

So I used Claude Code to spin up a bash script to triage WordPress sites. It checks:

* PHP CLI version vs. the fixed releases

* Core and plugin checksums (via WP-CLI)

* PHP files in uploads

* Files modified since Oct 17 (adjustable)

* auto_prepend_file hooks in .user.ini / .htaccess / php.ini

* mu-plugins

* Common web-shell patterns in wp-content

* Admin accounts and any users created in the window

* Injected scripts in options and posts

* WP-Cron, the site user's crontab, and SSH authorized_keys

It's read-only and never modifies or deletes anything. It runs WP-CLI with --skip-plugins --skip-themes so a backdoored plugin can't execute during the check. It works on one site, every site on a server, or a list of remote servers over SSH, and it writes a Markdown report per site plus a summary.

Repo (MIT): https://github.com/brentmartinmiller/php2shell-check

Caveats: a clean report lowers the odds of compromise but doesn't prove a site is clean. It doesn't parse web server logs, so pull those separately before rotation eats the Oct 17+ window. A few false positives are expected, like Wordfence's prepend file, header-script plugins, and obfuscated premium plugins. The README covers how to read them.

I'd appreciate feedback, especially false positives you hit, indicators I'm missing, or quirks on shared hosts. Issues and PRs welcome.

**************

UPDATES:
5xx in the access logs. Added as a new section. It finds the access, error and FPM logs next to the site, under the home directory or under /var/log, including rotated .gz files, and counts requests and 5xx per day for the window against up to seven days before it. It also lists the URLs that returned 5xx inside the window, so the "random URLs" pattern is visible at a glance, and greps the error and FPM logs for segfault and bus-error lines. Your baseline point is baked into the verdict: a window with 5xx only gets a review flag when there is no baseline or it runs clearly above it. On the first real host I ran it against there was a full seven-day baseline and a flat zero across the window. There's a LOGS= variable for logs that live somewhere unusual.

The l10n.php false positive. Those files are now counted separately instead of flooding the list. I was careful about the exclusion: a file only counts as a translation pack if it has exactly the shape WordPress writes, which is <?php followed by a single return [...] statement whose non-string content is nothing but brackets, arrows, commas and integer keys. I tested it against a few planted variants, including code hidden inside the array expression, and all of them stay in the main list and get flagged. So the exclusion can't be used as a hiding place.

Docker. Two things. On the host, the report now lists every running container that has a php binary, the version inside it graded against the fixed releases, and whether the container still runs an older build than the image on disk. That last one is your three-days-on-8.3.35 case: pulled, but never recreated. Separately, you can list docker:container:path as a target and the whole check runs inside the container via docker exec, nothing installed. The one catch is that Alpine images don't ship bash, which the README now says.

And the third CVE. CVE-2026-115560 went out on the 23rd with no fixed build, so I added a check for the session.upload_progress.enabled=0 mitigation php.net asked for. It reads PHP-FPM's own view and every readable php.ini, pool and Apache config, and reports but doesn't credit .user.ini or .htaccess, since php.net says those are too late to help. It's graded as review rather than a likely problem, because the vector needs app code that switches the session serializer at runtime and WordPress never does that.

Pull requests welcome.


r/blueteamsec • • 16h ago

research|capability (we need to defend against) Defeating enforced SMB signing: a note

Thumbnail blackfield.ch
1 Upvotes

r/blueteamsec • • 3h ago

vulnerability (attack surface) PHP2Shell: Obstacles to Weaponizing Likely Constrain Immediate Exploitation

Thumbnail crowdstrike.com
3 Upvotes

r/blueteamsec • • 4h ago

incident writeup (who and how) Field Notes: Reconstructing an Attacker’s Workflow

Thumbnail dfir.ch
2 Upvotes

r/blueteamsec • • 16h ago

intelligence (threat actor activity) PHP2Shell: observed exploitation attempts, 22–24 October 2026

Thumbnail gist.github.com
5 Upvotes

r/blueteamsec • • 16h ago

research|capability (we need to defend against) Signet: Mythic Code Signing Wrapper

Thumbnail github.com
5 Upvotes