How to Find a Hidden WordPress Plugin Backdoor After a Supply-Chain Injection
🛡️ Security Intermediate 6 min read

How to Find a Hidden WordPress Plugin Backdoor After a Supply-Chain Injection

A backdoor plugin that hides from the Plugins screen and lives in mu-plugins will not show up in a casual audit. The log searches, disk comparisons and account checks that find it, step by step.

Published: September 20, 2026 • Updated: September 20, 2026
wordpress securityincident responsebackdoormu-plugins

The Brevo supply-chain attack on 14 September 2026 added a step that most WordPress incident checklists do not have. If a site administrator loaded any page carrying a Brevo form, chat widget or tracker while the injected loader was live, the script used their session to upload and activate a plugin called "Web Media Optimizer." According to BleepingComputer's analysis of the sample, it copies itself into the must-use plugins directory, hides from the plugin list, beacons to a command-and-control host, and carries a hardcoded key that lets the operator create an administrator session without a password. A quick glance at the Plugins screen would find nothing. This is how to look properly.

Step 1: Establish Whether You Were in the Window

Start with time, not files. Brevo's write-up puts the embedded-script injection between 16:07 and 20:30 UTC on 14 September; Sansec measured it from 16:05 to 20:12. Convert that to your local timezone and answer two questions: did this site embed any Brevo asset (the forms script, the Conversations widget or the SDK loader), and did anyone with an administrator role browse the site during those hours? Check your own habits, your team's, and any agency or contractor with admin access. A "no" to either lowers the priority but does not close the case; the same technique works for any injected script, so run the rest of the checks anyway.

Step 2: Search the Access Log for the Install Sequence

The plugin install is not a stealthy operation at the HTTP layer. It uses WordPress's own upload endpoint, which means it leaves the same footprint a human would. Sansec's recommended search, quoted directly:

> Search your access log for a `POST` to `/wp-admin/update.php?action=upload-plugin` that day, and for a `GET` to `/wp-admin/plugins.php?action=activate` shortly after.

Pull the web-server access logs for the day and grep for both strings. A legitimate administrator installing a plugin produces the same pair, so check each hit against what your team actually did. An upload you cannot account for, from an administrator's IP addressIP Address🔐A unique numerical identifier assigned to every device connected to the internet., during a window when that person was just reading the site, is the signature you are looking for. Note the exact timestamp; you will use it in the next two steps.

Step 3: Compare What the Admin Screen Shows With What Is on Disk

The Plugins screen is rendered by PHP that any active plugin can filter, which is how a backdoor hides. The disk cannot be filtered. There are two directories to compare:

  • `wp-content/plugins` — normal plugins, listed under All on the Plugins screen.
  • `wp-content/mu-plugins` — must-use plugins. Per the WordPress developer documentation, these are "automatically enabled on all sites in the installation," load "before normal plugins," do not appear in the default plugins list but "do appear in a special Must-Use section," and "will not appear in the update notifications." WordPress only loads PHP files directly inside that directory, not in subdirectories. Nobody can deactivate them from the admin interface.

That combination is why the Brevo plugin copied itself there: always on, no update nag, no deactivate link, and a section of the Plugins page most people never open. If you have overridden `WPMU_PLUGIN_DIR` in wp-config.php, check the custom path instead.

List both directories and put the result next to the admin screen. With WP-CLI, `wp plugin list --status=must-use` enumerates the must-use set, and `wp plugin list --format=json` gives the full inventory with the `file` field if you add it to `--fields`. Any file on disk that the screen does not show, any must-use plugin you did not put there, and any plugin whose install or activation date matches Step 2 is a finding. Many sites have no must-use plugins unless the host or a caching layer added one; know which is which.

Step 4: Look for Files by Modification Time

If the plugin is well hidden, the directory listing may still look plausible. Sort the contents of both plugin directories, and the web root, by modification time and inspect anything written on or after the timestamp from Step 2. Backdoors of this type often drop a second file outside the plugin folder so that deleting the plugin does not remove access. Pay particular attention to PHP files in upload directories, in the theme, and at the web root, and to any file whose timestamp is later than your last deliberate change. If your host offers a file-integrity or malware scan, run it, but do not rely on it alone.

Step 5: Assume the Administrator Accounts Are Burned

The hardcoded authentication key is the part that changes the response. It means the operator does not need to steal a password, create a new user, or reuse the original session; they can mint an administrator session at will as long as the code is present. Removing the code is what actually revokes that access, but change the passwords anyway, because you do not know what else the plugin did while it was there. Brevo's own advice is to change administrator passwords and remove any plugin installed or activated on 14 September.

Also open the Users list and look for administrator accounts you did not create, changed email addresses on existing accounts, and application passwords you do not recognise. Any of those is a second persistence path.

Step 6: Remove It Properly

Deactivating is not enough, and for a must-use plugin it is not even possible from the dashboard. Delete the plugin files from disk, from both directories and from anywhere Step 4 turned up. Then re-run Steps 3 and 4. If the plugin restored itself, something else on the site is writing it back, and you are no longer dealing with a plugin but with a compromised server. At that point the checklist in "How to Rotate Every Secret After a Server Compromise" applies in full: database credentials, salts and keys in wp-config.php, API tokens the site holds, and the hosting control panelHosting Control Panel🛡️A web application such as cPanel & WHM, Plesk, or DirectAdmin that manages a hosting server's accounts, websites, mail, and databases. It runs with root privileges and loads third-party plugins, so a flaw in the panel or a plugin can expose every customer on the server. login.

If you cannot get a clean result, restore from a backup taken before the window and repeat the log search against the restored site.

Step 7: Close the Detection Gap

The reason this attack succeeded at scale is that nobody on the customer side was watching for a change to a vendor script. Two controls would have shortened the window from hours to minutes on your own site. A `Content-Security-Policy-Report-Only` header with a reporting endpoint would have flagged the loader's request to an attacker hostname the first time it ran. A scheduled comparison of the plugin directories on disk against a known-good manifest would have flagged the new must-use file on the next run. The reasoning behind the first control, and why it detects rather than prevents, is in "Why a Third-Party Script Tag Hands Its Vendor Control of Every Page It Loads On."

What This Case Changes

The Brevo incident shows a vendor's edge configuration reaching directly into your administrator session and installing code you cannot see from the interface it compromised. The hunt above is not exotic; it is a log search, a directory listing and a timestamp sort. The change is that it now belongs on the checklist every time a vendor in your page load announces a compromise, even when your own servers were never touched.