← AEU Hosting Blog

Stealthy WordPress malware hides admin via must-use plugin

2026-10-06

Stealthy WordPress malware hides admin via must-use plugin

A newly documented WordPress malware sample shows how a malicious must-use plugin, a special file that runs automatically on every WordPress request and cannot be turned off from the normal Plugins screen, can hide a rogue administrator account, harvest login passwords, and restore itself after deletion. The Wordfence Threat Intelligence Team identified the sample in mid June during a site clean. The malware uses Etherhiding, a technique that hides the location of the attacker's command servers behind a smart contract on the Ethereum blockchain, making the command channel hard to take down. Wordfence developed and released a malware detection signature after its question-and-answer review process on June 23, 2026. Wordfence Premium, Wordfence Care, and Wordfence Response customers received the signature immediately, while free versions received it after the standard 30-day delay. Wordfence says its plugin scanner detects over 99 percent of samples and indicators of compromise when using the premium signature set, and its threat intelligence database contains more than 4.4 million unique malicious samples.

Must-use plugins live outside the regular plugin system, which makes them attractive to attackers: they execute before normal plugins and are not listed in the regular Plugins screen. Wordfence reports that this particular sample was disguised as an automated health check and reporting tool, complete with a plausible author name and a link to a code repository. Across its detections, the malware appeared under more than 4,000 different filenames. The most common were legitimate-looking WordPress drop-ins such as advanced-cache.php and db.php, as well as a theme's functions.php file. The plugin's name, author, and URI fields also vary between samples, so those metadata are not reliable indicators for detection.

To avoid analysis and administrator attention, the malware uses a custom string obfuscation scheme. Unlike common WordPress malware that uses recognizable patterns like eval(base64_decode(...)), this sample keeps ordinary WordPress API calls such as add_action() and get_option() visible but hides the values passed to them, including hook names, option names, constants, and file paths, behind a custom decoder. The decoder is built around two functions: one stores a large lookup table of encoded strings, and the other retrieves and decodes a string when needed. The trick is a simple substitution cipher: two alphabets are aligned character by character, and each character in the scrambled string is replaced by the character at the same position in the real alphabet. For example, the encoded string gw|hIW|'Il decodes to get_option. Some SQL statements are also hidden by replacing individual characters with hexadecimal codes, which PHP interprets correctly at run time, so database queries execute normally; the hexadecimal sequence \x55\x50\x44\x41\x54\x45 is read by PHP as UPDATE. On top of this, the malware hooks several filters and uses additional techniques to keep itself off the WordPress dashboard. It hooks the show_advanced_plugins filter and removes the malicious plugin from the must-use plugin list before WordPress renders the page, and it also hides the plugin from the regular Plugins page, update notifications, and the Site Health screen.

Persistence starts with administrator access. The malware first checks whether it has already created an admin account by reading a username from an option stored under the key `bu`. If a target account exists, it resets that account's password and adopts its login, leaving the total user count unchanged. If no account exists, it builds a username from a pool of four prefixes, admin_, adm_, administrator_, and backup_, and appends a random six-character suffix. It then creates the account with the administrator role and a randomly generated password, and writes the username and password back into its own WordPress options named bu and bp. To prevent the rogue account from being spotted, the malware hooks three filters. The first modifies the query WordPress runs before listing users, appending a condition that excludes the hidden username. The second applies the same exclusion to the REST API using the login__not_in parameter. The third subtracts one from the displayed counts for All and Administrator user views, so the totals appear consistent with the filtered list. Because the account is hidden from both the dashboard and REST API listings, Wordfence notes that inspecting the database directly is the reliable way to see the account.

The malware also harvests plaintext administrator passwords. It hooks WordPress's authenticate filter, which fires after every login and receives the authenticated user object, the username, and the plaintext password directly from WordPress. The handler acts only on accounts with the administrator capability, and stores the plaintext password in a WordPress option named `ic`, keyed by username. This means every successful administrator login is recorded in the database in clear text, giving the attacker a steady stream of credentials.

Self-healing and self-reactivation make cleanup difficult. The malware registers its restore routine on plugins_loaded at priority 0, so it runs early on every WordPress request. A transient limits the check to once per hour. If the plugin file is missing or smaller than 5,000 bytes, the routine retrieves a stored copy from the `src` option and writes it back to disk. It then uses touch() to backdate the modification time and chmod() to set the file mode to 0444, making the restored file appear old and read-only. Separately, the plugin also writes itself back into the active_plugins option directly, bypassing WordPress's normal plugin activation process. It reads wp-config.php to obtain the database name and table prefix, builds the qualified options table name, and under a locked transaction appends itself to the active_plugins option. This mechanism applies to copies placed in the standard wp-content/plugins directory.

Finally, the malware spreads to other WordPress installations on the same server. It searches the filesystem for WordPress installations, targeting common web-server roots including /home, /var/www, /var/www/vhosts, /var/www/html, /srv/www, /srv/users, and /usr/local/www, as well as paths relative to its own location. The spread routine runs at most once every three days, controlled by a transient named sc_spread_interval. It reads its own source code and writes a copy into each accessible installation. On a shared hosting environment where multiple sites share the same server, a single infection can spread silently across all of them.

Site owners should treat this as a reminder that hidden administrator accounts and must-use plugin folders deserve regular manual review, not just dashboard glances.