The backdoor that took its orders from a blockchain: recovering a compromised WordPress portal
A news portal with nine years of archives arrived with a site that was "misconfigured". It was not misconfiguration: it was an active breach, with a backdoor that hid itself from the administrator and queried a contract on the Polygon blockchain to find out where to fetch its payload.
This case is published anonymously, without the client name or domain. It is a news portal with nine years of operation and roughly 36,000 published posts, running on its own server. The original request was simple: recover access to the admin panel and fix a site that was "misconfigured".
The audit changed the diagnosis almost immediately. It was not accidental misconfiguration. It was an active breach, with an attacker who still had server access at the moment we started looking.
How you tell a breach from a bug
The first stage was read-only — no changes to the server until we had the full picture and a backup in hand. That matters: in a compromised environment, touching things before you understand them destroys the evidence that the problem predated your arrival.
What the read-only pass found: a forged administrator account in the database, created weeks earlier; an active backdoor plugin in the WordPress plugin list; copies of the same code loose outside the plugins folder, named with timestamps; and dozens of folders with plausible generic names — analytics, security, slider, widget — all created within the same one-hour window, a clear signature of an automated mass attack.
One of those copies had been created in the early hours of the audit day itself. The attacker access was not historical: it was ongoing.
The root cause was in the log, not in the guess
The most obvious initial hypothesis was a known vulnerability in a file management plugin installed on the site. It was ruled out by verification: the installed version was current, and that plugin’s known flaw had been patched years earlier.
The real answer came from the server access log. Within seconds, a single address made several login requests while rotating the browser identifier between Chrome, Safari and Firefox — the signature of an automated script — and received a successful-login redirect response more than once. Entry by brute force straight at the WordPress login screen, against a site with no attempt limiting in place.
Ruling out the convenient hypothesis by verification rather than intuition is what separates remediating from merely cleaning what is visible.
The most serious finding: a backdoor that hid from the administrator
Among the artifacts was a fake plugin named to imitate one of the most popular WordPress security plugins. The name is not an accident: in a panel with dozens of plugins, an item called "security" is the last one anyone clicks to investigate.
That plugin injected a script obfuscated with base64 and XOR into the site pages. And it did something worth highlighting: before injecting, it checked whether the visitor was a logged-in administrator. If so, the payload was skipped. The site owner could browse their own portal all day and never see the malicious code every other visitor was served.
Decoded, the script revealed the unusual part. It did not have the malicious server address embedded in it. Instead, it queried a smart contract on a public blockchain — trying several access providers until one answered — and read from there which domain it should load the next remote script from.
Why that matters
This technique is known as EtherHiding, and it inverts the logic of defense. In a common backdoor, the attacker server address is in the code: you block the domain, or the provider takes the server down, and the malicious infrastructure dies.
When the address comes from a contract on a blockchain, the attacker can change the destination at any time without ever touching the compromised site again. And the record itself cannot be removed by third parties — there is no provider to notify, no host to take down. Blocking by domain or IP becomes a game the defense does not win. The only path is removing the injector at the site.
The plugin was removed from disk and cleared from the active plugin list in the database.
The invisible link on the home page
Another finding explained a symptom the client had already noticed without being able to name it: the description shown on Google and on social networks when sharing the home page was not the site text.
The home page content in the database had been replaced by a single link positioned off the visible area of the screen, with casino brand anchor text pointing to an external domain. Invisible to any human visitor, perfectly legible to search engines — SEO link injection, using the portal’s nine years of accumulated authority to push a third-party site.
Removed, with a review of every page on the site to confirm it was an isolated case.
291 spam posts, and why cleanup was the hard part
The attacker had also published hundreds of gambling spam posts in several languages — English, Russian, Polish, Czech, Turkish, Azerbaijani. They showed up even in the home page "most popular" widget, because bot traffic was inflating the site’s own metrics.
Cleaning that out of an archive of 36,000 legitimate posts is where the real work lives. Three traps appeared, and each one forced a change of criteria.
Filtering by date did not work: some spam posts had falsified backdated timestamps, predating the attack. Filtering by keyword was not enough either: later waves came in Cyrillic, in Polish spelling, with brand names containing none of the searched terms. And generic terms produced false positives in real content — there were legitimate articles about bands, hotels and events mentioning the word in a genuine context.
The final criterion was structural rather than textual: the sequential ID range where contamination began, cross-checked against post authorship. Every listing was written to a file and reviewed line by line before any removal — never a filter feeding directly into a mass deletion command.
Total removed: 291 posts. Zero loss of legitimate content, confirmed including the ambiguous cases mentioning casino terms in a real context. The final verification was re-run immediately before declaring completion — and it was precisely that recount which revealed a last batch of 32 posts that had escaped every previous check.
On a large database, "I checked two hours ago" is not verification. The final count has to be re-run at the moment of closing.
The fix almost nobody tests: does the protection actually protect?
With the malware removed, the root cause remained: login without attempt limiting. The site had two login protection plugins installed and active, which in most audits would be recorded as "protected, item resolved".
We tested it for real. Five incorrect login attempts in a row went through with no blocking at all. The protection was active in the panel and inert in practice — meaning that after all the cleanup, the door the attacker came through was still exactly as it had been.
The fix was made one layer down, directly at the web server: request rate limiting per address on the login route, returning a blocking response above the threshold. Validated with eight simultaneous requests, with blocking occurring as expected and legitimate login continuing to work normally.
A warning was recorded in the delivery documentation: that configuration file is generated by a server administration panel and can be overwritten if someone regenerates it through the interface. A backup of the file was saved and the caveat delivered in writing, because a protection that can disappear without notice needs to be documented.
What else was done, and what stayed open
Beyond the remediation: forged administrator account removed, database and panel passwords rotated, file permissions corrected on the server — including a configuration file writable by any process — and roughly 7.5 GB of abandoned old backups removed to relieve the disk, which was at 95% usage.
Capacity adjustments to PHP and the database fixed images that were failing to load and cut home page load time in half. They did not fix everything: the server remains undersized for the portal’s real traffic, and that was communicated as a capacity decision to be made, not as a delivered item.
One honest open item remains: a fragment of inert text still appears once in the home page footer. It is not a link, not a script, it does not redirect and it collects nothing. The investigation did not locate the origin — it is not in any site file, not in any text column of the database, not cache and not web server rewriting. It was recorded as a next-phase item, with a dedicated scanner, rather than declared resolved.
What this kind of project teaches
A compromised site rarely has one problem. It has a chain: an open door, an attacker who came through it, the persistence they installed to return, and the damage they monetized. Removing the malware without closing the door is an invitation for a second visit.
And a note on responsibility: when a site with registered users is compromised, assessing the duty to notify data subjects and the data protection authority belongs to the controller — the site owner. Our role is to report the finding clearly and in writing, document what was found and when, and not decide that on their behalf.
Scope and stack
WordPress · PHP · MySQL · nginx · Linux · WP-CLI · access log analysis · web server rate limiting · credential rotation · permission correction
