You found a fake CAPTCHA on a client's site, or a customer forwarded a spam e-mail sent from your domain, or Google marked the site as dangerous. The order below is the one we use ourselves. Do not skip to step six because it feels productive.
1. Take a copy before you touch anything
A full copy of files and database, as the site is now, infected. You will need it to find the entry point later, and if the cleanup goes wrong you need the "before" state. Your host's backup panel or a plugin export is fine; label it with the date and time.
2. Put the site in maintenance mode or take it offline
For every hour a compromised site stays live it is serving malware to visitors or sending spam. A maintenance page at the host level (not a WordPress plugin, which the attacker can disable) stops that. Tell the client in one sentence what you did and why.
3. Change every credential, in this order
- Hosting control panel and SFTP/SSH passwords.
- The database password, and update wp-config.php to match.
- All WordPress administrator passwords, and the salts in wp-config.php so every session is logged out.
- Any API keys stored in plugins (payment, mail, CRM).
4. List the administrators and remove the ones you did not create
Users → Administrators. In the BdThemes incident and in the ClickFix campaign, a rogue administrator was the first thing the attacker created. Also check for users with a role you do not recognise and for e-mail addresses that look like yours with one letter changed.
5. Compare core files to the release
Download the exact WordPress version the site runs from wordpress.org and compare wp-admin and wp-includes file by file. Anything different or extra is suspect. Then look in wp-content/uploads and wp-content/mu-plugins for PHP files: uploads should contain no PHP at all, and the mu-plugins folder is where attackers hide code that loads on every request.
| Location | What to look for |
|---|---|
| wp-content/uploads | Any .php file. There should be none. |
| wp-content/mu-plugins | Files you did not put there. Must-use plugins cannot be deactivated from wp-admin. |
| Theme functions.php, header.php, footer.php | Base64 strings, eval(), long lines of encoded text, unfamiliar script tags. |
| .htaccess and wp-config.php | Redirect rules and includes you did not write. |
| Database: wp_options, wp_posts | Script tags in post content, unknown entries in active_plugins, a changed siteurl. |
6. Find the entry point
Cleaning without knowing how they got in means doing this again in three weeks. The access logs from the host show the first request to the malicious file. Work backwards from that timestamp: which plugin had a known vulnerability at the time, which form accepted an upload, which login succeeded from an unfamiliar country. Vulnerable plugin versions are the most common answer; a reused password is the second.
7. Clean or restore
If you have a clean backup from before the entry point, restoring it and then applying every update is faster and safer than cleaning by hand. If you do not, replace core and every plugin and theme with fresh copies from the source, then clean the database and the uploads folder by hand using the table above.
8. Update everything, then close the hole
WordPress core, PHP if the host allows it, every plugin, every theme, including inactive ones (delete inactive plugins and themes instead). Then the specific fix for the entry point: the patched plugin version, the removed upload field, the login protected behind two-factor.
9. Bring it back and get it delisted
Remove maintenance mode. Request a review in Google Search Console if the site was flagged, and check the domain against the common blocklists. Tell the client what happened, what you did, and what is different now, in plain words.
10. Make the next one visible on day one
Most compromises are found by a visitor or by Google, weeks in. A check that runs around the clock, compares core files to the release, lists new administrator accounts and matches plugin versions against known vulnerabilities turns that into a message within the hour. Whatever tool does that for you, make sure it runs on every site, including the small ones nobody logs into.
How long each step takes, realistically
| Step | Time | What makes it longer |
|---|---|---|
| 1. Copy | 10 to 30 minutes | A large uploads folder, or a host that only offers a slow export |
| 2. Offline | 5 minutes | No access to the host's control panel, so you need the client's login first |
| 3. Credentials | 20 to 40 minutes | API keys nobody documented; you find them by reading plugin settings one by one |
| 4. Administrators | 5 minutes | A site with many editors, where the rogue account uses a plausible name |
| 5. Core file comparison | 30 to 60 minutes | An old WordPress version whose release archive you have to find first; a theme edited by hand over the years |
| 6. Entry point | 30 minutes to half a day | Logs that rotate after seven days, so the first request is already gone |
| 7. Clean or restore | 1 to 3 hours | No clean backup, so every plugin and theme is replaced by hand and the database is searched line by line |
| 8 to 10 | 1 hour | Google's review can take days; the client conversation cannot wait for it |
What to tell the client, and when
Three messages, in plain words, without technical detail the client cannot act on.
- Within the first hour: "Your site has been compromised. We have taken it offline so visitors are safe, we have a copy for the investigation, and we are cleaning it now. I will send an update by [time]." Nothing else. Do not speculate about the cause yet.
- When the site is back: what was found, how the attacker got in, what you changed so it cannot happen the same way, and what you still have to wait for (Google's review, a blocklist). Keep the technical version for your own log.
- A week later: confirm the site has stayed clean, list what is different in the care plan from now on, and say what the client should do on their side: a password manager, no shared logins, tell you before they install a plugin.
Do you have to report it?
If personal data may have been accessed, a shop's customer records or a form's submissions for instance, the GDPR treats that as a personal data breach. The site owner has to assess it and, when there is a risk to the people involved, report it to the supervisory authority within 72 hours of becoming aware of it. That is the owner's duty, not the agency's, but the owner will only know because you told them. Put it in the first-hour message when customer data is involved, and write down when you found out.
What to change afterwards, so this is the last time
- Logs that live longer than a week. The entry point is found in the access log. If the host rotates logs after seven days and the break-in is found after three weeks, step six becomes a guess.
- A backup you have restored at least once. A backup that has never been restored is a hope. Restore one to a staging copy every quarter.
- Updates that do not wait for a Friday. The vulnerable plugin version is the most common entry point. The gap between a fix being published and it being installed is the window the attacker uses.
- A check that runs on every site, including the ones nobody logs into. The site that gets hacked is rarely the flagship. It is the brochure site from 2021 that still runs the old form plugin.
- No shared administrator accounts. One account per person, so a rogue login is visible as a rogue login and not as "probably Tom".
Sources and further reading
- Rapid7 Labs, "When Trusted Websites Turn Malicious"10 March 2026, on the ClickFix campaign's entry points
- The Hacker News, "BdThemes Supply Chain Attack Poisons JSON to Create Rogue WordPress Admins"August 2026
- WordPress.org, "FAQ: My site was hacked"wordpress.org/documentation