Skip to content
Case Studies

Three Backdoors, Zero Alerts: Finding WordPress Malware a Security Plugin Missed

August 1, 2026 · Webnorly · 9 min read

A client’s WordPress site had stopped behaving properly. Booking forms failed intermittently, the page builder would not load, and the error log offered nothing useful. Their security plugin, installed and active, reported the site as clean. We verified the installation file by file against its official sources and found three backdoor files hidden inside it, each one built specifically to survive the kind of scan that had already cleared the site.

The Setup

The site ran WordPress 7.0 with a commercial page-builder theme, an appointment booking system, and fifteen plugins including a widely used security suite and a custom fields framework. A conventional stack, professionally built, with several premium components.

Two details stood out before we opened a single file.

The security plugin was installed, active, and reporting no problems. Whatever was wrong had already passed a scan by one of the most widely deployed security tools in the ecosystem.

And the error log was nearly empty. Six entries, all from a single eighteen-minute window eleven days earlier, all relating to one plugin’s scheduled tasks. No fatal errors. No stack traces. Nothing describing the failures the client was reporting. The log was not recording the actual problem at all.

The Challenge

The reported symptoms were vague in the way these things usually are. Some features worked, others did not. The booking system failed unpredictably. Nobody could say when it started.

That ambiguity is the real difficulty. Symptoms like these have at least three plausible explanations: a plugin conflict, a server configuration change, or a compromise. Each demands a completely different response, and guessing wrong costs days. So we did not guess. We verified everything.

Investigation

Why signature scanning misses modern WordPress backdoors

Most malware hunts start by searching for suspicious functions: eval, base64_decode, shell_exec. On a modern WordPress installation this is close to useless. WordPress core, cryptography libraries, HTTP clients, the theme framework, and the security plugin’s own scanner all use these functions legitimately.

Our heuristic pass flagged 49 files across the premium plugins and theme. Every one was clean library code.

You cannot find a well-written backdoor by looking for code that looks malicious. There is far too much legitimate code that looks malicious.

So we inverted the question. Instead of asking which files look suspicious, we asked which files have no business being there.

Verifying WordPress core against the official release

We downloaded the official WordPress 7.0 release and compared all 1,921 core files by checksum.

This is the highest-value test available on a WordPress site. Core files are published, versioned, and identical on every installation worldwide, so any deviation is by definition something someone put there. Almost nobody runs this check.

Verifying every plugin against the repository

We pulled the exact matching version of every plugin available on the WordPress repository and compared them the same way, ten plugins in total. Premium plugins and commercial themes publish no checksums, so those were analysed structurally instead: entropy analysis, obfuscation patterns, code appended after long whitespace gaps, and abnormally long single lines.

The line-ending trap that creates false positives

One pitfall is worth naming, because it will catch anyone attempting this.

Our first plugin comparison reported 210 of one plugin’s 459 files as modified. That looks like a catastrophic compromise. It was line endings. The hosting deployment had converted CRLF to LF across the codebase, and after normalising for that, every one of those files was byte-identical to the official release.

A comparison that does not account for line endings will bury you in false positives and destroy the credibility of everything else in your report.

Key Findings

Three backdoors, two separate intrusions

The comparison surfaced what pattern matching never would have: a file sitting in the WordPress admin directory that exists in no WordPress release.

In total we found three backdoor files across two copies of the site, two unique payloads, each protected by a different password. Two separate intrusions, not one.

Each was 402 bytes. In plain terms:

  1. The attacker sends a request containing a password.
  2. Without it, the file produces no output and exits.
  3. With it, the file decrypts a payload sent in the same request and executes it as PHP.
  4. The response is encrypted before being returned.

That is unauthenticated remote code execution. No WordPress login required, and full read and write access to every file on the account.

How the backdoors stayed hidden

Three design choices explain why the security plugin found nothing.

  • No matchable strings. No shell_exec, no system, no long encoded blob. Just execution of a variable decrypted moments earlier. There is nothing for a signature scanner to match on.
  • Silence on failure. Without the correct password the file exits immediately with no output. Request its URL and you get a blank page and an HTTP 200. Nothing reaches any log.
  • Plausible filenames. One posed as a cache file in the admin directory. The other posed as a language file, sitting among roughly 85 genuine translation files.

