Your site was on the target list. The disclosure says 25,000 WordPress installs are confirmed breached. You check your dashboard, your login page loads, your posts render. So you breathe out and move on. That is exactly the reaction the attackers bet on.
Modern mass-exploitation crews do not pop a defacement page the moment they land. They plant a quiet webshell, register a sleeper admin, and wait weeks before activating the real payload. By the time you see something wrong, the beacon has already talked to a command-and-control server for 47 days. This article is the checklist I wish every site owner runs the moment their hosting provider, a CVE feed, or a Twitter thread puts their URL anywhere near a “potentially affected” list.
Why “Looks Fine” Is the Most Dangerous Verdict
The dirty secret of mass WordPress exploitation campaigns is that the visible 25K number is the floor, not the ceiling. Researchers only confirm a site as compromised when they find an outbound callback, a blacklisted redirect, or a known payload hash. Anything that hides well stays uncounted. The 1.375M sites in the targeting window are roughly evenly split between three buckets:
- Hardened sites that were probed but rejected.
- Patched sites that were vulnerable for a window and survived.
- Owned sites with a payload so quiet that the owner has not noticed yet.
Bucket three is the silent majority. Your job today is to confirm you are in bucket one, not bucket three.

The 30-Minute WordPress Compromise Self-Triage
Run this in order. Skip a step only if the previous one stops the chain, never because you “feel” the site is clean. The order matters because each layer catches what the layer above missed.
Step 1: Diff Your Core, Themes, and Plugins Against a Known-Good Source
Download a fresh copy of WordPress from wordpress.org matching your exact version. Then run a recursive diff against wp-admin, wp-includes, and every active theme. Anything that does not match a stock file is suspect. Attackers love to drop a single line into wp-includes/version.php or wp-config.php because those files get overwritten by updates only sometimes.
For plugins and themes, the gold standard is the WordPress Plugin Check tool paired with a manual find for any PHP file inside /wp-content/uploads/. The uploads directory should never contain executable PHP. If it does, you have found your first IOC.
Step 2: Hunt for Webshells and Dropper Patterns
Run these four greps from the document root. Each line targets a pattern you will not see in legitimate WordPress code:
grep -rEi "eval\s*\(\s*(base64_decode|gzinflate|str_rot13|gzuncompress)" wp-content/
grep -rEi "assert\s*\(\s*['\"]" wp-content/
grep -rEi "system\s*\(\s*\$_" wp-content/
find wp-content/uploads -type f -name "*.php"
If any of those return hits, copy the file, hash it with sha256sum, save the path, and move on without deleting yet. Order of operations during triage is capture, classify, then clean. Deleting first destroys evidence you will need for the next step.
Step 3: Check the Database for Rogue Admins and Injected Options
Two queries tell you most of what you need:
SELECT * FROM wp_users WHERE user_login NOT IN ('your_known_admins');
SELECT option_name, option_value FROM wp_options WHERE option_name IN ('siteurl','home','active_plugins','template','stylesheet','rewrite_rules') ORDER BY option_name;
Look for admin accounts you did not create, especially with registration dates in the campaign window. Look for active_plugins entries that point to files you cannot find on disk (a classic lazy webshell wrapper), and check rewrite_rules for any regex that injects a redirect to a non-stock domain.

