My WordPress Blog Was Compromised: A Recovery Story and a Possible Connection to wp2shell
I’ve been running garaged.org for many years. Like many personal projects, it’s a website that has accumulated content, plugins, configuration changes, and plenty of technical history along the way.
Recently, while working on improvements to the server, I discovered something I definitely wasn’t expecting: the server had been compromised, and someone had installed a mechanism to maintain remote root access.
What started as routine maintenance became a small incident-response and recovery exercise.
What I found
The first major warning sign was a suspicious script named /etc/c2beacon.sh, configured in root’s crontab:
@reboot /etc/c2beacon.sh
*/15 * * * * /etc/c2beacon.sh
The script decoded a Base64-encoded SSH public key, copied it into /root/.ssh/authorized_keys, and ensured the SSH service was enabled and running.
In other words, someone had arranged for their SSH access to be restored every 15 minutes and after every reboot.
This wasn’t a normal WordPress problem anymore. The presence of root-level persistence meant I could no longer trust the operating system.
Reconstructing the timeline
I preserved the database and began investigating the accounts and other artifacts left behind.
Two unauthorized WordPress administrator accounts stood out:
| Date | Finding |
|---|---|
| July 17, 2026 | Public disclosure of the wp2shell vulnerability chain |
| July 20, 2026 | Suspicious administrator w2s_fc25ca466087 created |
| July 23, 2026 | Another suspicious administrator, cacheopsafb140, created |
| August 10, 2026 | Root-level SSH persistence script created, according to filesystem metadata |
| November 11, 2026 | I discovered the compromise |
The first account was particularly interesting because its associated email address used the domain wp2shell.local.
The timing is remarkable. The wp2shell vulnerability chain, involving CVE-2026-63030 and CVE-2026-60137, had been publicly disclosed only three days earlier. The chain allowed remote code execution against affected WordPress installations without authentication.
Was wp2shell the original entry point?
Possibly. The timing and account naming strongly suggest a connection. However, I don’t have sufficient historical logs to prove the exact exploitation method, so I’m treating this as a suspected connection rather than a confirmed root cause.
A second problem: hundreds of thousands of fake accounts
While investigating the database, I discovered that the blog had accumulated more than 612,000 WordPress user accounts, almost all of them subscribers.
There were also thousands of spam comments and large amounts of associated metadata.
The cleanup results were substantial:
| Item | Before | After |
|---|---|---|
| WordPress users | 612,318 | 1 |
| User metadata records | 9,879,875 | 51 |
| Comments | 7,778 | 415 |
| Spam comments | 7,363 | 0 |
After cleaning the database, optimizing its tables, and excluding unnecessary legacy data, the staging database was reduced to approximately 81 MB, including temporary recovery tables.
Fortunately, the historical articles, images, categories, and legitimate discussions were preserved.
Rebuilding instead of simply cleaning
Given the level of access indicated by the SSH persistence mechanism, I decided against trying to repair the original server in place.
Instead, I built a fresh environment:
- A clean Linux server with Nginx, PHP-FPM, and MariaDB.
- A fresh WordPress installation and carefully selected plugins.
- A database restored from an isolated, sanitized staging copy.
- New credentials, authentication salts, and administrator sessions.
- Reviewed theme files and restored media.
- Restricted SSH, database access, and WordPress filesystem permissions.
- Cloudflare protection and HTTPS through Let’s Encrypt.
I also removed obsolete plugins and integrations that were no longer useful for the blog.
The restored site has passed functional checks covering its custom theme, article URLs, navigation, pagination, media, comments, and administrator access.
Encrypted off-server backups, recovery testing, and final integrity monitoring are the remaining hardening tasks.
What I learned
The biggest lesson was that a website can appear to work perfectly while its underlying server is no longer trustworthy.
Other takeaways were just as important:
- Patch WordPress and its dependencies promptly, particularly when critical vulnerabilities are being actively exploited.
- Treat unexpected administrator accounts as evidence worth investigating.
- Don’t assume removing malicious files eliminates a compromise.
- Preserve evidence before making destructive changes.
- Test database restoration and cleanup away from production.
- Minimize installed plugins and unnecessary services.
- Protect backups independently of the production server.
- Monitor the operating system, not just WordPress.
I also learned how much easier recovery becomes when important content is separated from the software and infrastructure running it.
Final thoughts
I cannot confirm how the attacker initially entered the server, whether the suspicious WordPress accounts and root-level script were created by the same actor, or whether any information was extracted.
What I can confirm is that unauthorized administrator accounts existed, a root-level SSH persistence mechanism was installed, and the old environment could no longer be trusted.
The blog is now running on a freshly built server, with its historical content restored and the most important access controls verified.
It wasn’t the kind of maintenance I had planned, but it was a useful reminder that security isn’t only about preventing incidents. It’s also about being prepared to investigate, recover, and build something better afterward.