A compromised WordPress site rarely announces itself cleanly. More often it’s a slow leak of symptoms – outbound spam nobody sent, a hosting provider warning about resource abuse, or a security plugin flagging a handful of files you don’t recognise. Investigating a WordPress malware infection well is its own small discipline. Here’s a practical walkthrough of the process, based on a real infection investigation.
Step 1: Find the Files
Three tools cover most of the discovery work, each catching things the others miss:
- grep for known-suspicious function calls across the whole install – a manual but effective first pass:
grep -rlE "eval(|base64_decode(|gzinflate(|str_rot13(|assert("
--include="*.php" /path/to/wordpress/
These functions all have legitimate uses, so this will produce false positives from plugin code – but a webshell dropped into wp-content/uploads/ or a random theme subfolder, using eval(base64_decode(...)) to hide its payload, stands out immediately because legitimate uploads directories shouldn’t contain executable PHP at all.
- Linux Malware Detect (maldet) – a signature-based scanner purpose-built for shared hosting environments, checks files against a database of known malware signatures and heuristics rather than just pattern-matching function names.
- Sucuri’s SiteCheck / WordPress scanner – WordPress-specific and good at catching things that are only suspicious in the context of a WordPress install: unexpected admin users, modified core files, malicious redirects injected into theme files.
Running all three against the same install and cross-referencing the results gives a far more complete picture than any single scanner – it’s common for a Sucuri scan to flag eight files where a plain grep only caught two, because Sucuri’s WordPress-specific heuristics catch injected redirects and modified core files that don’t match an obvious “eval + base64” pattern.
Step 2: Understand What You Found
Once you’ve isolated a suspicious file, the next question is what it actually does. Malicious PHP is almost always obfuscated – layers of base64_decode, gzinflate, and string reversal wrapped around the real payload specifically to defeat casual reading and simple pattern-matching scanners. A few approaches for unwinding it:
- Manual decoding – work from the outside in. If the file is
eval(gzinflate(base64_decode('...'))), replace the outerevalwithechoand run just that line in a sandboxed PHP CLI (never on the live server) to print the decoded layer beneath. Repeat until you hit readable code. - Online PHP sandboxes – sites like 3v4l.org let you run a snippet of PHP against dozens of PHP versions simultaneously and see the output, which is useful for confirming exactly what a decoded payload does without touching your own infrastructure. Never paste anything containing real credentials or site-specific data into a third-party sandbox.
- VirusTotal – upload the suspicious file (or hash it and search first, to avoid uploading anything sensitive) and check whether other AV engines have already classified it. Generic classifications like “PHP.Spambot” or “PHP.Shell.Generic” are common for these kinds of drops – they tell you the broad family even when the specific variant is novel.
What You’ll Typically Find
Two payload types dominate real-world WordPress compromises:
- Webshells – a file that accepts commands via a GET/POST parameter and executes them on the server, giving the attacker an ongoing backdoor rather than a one-off exploit. These range from minimal one-liners to full-featured shells with file managers and database browsers built in.
- Spambots – PHP that hijacks the compromised site to send spam email or inject SEO spam content into pages, monetising the compromise rather than using it as a foothold for further access. These are often what actually gets a site noticed, because the outbound spam volume triggers hosting provider abuse alerts.
Both frequently get planted through the same handful of entry points: an outdated plugin or theme with a known vulnerability, weak admin credentials, or a compromised FTP/SFTP account. The malware itself is rarely the interesting part – the entry point is what actually needs fixing, or the same file reappears within days of cleanup.
Cleanup Checklist
- Take the site offline or into maintenance mode while you work, so the malware can’t do further damage or send further spam mid-cleanup.
- Diff core files against a clean WordPress install of the matching version, to catch anything injected into files you wouldn’t normally think to check.
- Remove every flagged file, not just the obvious one – webshells are frequently dropped in multiple locations as redundancy.
- Rotate every credential – WordPress admin accounts, database password, FTP/SFTP, and hosting control panel – since you don’t know which was the actual entry point.
- Update everything – WordPress core, every plugin, every theme – before bringing the site back, since an outdated component is the most common original entry point.
- Re-scan after cleanup with the same tools used to find the infection, to confirm nothing was missed.
If you’re building this into a repeatable process rather than a one-off cleanup, it’s worth combining a scheduled maldet or Sucuri scan with the kind of static analysis covered in our post on SQLi detection with static analysis – the same taint-tracking techniques that catch unsanitised database queries can also flag suspicious eval/base64_decode patterns before a compromise even happens.

Leave a Reply
You must be logged in to post a comment.