WordPress Admin or Client Account Compromised: How to Contain It
Someone logged in as one of your users. Lock the account, kill its sessions and application passwords, secure the email behind it, then review what it changed.
A security scanner flagged a file on your site. Verify it against official checksums, compare it with a clean copy, check its timestamps and read it safely to decide.
Your security plugin, your host’s malware scanner or a one-off scan has flagged a file: “Suspicious code found”, “Known malware signature”, “File appears to be malicious or unsafe”, or “Modified core file”, with a path such as wp-content/plugins/some-plugin/includes/class-loader.php. The site looks fine. Now you have to decide: is this a real infection, or a scanner being overcautious?
Both happen often. Scanners deliberately err on the side of caution, so legitimate code that is compressed, encoded or unusual can trip them. But real malware is designed to look boring, and deleting a flagged file without understanding it can either break your site or leave the actual infection in place. This guide gives you a repeatable way to decide, starting with checks that settle most cases in minutes and ending with how to read suspicious code without ever running it.
Malware scanners use three main techniques, and knowing which one fired tells you a lot about how much to trust the alert.
| Technique | How it works | False positives |
|---|---|---|
| Signatures | Matches a file’s hash or a specific code pattern against a database of known malware samples | Rare for exact hash matches; more common for broad pattern signatures |
| Heuristics | Scores code for risky traits: encoded blobs, eval, dynamic function names, unusual file locations |
Common. Encoded or minified legitimate code scores high. |
| File integrity | Compares files to the official versions published by WordPress.org or the vendor | Low, but it cannot judge premium plugins, custom code or files that should not exist |
Scanners also differ in where they look. Plugin scanners such as Wordfence or MalCare read files from inside WordPress. Server scanners such as ImunifyAV, ClamAV or Linux Malware Detect read the whole account, including files outside WordPress. Remote scanners such as Sucuri SiteCheck only see the pages your site serves to the public, so they cannot flag a file at all, only its visible effects.
The question you are answering is narrow: is this exact file the one its author published, and if not, is the difference harmful?
wp-config.php. WP-CLI is wp on most servers and wp-cli on some managed hosts. stat, diff, grep and find run on the server.php, and do not paste its contents into online “decoders” that execute code. Reading it as text is safe.Before you touch anything, write down the full path, the scanner’s reason or signature name, and any line number it gives. Signature names are often searchable: a name tied to a known malware family is a much stronger signal than a generic “suspicious eval” label. Note whether the scanner says “modified”, “unknown file” or “malicious”, because those lead to different checks below.
Also check whether the scanner flagged one file or many. A single hit in a well-known plugin is often a false positive. Several hits across unrelated folders, or a flagged file in wp-content/uploads, point toward a real infection.
wp core verify-checksums --include-root
This downloads the official checksums for your installed WordPress version and compares every core file against them, without loading WordPress (so a broken or infected plugin cannot interfere). --include-root also warns about non-WordPress items in the root folder. Good output: “Success: WordPress installation verifies against checksums.” Bad output: lines like “Warning: File doesn’t verify against checksum: wp-includes/FILENAME.php” (a core file was changed) or “Warning: File should not exist: wp-includes/FILENAME.php” (an extra file was added inside core folders, a classic hiding place). With --include-root, expect warnings for your own legitimate root files such as wp-config.php, .htaccess or a verification file, and judge each one. If you run a translated WordPress, add --locale=LOCALE, where LOCALE is the code shown in Dashboard, Updates, for example de_DE.
wp plugin verify-checksums --all
--all checks every installed plugin. For each plugin hosted on WordPress.org, it compares your files against the official checksums for your installed version. Good output: “Success: Verified N of N plugins.” Bad output: a table listing a plugin, a file and a message such as “Checksum does not match” (file changed) or “File was added” (extra file). Premium plugins and plugins not hosted on WordPress.org are reported as skipped because there are no official checksums to compare against; for those, use Step 3. To check one plugin, replace --all with its folder name.
If the flagged file passes its checksum, it is byte-for-byte identical to the official release. The scanner may still have a point (the official version could have a known vulnerability) but the file itself is not an injected infection.
For themes, premium plugins and any file the checksum tools cannot cover, compare against a clean copy of the exact same version.
mkdir -p ~/clean && cd ~/clean
curl -sO https://downloads.wordpress.org/plugin/PLUGIN_SLUG.VERSION.zip
unzip -q PLUGIN_SLUG.VERSION.zip
diff -rq ~/clean/PLUGIN_SLUG /PATH/TO/SITE/wp-content/plugins/PLUGIN_SLUG
Placeholders: PLUGIN_SLUG is the plugin’s folder name; VERSION is your installed version from wp plugin list; /PATH/TO/SITE is your site folder. For a theme, use https://downloads.wordpress.org/theme/THEME_SLUG.VERSION.zip. For a premium product, download the same version from the vendor’s account area and upload it to ~/clean instead. Flags: curl -sO saves the file under its own name quietly; diff -r compares folders recursively and -q lists only which files differ. Good output: no lines at all, or only “Only in” lines for files you know about (such as a cache or log the plugin writes). Bad output: “Files … differ” for PHP files, or “Only in /PATH/TO/SITE/…” for PHP files that the clean copy does not contain.
To see exactly what changed in one file:
diff -u ~/clean/PLUGIN_SLUG/RELATIVE_PATH /PATH/TO/SITE/wp-content/plugins/PLUGIN_SLUG/RELATIVE_PATH | head -60
RELATIVE_PATH is the file’s path inside the plugin folder. -u prints a unified diff: lines starting with - are in the clean copy only, lines with + are in your copy only. A block of added code at the very top or bottom of the file, especially on one long line, is the most common injection pattern. Remove ~/clean when you are done.
stat -c '%y %z %n' FLAGGED_FILE
ls -la --time-style=full-iso "$(dirname FLAGGED_FILE)"
Placeholder: FLAGGED_FILE is the path the scanner reported. stat prints the modification time (mtime, %y), the change time (ctime, %z) and the name. The ls line lists the whole folder with full timestamps so you can compare. Reading it: files from one plugin release usually share similar times from when you installed or updated it. A single file with a much newer mtime than its neighbors is suspicious. Attackers often copy a neighbor’s mtime onto their file with touch, but they cannot set ctime that way, so a file whose mtime matches the folder while its ctime is recent and different deserves a close look. Compare the ctime with your update history and your host’s backup logs.
To find other files changed around the same time:
find . -type f -name "*.php" -newerct "YYYY-MM-DD HH:MM" ! -newerct "YYYY-MM-DD HH:MM" -ls
Use the flagged file’s ctime minus and plus an hour for the two timestamps, in the server’s time zone. -newerct compares change time with a date string; the ! negates the second test, so this lists PHP files changed inside that window. Bad output: other files in unrelated folders changed in the same minutes, which suggests one intrusion dropping several files.
View the file as plain text, a page at a time, with long lines cut short so one huge blob does not flood your terminal:
cut -c1-300 FLAGGED_FILE | less
grep -nE 'eval\(|base64_decode\(|gzinflate\(|gzuncompress\(|str_rot13\(|assert\(|create_function\(|preg_replace\(.*/e|\$_(POST|GET|REQUEST|COOKIE)\[' FLAGGED_FILE | cut -c1-200
cut -c1-300 trims each line to 300 characters; less lets you scroll and quit with q. The grep lists line numbers for common risky functions and for direct use of request data. Neither command executes anything.
eval(base64_decode(...)), eval(gzinflate(...)) or chains of decoders. Decoding alone is common; decoding straight into eval is the red flag.$f = 'ba'.'se'.'64'.'_de'.'code';, chr() sequences, or hex escapes like "\x65\x76\x61\x6c", followed by $f($data).$_POST, $_REQUEST or $_COOKIE values passed into eval, assert, system, shell_exec or a variable function. That is a remote control.preg_replace with the /e modifier, create_function or string assert: old code-execution tricks. Modern PHP removed or disabled them, so in current code they are almost never legitimate.wp-content/uploads; PHP disguised with image or icon extensions and pulled in with include; hidden dot-files; names that imitate core files but sit in the wrong folder.eval.If you want to see what a blob decodes to, copy only the encoded string into a text file and decode it with base64 -d BLOB_FILE | head -c 1000, where BLOB_FILE is that text file. Decoding is safe; running the result is not. The malicious redirect guide (FG-23) walks through decoding layered obfuscation step by step.
Sometimes the checks disagree: the file differs from the clean copy, but the extra code is not obviously hostile. Work through these questions:
grep "FILE_NAME" ACCESS_LOG, where FILE_NAME is the flagged file’s name and ACCESS_LOG is your log path. Legitimate include files are almost never requested directly; POST requests straight to a deep plugin file from unknown IPs are a strong sign of a webshell in use.wp plugin install PLUGIN_SLUG --version=VERSION --force, which overwrites the plugin files with the official release. If the flag disappears and the site works, you have removed the uncertainty without needing to prove which it was.If you conclude it was a false positive, use the scanner’s “ignore” or “mark as safe” option for that specific file and version only, so a future change to the file is still caught. Report it to the scanner vendor as well, with the plugin name and version; they fix their signatures that way.
One confirmed malicious file means the site has been compromised, and there is almost always more than one file. Do not just delete it and move on. Move a copy outside the web root as evidence, then follow the first-hour hacked site guide (FG-05) to contain the incident and find the way in. Once clean, the reinfection guide (FG-26) shows how to check for the backdoors, scheduled tasks and hidden admins that bring infections back.
Open a ticket if the flag came from the host’s own scanner and you believe it is wrong, if a flagged file reappears after removal, or if you lack the access to run these checks. Include:
Hosts can often tell you which scanner rule fired and whether the same signature is triggering on other accounts, which quickly separates a bad rule from a real outbreak. Our diagnostic tools can check what your site shows to visitors, and if you see visible signs of a hack, go to the Emergency Room.
Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
Still stuck?
Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.
Get the free checklistsKeep reading
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close