The language file gave itself away on structure rather than content. Every legitimate translation file in that directory has matching .mo and .po companions beside it. This one had neither, and at 402 bytes it sat against real files ranging from 12 KB to 488 KB.

Debug mode left on in production

The live configuration had debug mode enabled, with the safe default commented out immediately below it. A deliberate change, not an accident.

With debug display active, PHP notices are printed into every response. That corrupts the JSON that REST-driven features depend on, which is exactly why the booking system and page builder failed while the rest of the site appeared fine. The malware and the visible symptoms had different causes.

The firewall’s strongest layer was switched off

The security plugin’s extended protection, which inspects requests before WordPress loads, had been commented out in the server configuration file. The plugin still reported itself as enabled.

Without that layer, requests hitting a PHP file directly are never inspected, precisely the route both backdoors used.

Three core files had been hand-patched

Not malicious. The code was well-commented and clearly a developer’s work, addressing a genuine bug. But core patches are silently reverted by every WordPress update, and one of them could return empty responses across the entire REST API under conditions that occur in normal use.

Recovery Strategy

We delivered a prioritised plan rather than a list of files.

Remove the backdoors first, then rotate every credential, in that order, because rotating passwords while a shell is still live simply hands the attacker the new ones.

From there: restore the safe production configuration, re-enable the firewall’s pre-WordPress layer, audit the database for injected administrator accounts and malicious option rows, and correct the misconfigurations that were breaking the site independently of the malware.

We also documented what the review could not cover. A file-level review cannot see the database, and a compromised WordPress site often carries injected accounts and option rows that survive every file being replaced. Stating that plainly is the difference between a report a client can act on and one that leaves them confidently wrong.

Outcome

The client received a complete map of the compromise:

  • ✅ Three backdoor files identified and located
  • ✅ Two separate intrusions distinguished from one another
  • ✅ All 1,921 WordPress core files verified byte-identical to the official release
  • ✅ Every repository plugin verified against its published version
  • ✅ Four misconfigurations documented, including debug mode and the disabled firewall layer
  • ✅ A prioritised recovery sequence, with the limits of the review stated plainly

The clean bill of health mattered as much as the findings. Confirming that every core file and every repository plugin matched its official release converts “we think we cleaned it” into “we know what was there”, and gives every future scan a baseline to compare against.

One limitation shaped the entire engagement. All file timestamps had been rewritten when the site was copied for analysis, which destroyed any possibility of timeline forensics. We could not establish when the backdoors were placed or in what order.

If you ever need a compromised site investigated, preserve modification times before copying anything. Use an archive format that retains them. Once they are gone, you lose the ability to answer the first question every client asks: how long has this been happening?

Key Lessons

Signature scanning has not been sufficient for years. A backdoor that returns a blank page and contains no recognisable strings is invisible to it. Integrity verification against known-good sources finds what pattern matching cannot.

Expired premium licences are a security problem rather than a billing one. Commercial themes and plugins receive no security updates without a valid licence, and a lapsed renewal quietly converts a maintained site into an unmaintained one.

And removing a backdoor is not the same as ending a compromise. Whoever had access could have added administrator accounts, injected database rows, or scheduled tasks that recreate the files. Until the entry point is identified, cleanup buys time rather than safety. We saw the same principle in a compromised Multisite network we investigated, where the shared layer meant a single intrusion reached every site on the install.

The clearest signal in this engagement was that the two payloads used different passwords. That is not one break-in. That is someone who came back.

Is your WordPress site behaving in ways nobody can explain?

If a site is failing in ways your security plugin cannot account for, verifying it against its official sources is usually the fastest way to find out whether the problem is a bug or something that has been sitting there for months. Webnorly can run that verification, identify what is genuinely malicious, and give you a recovery plan you can act on. Send us the site and we will tell you what is actually in it.