Traffic Patterns That Betray a Hidden Compromise
File and database checks catch most drop-and-run payloads. They miss the slow-burn cases, the kind where the attacker touches the filesystem only once and communicates through wp-cron.php, admin-ajax.php, or a manipulated front-end template. For those, you read the web server access log.
POST Floods to admin-ajax.php With No Front-End Trigger
Real visitors hit admin-ajax.php as a side effect of loading a page. Bots hitting it directly, especially with a action= parameter that is not in your plugin list, are usually either a brute-force attempt or a compromised site calling home. Group by source IP and look for any IP generating more than 5% of the day's POSTs to that endpoint.
Outbound DNS to Freshly Registered Domains
Spin up a packet capture or, easier, tail your DNS resolver log. Look for lookups that never resolve to a CDN or a known ad network. Most webshells phone home via a TXT, A, or HTTPS request, and the destination domain is usually registered in the last 90 days. Cross-reference against AlienVault OTX for free reputation data.
Spikes in 404s for Plugin or Theme Files That Should Not Exist
Once a payload is active, it often probes for sibling vulnerabilities. A sudden wave of 404s for /wp-content/plugins/random-slug/... paths is a strong signal that something on the box is already doing reconnaissance for the next campaign.

The IOC Reference List Worth Bookmarking
Keep this table somewhere accessible. When a new disclosure drops, hash and path-match against it before you do anything else.
- Drop paths:
/wp-content/uploads/2024/and/wp-content/uploads/2025/directories containing any.php,.phtml,.phar, or double-extension file likeimage.jpg.php. - Loader files:
wp-includes/widgets/class-wp-widget-custom-html.phpandwp-admin/includes/class-wp-upgrader.phpmodified outside of a normal update window. These two are heavily abused as “looks legitimate” wrappers. - Mu-plugin drop: Any file under
/wp-content/mu-plugins/you did not add. This folder auto-loads on every request, which is why attackers love it. - Cron abuse: Custom entries in
wp_optionswithcronkey that schedule callbacks to non-stock domains every few hours. - Hidden admin: User role of
administratorwithuser_registeredtimestamp inside the campaign window and zero posts.
For a deeper file-integrity workflow, our file integrity monitoring guide walks through tripwire-style hashing at the kernel level.

What the 2% Do Differently
After watching dozens of post-mortems, the sites that catch compromise within 72 hours share one habit: they treat the access log as a primary signal, not a debugging tool. They alert on baseline deviation, not on known-bad signatures. The moment the average POST size to wp-login.php shifts, or a new user-agent string shows up in xmlrpc.php traffic, they know.
If you want a shortcut, set a daily cron that does three things: runs wp core verify-checksums, exports the wp_users table to a diff against yesterday, and greps the day's access log for the IOC list above. The whole thing takes 4 minutes to wire up and catches the patterns a weekly scanner sleeps through. If you need help tuning it, our 7 log patterns that catch pre-patch exploitation article gives you the exact regexes.
FAQ: Triaging a Suspected WordPress Compromise
How long does the average mass WordPress exploitation campaign stay hidden?
Research from Wordfence and Sucuri consistently shows dwell times between 30 and 180 days for non-defacement payloads. The 25K confirmed number almost always represents a campaign that has been running for at least two months before disclosure.
Can I trust a plugin-based malware scanner to find the IOC I care about?
Plugin scanners match against known signatures. They will miss a freshly packed webshell and they will miss a database-only payload entirely. Use them as a second opinion, never as the first line of defense. The manual file diff and database query above catch what scanners cannot.
My hosting provider said the site is clean. Should I still run the checklist?
Yes, without hesitation. Hosting-level scans check the shared filesystem for known payloads. They do not run your diff, they do not read your wp_users table, and they almost never inspect your access log. The provider's “clean” verdict is necessary but nowhere near sufficient.
What is the single highest-value action if I only have 10 minutes?
Run wp core verify-checksums via WP-CLI and the find wp-content/uploads -name "*.php" command. Together they catch the two most common mass-campaign patterns: tampered core and uploaded webshells. Everything else in this guide is the next 20 minutes of coverage.
Your Next Move
Do not bookmark this article and walk away. Open a terminal right now and run the four greps from Step 2. The output will either give you peace of mind or hand you a 17-character hash that just saved you a six-figure incident. Either way, you will know your real bucket.
Want a weekly short-form drop with new IOCs, regex templates, and the post-mortems I cannot publish in full? Subscribe below, and I will send the working artifacts straight to your inbox.



