A client asked us to look at their WordPress site because a security scanner had flagged some files as malicious. What we found was a professionally built backdoor: encrypted command-and-control traffic, a browser-based code-execution dropper being served to every visitor, and enough obfuscation to make the whole thing look, at a glance, like a legitimate plugin.
The malware was sophisticated. The way in was not. It was a payment plugin that had been pulled from the WordPress repository for an unpatched vulnerability, left running on the production site for roughly ten months, across two separate security alerts that told the owner exactly what was wrong.
This is the anatomy of that compromise, and the argument it makes on its own.
What Was on the Site
The malicious code was disguised as a WordPress plugin. Its name was a string of tech buzzwords mashed together, the kind of plausible-nonsense label that survives a quick scroll through the plugins list without raising an eyebrow. Inside that wrapper was a small, capable toolkit:
- An encrypted C2 channel. The backdoor communicated with an attacker-controlled server over AES-256, with the server's address hidden behind several layers of base64 encoding so it wouldn't show up in a plain string search.
- A remote-command backdoor. A specific, secret URL parameter turned any page request into a command it would execute, no login required. There were multiple such triggers, including an unauthenticated AJAX endpoint.
- A browser-based dropper. A JavaScript payload was served to site visitors. Rather than carry its malicious logic in the file itself, it fetched fresh code from the C2 server at runtime and ran it in the visitor's browser, then cleaned up after itself to avoid detection. In effect, the attacker could execute arbitrary code in the browser of anyone who loaded the site, and change that code at will without touching the server again.
- Decoy files. Junk and deliberately broken files padded the plugin folder to make casual inspection harder.
This is not a script kiddie's work. It is a maintained tool built to persist quietly, serve payloads to visitors, and resist a shallow look. Which makes the entry point all the more instructive.
How They Got In
The site ran an e-commerce payment gateway plugin that had, at some earlier point, been removed from the official WordPress plugin repository. Plugins get pulled from the repository for one reason: a security problem serious enough that it can't or won't be fixed. When that happens, the plugin doesn't disappear from the sites already running it. It just stops receiving updates, forever, while remaining fully installed and active.
That is the worst of both worlds: a known-vulnerable, unpatchable component, still wired into the site, still processing requests. Automated attack tools scan the entire web for exactly this signature, sites running plugin versions with public vulnerabilities, because it is the cheapest possible way in. No password to guess, no zero-day to burn. Just a door someone forgot to close.
The attacker walked through it, gained the ability to write files into the WordPress installation, and dropped the backdoor.
The Timeline Nobody Was Watching
The most sobering part of the investigation was the calendar. The compromise wasn't a lightning strike; it was the end of a long, well-documented ignored warning.
The vulnerable plugin was installed in late 2023. The following spring, the site's security scanner sent its first alert in plain language: this plugin has been removed from wordpress.org, it contains an unpatched security vulnerability, it is still installed on your site. That alert was not acted upon.
Ten months later, on an ordinary morning, a second alert repeated the same warning and added a new one: a second plugin now had a known high-severity vulnerability too. Within roughly a day of that alert, the site was compromised. A third alert fired listing eleven malicious files by name. The backdoor then ran, live, serving payloads to visitors for about a month before anyone investigated.
Every stage of this was visible in advance. The tooling did its job. The alerts were specific, correct, and early. What was missing was a person or a process to read them and act. Security software you don't monitor is a smoke detector with the battery pulled: it will chirp faithfully into an empty house while it fills with smoke.
Why This Is a WordPress-Shaped Problem
It would be easy, and wrong, to file this under "someone should have read their email." The deeper point is architectural.
WordPress's defining strength is its plugin ecosystem: tens of thousands of drop-in components that let a non-developer bolt on a store, a form, a gallery, a payment gateway. That same ecosystem is its defining liability. Every plugin is third-party code running with real privileges inside your site, on a schedule of maintenance you don't control. The moment an author walks away, a plugin becomes a permanent, unpatchable hole that keeps working exactly well enough that no one notices it should be gone. Multiply that across the half-dozen or dozen plugins a typical site accumulates, and you are running a standing attack surface that grows quietly over time and demands perpetual, disciplined vigilance just to hold steady.
And there is always a server. A WordPress site is a live application with a database, an admin login, and a PHP runtime answering every request. All of it is reachable, all of it is a target, and all of it needs patching in perpetuity. High-traffic and public-sector sites in particular are scanned and probed continuously by bots hunting for precisely the weaknesses this attacker exploited.
The Part That Didn't Get Hit
Here is the detail from this engagement that has stayed with me. This site was in the middle of a migration off WordPress. The sections we had already rebuilt on a static, serverless architecture were untouched by the attack, and not because they got lucky.
They were untouched because they present nothing a WordPress exploit can grab. There are no plugins to abandon. There is no admin login to brute-force. There is no database sitting behind the public pages, and no long-running server between requests to compromise and persist on. A statically generated page backed by serverless functions is not a smaller version of the same attack surface; it is a different shape that most of these attacks simply cannot address. The compromise landed entirely on the WordPress remainder, the one part of the system that still had a door.
That is the argument this incident makes without anyone making it. The same automated traffic that turns an abandoned WordPress plugin into a month-long breach finds nothing to do against a site that has no plugins, no login, and no server to own.
If You're Running WordPress
Migrating is a project, not a weekend, and plenty of sites will stay on WordPress for good reasons. If yours is one of them, the lessons here are concrete and cheap relative to a breach:
- Treat "removed from the repository" as an emergency, not a notification. A plugin pulled from wordpress.org for a security issue should be deactivated and deleted the same day, and a replacement found after, not before, it's gone.
- Actually route and action security alerts. Send them to more than one person, track them like tickets, and set response times by severity. An unread alert is worth nothing.
- Audit your plugins on a schedule. Once a month, check every plugin against the repository and its last update date. Anything unmaintained or missing is a candidate for removal.
- Minimize the surface. Every plugin you can live without is one you never have to patch, monitor, or worry was quietly abandoned two years ago.
- Ask whether you need the server at all. For marketing sites, storefronts, and content-driven pages, a static, serverless architecture removes most of this problem by construction rather than by vigilance.
The malware in this case was genuinely clever. But it never needed to be. It walked in through a door that had been marked "unlocked" ten months earlier, in writing, twice. The most dangerous component on a WordPress site is rarely the one under active attack. It's the one everyone forgot was still there.