What a webshell actually is
A webshell is a small script — on WordPress, almost always PHP — that an attacker uploads to give themselves remote command execution or file management through nothing more than a browser and a URL. It's not the vulnerability that let them in; it's what they leave behind after getting in, so they don't need that original vulnerability a second time. Patch the plugin that let them through, and the webshell doesn't care — it's a separate, independent way back in.
That distinction matters more than it sounds. Sophos's incident-response data puts webshells at the center of a huge share of real intrusions — they turned up as the persistence mechanism in roughly 1 in 5 of all investigated compromise cases across the industry, not a niche or rare technique.
Why a "clean" scan doesn't mean the webshell is gone
This is the part that catches people out. You fix the redirect. You remove the spam links from your posts. You even restore from a backup — and the problem comes back within days. A webshell survives all of that unless it's specifically found and removed, because:
- It doesn't depend on the plugin or theme vulnerability that got exploited originally — updating that plugin does nothing to it.
- A password reset doesn't touch it — it isn't using your login, it's running its own code directly.
- A backup restore only helps if the backup predates the infection. If the webshell was already sitting there when the backup was taken, restoring just brings it back too.
Attackers increasingly compound this by planting webshells in mu-plugins ("must-use" plugins), which WordPress loads automatically before regular plugins — including your security plugin. A backdoor sitting there can, in principle, execute and even try to interfere with a scanner before the scanner has a chance to run. It's a more advanced technique than a simple uploads-folder script, but it's a real and documented one.
Where attackers actually hide them
wp-content/uploads/. This is the single most common location. The directory exists to hold media — images, PDFs, documents — never executable code. If the vulnerability that got exploited was a file-upload flaw, the dropped file very often lands here, sometimes named to blend in (up.php, an image-like name with a hidden double extension, or something mimicking a real WordPress filename).wp-content/mu-plugins/. Loads before ordinary plugins and before most security scanners get a chance to run — a more sophisticated hiding spot, and one worth checking specifically if uploads comes back clean.- Inside a theme's
functions.php. Runs on every page load and is rarely read end-to-end by a human, making an injected function easy to miss. - The database itself. Some backdoors store their payload in a
wp_optionsrow or similar, then use a tiny loader elsewhere to pull and execute it — a way to survive a filesystem-only cleanup.
Obfuscation shows up everywhere on this list. A webshell rarely reads as plain, readable PHP — expect base64_decode(), eval(), or string concatenation built specifically to defeat a simple text search for suspicious function names.
Finding one without a security background
- Check
wp-content/uploads/for anything that isn't media. If you have shell access:What you'd type
Any result here deserves a look — a handful of emptyfind wp-content/uploads -name "*.php"index.phpfiles (used to block directory listing) are normal in some setups, but a PHP file with real code in it is not. - Compare core and plugin files against known-clean copies. Download the same WordPress version, and the same plugin versions, from wordpress.org, and diff them against what's actually on your server. Anything extra, or anything that differs, is worth reading.
- List recently modified files and check whether each one corresponds to a real update you made:
What you'd type
find wp-content -type f -mtime -14 -printf '%T@ %p\n' | sort -rn - Run a malware scanner — a decent one will flag known webshell signatures and common obfuscation patterns automatically, which is faster than manual review for a first pass, even though (as covered in why scanners produce false positives) no scanner catches everything or is perfectly precise.
Finding one means checking for more
Attackers who leave one webshell often leave several — a primary one and one or two smaller fallbacks in case the first is found. Treat discovering a single backdoor as a reason to search more thoroughly, not as the end of the job. Once you've found and removed what you can, work through the full malware removal process — including replacing WordPress core wholesale rather than patching it, and rotating every credential the site touches — since a webshell is rarely the only thing an attacker left behind.
If the entry point was a modified core file rather than an uploaded script, that's a related but distinct pattern — see what it means when a core file is silently altered.
Catch a backdoor before it becomes the reason cleanup "doesn't stick."
Install free →