What the Essential Plugin backdoor teaches about who has access to your site’s code

The changelog for version 2.6.7 read, in full, “Check compatibility with WordPress version 6.8.2.” It shipped on August 8, 2025. In the same release, according to a teardown published April 9, 2026 by Anchor Hosting, the plugin’s class-anylc-admin.php grew “from 473 to 664 lines”, adding “191 lines of code, including a PHP deserialization backdoor.”

Then nothing happened for eight months.

Anyone auditing that update the week it landed would have compared a compatibility note against a diff, in a plugin they’d trusted for years, published by a vendor with a decade of history on WordPress.org. Nothing about the release announced itself. The site owners who eventually got hit had already done the thing everyone tells them to do: they kept their plugins updated. If you sell a WordPress plugin or log into other people’s sites for a living, the part of this story worth your time is who was allowed to publish code, and for how long.

A decade-old plugin vendor sold, and the buyer’s first commit was a backdoor

Patchstack’s disclosure, published April 15, 2026, lays out the ownership chain. “The vendor has been developing plugins since 2015,” it notes. “However, in 2025, the company was sold to a buyer named ‘Kris’ in Flippa. After the acquisition, the first commit from the new owner was the plantation of the backdoor across all the plugins.”

The sale carried WordPress.org commit rights with it. No user was asked, and no user was told: from every site’s point of view, the same plugin kept updating from the same place.

The dormancy ended on April 5–6, 2026. Anchor Hosting’s teardown puts the live injection window “between 04:22 and 11:06 UTC” on April 6, roughly six hours and forty-four minutes. TechRetry’s April 16 write-up describes the payload as “Injected ~6KB PHP code into wp-config.php” that served “hidden SEO spam to Googlebot,” and counts “20,000+ websites actively infected” across 31 plugins.

WordPress.org’s Plugins Review team responded on April 7. Patchstack: “They closed all plugins fully from wp.org to prevent more sites from getting infected.” That closure is still visible today. The listing for WP Logo Showcase Responsive Slider and Carousel, one of the largest in the portfolio, reads: “This plugin has been closed as of April 7, 2026 and is not available for download. This closure is permanent. Reason: Security Issue.”

The plugins that were closed

Patchstack names 22 affected plugins. The biggest, by its published install counts:

PluginActive installs
WP Logo Showcase Responsive Slider and Carousel30k+
Popup Maker and Popup Anything30k+
Countdown Timer Ultimate20k+
WP Responsive Recent Post Slider20k+
WP News and Scrolling Widgets10k+
WP Slick Slider and Image Carousel10k+
Album and Image Gallery Plus Lightbox9k+
Testimonial Grid and Testimonial Slider9k+
WP Blog and Widgets8k+

Where the disclosures diverge, and what the forced update missed

The two disclosures disagree on one date. Patchstack places the malicious commit in “September 2025”; Anchor Hosting ties it to version 2.6.7 on August 8, 2025. They were written from different evidence, a repository history versus a shipped release. The eight-month dormancy holds either way.

The forced update did not finish the job. Anchor Hosting reports that the forced update to v2.6.9.1 “neutralized the phone-home mechanism in the plugin” but left the code injected into wp-config.php untouched, “which persisted until manual remediation.” A site that auto-updated its way out of the plugin problem could still be serving spam from a file the plugin never owned.

A team watching a CVE feed had nothing to react to. Neither the Patchstack disclosure nor the independent teardowns cited here carry a CVE identifier for this backdoor. If your process for “is anything we run compromised” is a vulnerability feed and a scanner signature, this incident produced no row for it to match on until the plugins were already closed. The signal that existed first was a plugin changing owners, which no feed tracks.

The mechanism was an unauthenticated endpoint calling unserialize()

Nothing on the site had to be misconfigured for this to work, and the mechanism shows why.

The plugin registered a REST route with no authentication at all. Patchstack quotes the registration as register_rest_route carrying 'permission_callback' => '__return_true', // No authentication. Anyone on the internet could call it. That handler reached out to a server the new owner controlled, and, in Patchstack’s words, “the content from the output of the fetch request to $this->analytics_endpoint is sent to the unserialize() function.”

Handing attacker-controlled input to PHP’s unserialize() is the whole attack. The remote server chooses what object gets reconstructed inside the site’s own PHP process, and reconstruction can trigger code the attacker picked. There was no password to guess and no permission to escalate. The plugin had already been installed, by the site owner, on purpose.

