After Malware: the Order of Operations That Cleans a Site
Deleting the suspicious file is the step that gets a site reinfected. Containment, evidence, credential rotation and a rebuild, in that order.
Tomas Lindqvist · @tlindqvist
Hardening and operations
- Topic
- SEC
- Section
- Security & Hardening
- Published
- 20 OCT 2026
- Reading time
- 8 MIN
- Words
- 1780W
The site was flagged by a browser warning on a Friday. The owner found a PHP file in wp-content/uploads with an obfuscated first line, deleted it, and the warning cleared by Monday. Nine days later the same warning returned. The file had not come back by itself. The entry point, an outdated plugin with a known upload flaw, was still there, and the attacker had left a second copy of the payload in a must-use plugin that the first cleanup never looked at.
Malware recovery fails in that pattern more often than it fails because the cleanup was technically difficult. The removal is the easy part. The order is the hard part: contain the site, keep the evidence, find the way in, rotate everything the attacker could have touched, rebuild from a state that predates the first change, and watch afterwards. Skip one and the site is clean until the attacker returns.
This is a defensive sequence, written for the person who has to run it on a site they own. It deliberately does not describe how any specific vulnerability is exploited.
Disclosure: some links on this page are affiliate links, and WPSifero may earn a commission if you buy through them. The sequence works without buying anything; the paid tools named at the end shorten two steps, scanning and host-side isolation.
Containment comes before cleaning
The first instinct is to delete the obvious file. Resist it for ten minutes. The file is evidence of how the attacker got in and what else they changed, and deleting it removes both. Contain the site first, so the attacker loses the ability to act while the investigation runs.
Containment has four parts. Put the site into maintenance mode or take it offline entirely, depending on whether it is still serving customers. Revoke active sessions by rotating the WordPress salts, which invalidates every login cookie. Change the passwords on the hosting panel, SSH, SFTP, the database and every administrator account. Rotate the API keys the site holds: payment provider, mail provider, CDN, backup destination, any integration that can move money or data.
Rotate the salts last, after the evidence is copied, because rotating them changes the session state the logs describe. Copy the site first: a full archive and a database dump, stored somewhere the attacker cannot reach. Label it with the time. That copy is what a forensics person would need, and it is also what the cleanup will be compared against.
Do not skip the database password
A site-level compromise usually means the database credentials in wp-config.php were readable. Changing the WordPress admin password does not revoke them. Change the database user's password, then update wp-config.php.
Find the entry point before deleting anything
Work from the outside in. Start with the files, because they leave timestamps, and timestamps tell you what changed and roughly when. Then check the database, because some payloads live entirely in options and never touch a file. Then check the accounts, because the cheapest persistence is a new administrator nobody notices.
On the file side, the checks that find most of it are unglamorous. List PHP files under wp-content/uploads, where WordPress never writes code. List the contents of wp-content/mu-plugins, which loads without appearing in the plugin list. Compare core and plugin files against the official checksums, which catches modified files that look normal by name.
wp core verify-checksums
wp plugin verify-checksums --all
find wp-content/uploads -name '*.php' -type f -printf '%TY-%Tm-%Td %p\n' | sort
ls -la wp-content/mu-plugins/
Then read the web server access log around the first suspicious timestamp. The request that uploaded the file is usually visible as an odd POST to a plugin endpoint, a admin-ajax.php call with an unusual action, or a direct request to a file that should not exist. The log answers the question the files cannot: which door was open.
On the database side, look for unexpected administrators, unknown scheduled events, and options that contain script tags or encoded payloads. These queries are read-only and safe to run on the preserved copy:
SELECT u.ID, u.user_login, u.user_email
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key = 'wp_capabilities'
AND m.meta_value LIKE '%administrator%';
SELECT option_name, LEFT(option_value, 160) AS preview
FROM wp_options
WHERE option_value LIKE '%<script%'
LIMIT 20;
Adjust the table prefix. The first query should return the accounts you expect and nothing else. The second is a crude filter and will produce false positives on a site that legitimately stores markup, so read the rows rather than trusting the count. Scheduled events are worth listing too, because a cron hook that re-downloads a payload is a common way to survive a file cleanup.
The database is part of the infection surface
A file-only cleanup misses payloads stored in the database. Injected scripts in post content, a modified siteurl or home, a widget holding a redirect, a user with an unexpected capability, a cron event with a name that looks like a hash. Each one survives a file restore and reappears when the relevant page renders.
List the scheduled events and compare them with what the installed plugins actually register. A hook name that matches no plugin is a candidate. List the administrators and their registration dates. Check siteurl and home against the domain you expect, because a hijacked option redirects the whole site without touching a file.
wp cron event list --fields=hook,next_run_relative,recurrence
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp option get siteurl
wp option get home
wp option get active_plugins --format=json
The active_plugins entry is a quick check for a plugin that was activated without a matching directory, which is one way a payload registers itself. It is not conclusive on its own; compare it with the directories on disk and with the plugin list.
Rebuild from known-good, then reapply content
There are two honest paths. The better one is a restore from a backup taken before the first malicious change, followed by patching the entry point. The other is a rebuild: replace core, plugins and themes with fresh copies from their official sources, then reapply the site's content and configuration by hand.
The restore path is faster and only as good as the archive's age. The rebuild path takes longer and is more certain, because every executable file is replaced with a known-good copy. On a site where the first change cannot be dated, rebuild. On a site with a clean archive from before the incident, restore and verify.
Content is the part that has to be treated as suspect either way. Export posts, pages and media, scan them, and reimport into the clean install. Do not copy wp-content wholesale from the infected site, because that is how the payload travels. Uploads need a scan for PHP files and for scripts embedded in image metadata, and a policy that no PHP file belongs in that directory at all.
When the rebuild is done, compare the new site against the preserved copy. A file count, a plugin list, a user list and a cron list are enough to show whether something was missed. The comparison is also the record that the cleanup was exhaustive, which matters if the site is later reviewed by a host or a payment provider.
Close the hole and keep it closed
The entry point is usually one of a short list: an outdated plugin or theme, a weak or reused password, a stolen session, an exposed configuration or backup file, or a hosting account with more access than it needs. The patch is specific to the case, and the general controls are the ones in the hardening checklist: updates on a schedule, two-factor authentication, least privilege, no file editing from the dashboard, and a web application firewall in front of the site.
Two controls are worth doing during the recovery rather than later. Remove every plugin and theme that is not in use, especially the abandoned ones, because an inactive plugin is still executable code. And delete the staging copies, old backups and test installs that share credentials with production, since those are the doors nobody remembers.
Then watch. A second payload, a dormant administrator or a scheduled task can appear days after the cleanup. File integrity monitoring, login alerts and a weekly check of the administrator list for thirty days is proportionate. The window is not arbitrary: the replacement often arrives after the site looks fixed, when attention has moved on.
Get removed from the blocklists, in order
Cleaning the site does not clear its reputation. Browsers, search engines, mail providers and hosting networks keep their own lists, and each has its own removal process. Do them in the order that unblocks the most visitors first.
- Confirm the site is actually clean, with a scan and a manual check of the preserved copy against the rebuild.
- Request a review in Google Search Console's security issues report, which is what drives the browser warning for most sites.
- Check the hosting provider's abuse or security dashboard, because some hosts suspend first and ask later.
- Check the mail provider's blocklist status if the site sends transactional email, since a compromised site is often used to send spam.
- Check any CDN or WAF in front of the site, which may cache the warning page after the site is clean.
- Tell the people who need to know: customers if data was exposed, the payment provider if card data was in scope, the host if the account was used to attack others.
The disclosure question is legal and jurisdictional, and this article cannot answer it for a specific site. What it can say is that the decision needs to be made deliberately, with the preserved evidence in hand, rather than improvised during the cleanup.
What to do in the first hour
Preserve the site. Contain it. Change the passwords on the panel, SSH, database and administrators. Rotate the salts. Pull the logs. List the PHP files in uploads, the must-use plugins, the administrators and the cron events. Then decide between restore and rebuild, based on whether a clean archive predates the first change.
The paid tools exist to shorten specific steps. Jetpack's scan and backup modules combine file monitoring with restore points that sit outside the hosting account, which helps when the account itself is the compromised surface. A host such as SiteGround can isolate or restore an account from its side, which is useful when the panel login is the thing you no longer trust.
Neither replaces the sequence. The order is the defence: contain, preserve, investigate, rotate, rebuild, watch. A site cleaned in that order tends to stay clean. A site cleaned by deleting the file that triggered the warning tends to be back in the queue within a fortnight.
Tomas Lindqvist · @tlindqvist
Hardening and operations
Threat models plugins, audits capability and nonce handling, and builds the observability that tells you an integration broke before a customer does.
Continue