Before you investigate: preserve, then contain
The instinct is to restore last week's backup and move on. Don't do that first. A restore wipes out the evidence of how the attacker got in, and if that way in is still open, whether it's a vulnerable plugin or a stolen password, they will be back within days.
- Take a full copy of the hacked site, files and database, and put it somewhere the site can't reach. That copy is your evidence.
- Change every password with access to the site: WordPress administrators, hosting panel, SFTP/SSH, the database user.
- Invalidate every logged-in session by replacing the authentication keys and salts in wp-config.php. A salts generator produces a fresh set. Everyone, including the attacker, is logged out.
- If the site is actively harming visitors, with redirects or malware downloads, put it in maintenance mode while you work.
Where to look, in order
Attackers change a small number of predictable things. Go through them in this order. The first two are quick, and they are where most break-ins show up.
1. Users and roles
Filter Users by Administrator and question every account. Attackers create admin accounts with plausible names ("wpadmin", "support", a copy of a real name) or quietly promote an existing low-level account. With WP-CLI: wp user list --role=administrator. Check the email addresses too. An attacker may change a real admin's email so that password resets go to them.
2. Plugins, themes and must-use plugins
Look for plugins you don't recognise, including ones that are deactivated, which can still hold a backdoor file anyone can reach by URL. Then check the Must-Use tab on the Plugins screen, or the wp-content/mu-plugins folder: code there runs on every request, can't be switched off from the dashboard, and is easy to miss.
3. Files
Compare your files with the official versions:
- wp core verify-checksums checks every WordPress core file against WordPress.org.
- wp plugin verify-checksums --all does the same for plugins from the WordPress.org directory. Premium plugins need a fresh copy from the vendor to compare against.
- PHP files in wp-content/uploads are almost always malicious. Uploads should hold media, not code.
- Recently modified PHP files, for example find . -name '*.php' -mtime -14, but bear in mind attackers often reset modification dates.
- The top of wp-config.php, .htaccess and your theme's functions.php, which are favourite places to insert a line of code.
4. Settings
A handful of settings let an attacker take the site over without touching a file. Check Settings → General: the WordPress and site address (redirecting all your visitors), the admin email (receiving your password resets), "Anyone can register" and the default role (handing out accounts). On a shop, check the payment settings, because redirecting payouts is one of the most lucrative things an attacker can do to a WooCommerce site.
5. The database
Injected spam and redirect scripts often live in the database rather than in files. Search post content and the options table for <script, eval(, base64_decode and domains you don't recognise. Look for published posts and pages you didn't write, especially large batches of them.
6. Scheduled tasks
A well-hidden infection reinstalls itself. wp cron event list shows every scheduled job: look for hooks you can't match to a plugin you use. If you clean everything else but miss this, the site is reinfected on the next cron run.
7. Server logs: how they got in
Your host's access logs are the best evidence of the entry point, if they still exist. Many hosts keep them only for days. Look for bursts of POST requests to wp-login.php or xmlrpc.php (password guessing), requests to PHP files that shouldn't exist, and requests to a specific plugin's folder just before the first sign of trouble, which points at a vulnerability in that plugin.
Why "what changed" is so hard to answer
Everything above is reconstruction. WordPress doesn't record who created a user, changed a role, installed a plugin or edited a setting. It stores the current state and overwrites the past. And the attacker has every reason to tidy up after themselves: delete the account they used, remove the plugin they installed, clear whatever log they can find. If an activity log was running but stored its records in an ordinary database table, anyone with admin or database access could delete the rows that mention them, and nothing about the remaining table would look wrong.
A log an attacker can't quietly rewrite
Who Changed It is built for exactly this situation. It records the events a break-in consists of, and it's designed so that covering them up leaves a trace.
- The break-in is flagged as it happens. A new administrator account, a promotion to administrator, a changed site address or admin email, a change to payment settings, and any use of the theme or plugin file editor are all classified dangerous. So is a burst of failed logins from one IP, or five deletions in ten minutes. A login at 03:00 from an IP that account has never used is escalated too. How events are classified has every rule.
- The alert leaves the server immediately. Dangerous events are sent to you by email, and natively to Slack, Discord or Telegram if you set them up. By the time an attacker goes looking for the log, the message about their new admin account is already in your inbox.
- Every record is chained. Each row's hash is keyed and folds in the hash of the row before it, so deleting or editing a row in the database breaks the chain, and verification reports exactly which records are altered or missing. Purging the log from the plugin's own settings is allowed, but it's always logged as a dangerous event, and that event can't be muted.
- The key can live outside the database. Define WHOCHITA_CHAIN_KEY in wp-config.php and an attacker with only database access, the typical result of SQL injection, can't forge a consistent chain.
- Silence is a signal. The optional daily or weekly digest reports the chain's verification state and is sent even when nothing happened. If it stops arriving, something stopped it.
- The evidence is portable. Export the relevant window as signed CSV or JSON. The manifest records the filter, the record count and the chain state at export time, so your host, a client or an insurer can check the file without WordPress.
#60114 · 02:47 · no user · user_created · "wp-support" (administrator) — new administrator account — dangerous, alert sent
What it won't do
Being clear about the limits matters most on a page like this. Who Changed It records what passes through WordPress. It does not scan files for malware, and it can't see a file uploaded over FTP, a webshell written through a vulnerability that never goes through WordPress's own functions, or a row inserted straight into the database. An attacker with full control of the server can switch it off. That's why the alerts go out immediately and why a missing digest is meaningful. It doesn't replace backups, file integrity checks or a firewall. It's the record that tells you what happened in WordPress and lets you prove the record is intact.
If you'd rather hand the cleanup to someone else, finding the entry point, removing what was left behind and hardening the site is incident work. Malware removal and hardening is the service run by this site's own author.
Common questions
Should I restore a backup straight away after a hack?
Not before you have copied the hacked site. Restoring overwrites the evidence of how the attacker got in, and if the way in is still open (a vulnerable plugin, a stolen password) the restored site will be compromised again. Take a full copy of files and database first, find the entry point, then restore or clean.
How do I find files an attacker changed on WordPress?
Compare them with the official copies. With WP-CLI, wp core verify-checksums checks WordPress's own files and wp plugin verify-checksums --all checks plugins from WordPress.org. Then look for PHP files in wp-content/uploads, which should contain none, for recently modified PHP files, and for anything in wp-content/mu-plugins you didn't put there.
Can an attacker delete the activity log?
An attacker with administrator access can, but not quietly with Who Changed It. Purging the log from its settings is always logged as a dangerous event and triggers an alert. Deleting or editing rows directly in the database breaks the hash chain, and verification reports the missing or altered records. Alerts about the break-in, such as a new administrator account, will already have gone out by email, Slack, Discord or Telegram before the attacker gets to the log.
Does Who Changed It remove malware?
No. It is an activity log, not a scanner or a cleanup tool. It records what happens through WordPress, flags the events that look like an attack and alerts you, and keeps a record that can be verified later. It doesn't scan files, and changes made over FTP, SSH or directly in the database bypass it.