The failure underneath is standing access nobody re-checked

Strip out the specifics and what remains is an access-control failure. Someone held the right to publish code to every site running any of those plugins, at least 180,000 of them, adding up the install floors Patchstack publishes for the 22 it names. That right transferred in a private transaction, and nothing in the system re-evaluated it, because nothing in the system was designed to. Commit access, once granted, is a fact about the world until a human changes it.

That’s the same failure mode as the support hire who left in March and still has an Administrator login on eleven customer sites, or a plugin that mints itself a non-expiring Application Password as soon as a support panel opens. Different blast radius, identical root: access that was granted once, for a reason that made sense at the time, and then never re-asked.

The Essential Plugin case is ownership-transfer risk, which changes what you do about it. The people who could have caught it early weren’t watching a login screen; they’d have been watching for a plugin’s maintainer changing, or reading diffs on updates. That’s a different practice from managing who logs into your customers’ sites, and it needs its own habit.

Granting a developer WordPress access without leaving a door open

The everyday version of this problem is smaller and much more common: a contractor needs to get into a site, and the fastest thing to hand over is the admin login everyone already shares. These rules cover almost all of it.

  1. Create a named account for the individual. Never share an existing login, and never create “developer” as a shared identity. If two people use one account, your logs record neither of them.
  2. Grant the lowest role that lets them finish the work. A designer editing templates is not an Administrator. Only work that installs plugins or changes site settings needs Administrator, and that’s a smaller share of tickets than the default suggests.
  3. Keep hosting access separate from WordPress access. SFTP, SSH, database, and control-panel credentials are their own grants, with their own removal step. A developer who needs to edit a theme file does not automatically need the hosting account.
  4. Set a removal date when you create the account, not when the work ends. “When the project’s done” is not a date, and projects don’t announce their completion to your Users screen.
  5. Never send credentials through a support ticket, chat, or email. A password pasted into a thread lives in the help desk database, the recipient’s inbox, and any forward either of them sent.
  6. Check the account after the work is finished, not before. The last step of the project is opening Users and confirming the account is gone, rather than marking a task complete and trusting that it was.

Reviewing your own access chain on a schedule

If you sell a WordPress plugin or theme, you have a second access chain most teams have never inventoried: the one that can ship code to your customers. Put a calendar reminder on a quarter and answer these in writing.

  • Who can commit to your WordPress.org SVN repository? List the wordpress.org usernames, not the people you assume they map to.
  • Who has write or admin access to your GitHub or GitLab organization? Include deploy keys, machine users, and personal access tokens, which outlive the people who made them.
  • Which CI or deployment services hold a credential that can publish a release? A token in a build pipeline is commit access wearing a different hat.
  • Who has Administrator on your own site, the one that sells licenses and serves updates? Former contractors are the usual finding here.
  • What Application Passwords exist across those accounts? They don’t expire on their own; someone has to look.
  • Who has hosting, SFTP, or DNS access? A DNS record is an update server.
  • Which third-party integrations hold API keys with write scope? Every one is a person’s access delegated to a service.
  • For each name on the lists above: is this person still doing the work that access was for? If the answer takes longer than a second, that’s the finding.

The output is a list of names, and for each name, a yes or a no.

Where this connects to support access, and where it doesn’t

TrustedLogin doesn’t solve the problem in this incident. No support-access tool does. If someone acquires your plugin and pushes a malicious release, the failure happened upstream of every login on the site, and the checklist above is the only real defense.

What it does solve is the other half of the same root cause: the standing admin access your own support team accumulates across customer sites. In that flow, the customer’s site creates a scoped WordPress account when they click Grant Access inside your plugin, that account is deleted on schedule by the site’s own WP-Cron whether or not TrustedLogin’s servers are reachable, the customer can revoke it from their own Users screen at any time, and there’s a record of who logged into which site and when (plans and what’s in each). The exchange behind it is drawn out step by step on how it works. The access ends without anyone remembering to end it, which is the specific thing that failed here, applied to the part of the chain support controls.

Two chains, one habit: nobody keeps access they aren’t currently using. Start with the one you can fix this afternoon: what happens when a plugin’s Help & Support button grants itself admin. Then run the review above on your release pipeline.

Similar Posts