On July 28, 2026, we investigated a major WordPress exploit that was actively being used in the wild. One of our WordPress properties, let.live, was down, and that changed the question from “are we vulnerable?” to “were we compromised while vulnerable?”
The short version: yes, let.live had been compromised.
We found evidence of a successful exploit, contained the compromised host, rebuilt the site from a pre-exploit backup, restored only vetted newer content, and moved production traffic to a clean replacement server.
This is the write-up of what happened and what we did.
The Exploit
The active exploit chain was being referred to as wp2shell. The vulnerable WordPress versions included WordPress 7.0.0 and 7.0.1, fixed in 7.0.2.
The critical endpoint involved was:
/wp-json/batch/v1
That mattered because let.live had been running WordPress 7.0 before we patched it.
Initial Triage
We first checked our WordPress properties for exposure.
people4liberty.org was already patched as was this site fishdan.com. Both looked healthy:
- WordPress core was patched.
- HTTPS returned
200. /wp-json/batch/v1returned404.
let.live was more concerning:
- The site was down.
- The original Lightsail IP was unreachable.
- WordPress had previously advertised a vulnerable version.
We recovered access by stopping and starting the Lightsail instance, then attaching a static IP so the host would have a stable target while we investigated.
Evidence of Compromise
Once the server was reachable, we reviewed Apache logs, WordPress users, plugins, files, processes, and database state.
The first confirmed exploit hit was:
2026-07-20 22:44:38 UTC
POST /?rest_route=/batch/v1
Less than 20 seconds later, a rogue WordPress administrator had been created:
user / user@wordpress.org
created 2026-07-20 22:44:56 UTC
We also found a malicious plugin:
wp-content/plugins/yoast-seo-plus-b9c0b4de/
The plugin contained command-execution code, including functions such as:
shell_exec
exec
system
passthru
popen
proc_open
It also contained an explicit wp2shell command runner and a secret token.
The logs showed that the attackers read wp-config.php, queried users and plugins, and later launched an xmrig cryptocurrency miner on July 27.
At that point, the host was not merely vulnerable.
It was compromised.
Containment
We treated the server as hostile but useful for forensics.
Immediate containment steps included:
- Patched WordPress core to
7.0.2. - Deactivated the malicious plugin.
- Archived the malicious plugin for evidence.
- Removed the malicious plugin from the webroot.
- Downgraded the rogue administrator account and destroyed its sessions.
- Killed miner-related processes.
- Removed miner and webshell artifacts from
/tmp. - Rotated WordPress salts.
- Rotated the local WordPress database password.
- Verified WordPress core checksums.
The malicious plugin archive was preserved at:
/opt/bitnami/forensics/wp2shell-20260728/
We did not delete the compromised server.
We retained it for forensics.
Finding a Clean Restore Point
The most important recovery question was:
Do we have a backup from before the exploit?
We found two relevant Lightsail snapshots:
np-migration-letlive-20260625let-live-pre-reboot-20260728-225513
The July 28 snapshot was post-compromise and therefore useful only for forensics.
The June 25 snapshot was from before the first confirmed exploit on July 20, so it became our clean baseline.
We also had the actual migration backup files from June 25:
/home/dfish/backups/np-migration-20260625/letlive/
Those included:
np-migration-letlive-rds.sql.gz
np-migration-letlive-wp-content.tar.gz
np-migration-letlive-wp-config.php
That distinction mattered.
The Lightsail snapshot itself was effectively stock WordPress content. The real historical let.live content was in the migration database and wp-content backups.
Preserving New Content
The exploit happened after the June 25 migration, but legitimate posts had been published after that date.
We did not want to lose them.
From the contained host, we exported only vetted post-migration content:
Liberty in PiecesDaylight Saving or Standard? The Sun Doesn't Care!- One referenced image attachment
We explicitly excluded attacker-created trash and auto-draft content.
The clean export was:
/tmp/letlive-wp2shell-recovery/export-20260728-2/letlive-published-posts-after-migration.xml
We checked the export for known bad indicators:
yoast-seo-plus
wp2shell
xmrig
the malicious token
user@wordpress.org
trash records
auto-draft records
The clean WXR passed those checks.
The Rebuild
Our recovery strategy was:
- Build a replacement server from a pre-exploit baseline.
- Patch WordPress before serving public traffic.
- Restore the full pre-exploit database and
wp-content. - Reapply only vetted newer posts and media.
- Rotate credentials.
- Validate content and security checks.
- Move the static IP to the clean host.
We first created:
let-live-clean-20260728
from the June 25 snapshot and patched it to WordPress 7.0.2.
That host validated clean, but once we realized that the full historical content was not present in the snapshot itself, we restored the real migration database and wp-content.
The full content set was too heavy for the nano_3_0 Lightsail bundle. The host wedged under load, so we created a larger replacement:
let-live-full-clean-20260728
That host used small_3_0, restored the full June 25 backup, reapplied the two vetted July posts, and became the final production target.
Validation
After the final restore, we validated that:
- WordPress core was
7.0.2. - WordPress core checksums passed.
/wp-json/batch/v1returned404.- The malicious plugin directory was absent.
- The rogue
user@wordpress.orgaccount was absent. - No
wp2shell, malicious token, orxmrigindicators were found inwp-content. - No miner processes or suspicious cron entries were present.
- HTTPS returned
200. - Historical and July content appeared on production.
We also compared the 20 most recent published blog posts between the retained compromised host and the clean production host.
The comparison matched exactly by:
- Post ID
- Post date
- Slug
- Title
Final restored count:
145 published posts
Cutover
We attached the stable Lightsail static IP to the clean full-content host:
44.211.112.166 -> let-live-full-clean-20260728
GoDaddy DNS for let.live and www.let.live was updated to point at that IP.
Production then validated:
https://www.let.live/ -> HTTP 200
/wp-json/batch/v1 -> 404
The site uses /%postname%/ permalinks, so canonical post URLs look like:
https://www.let.live/stop-kink-shaming-ai-users/
Liberty in Pieces
Daylight Saving or Standard? The Sun Doesn’t Care!
What We Learned
The main lesson is that patching is not recovery.
Once an attacker has administrative access and can upload a plugin with command execution, the host cannot be trusted.
Even if WordPress core verifies clean afterward, the safer path is to rebuild from a pre-compromise backup and migrate only vetted content forward.
Other lessons:
- Keep pre-incident backups clearly separated from forensic snapshots.
- Do not assume a cloud instance snapshot contains the full application payload if migration files exist separately.
- Export new content before rollback, but inspect the export for attacker-created records.
- Preserve compromised systems for forensics instead of immediately deleting them.
- Validate both security indicators and business content before declaring recovery complete.
- Use stable IPs or DNS infrastructure that make emergency cutovers explicit and repeatable.
- Size the replacement host for the real application, not the stock WordPress baseline.
Current State
Production let.live is now running on the clean rebuilt host:
let-live-full-clean-20260728
44.211.112.166
WordPress 7.0.2
The compromised host remains retained for forensic review.
It should not be decommissioned until we explicitly decide that the evidence is no longer needed.
Leave a Reply