Skip to main content
rfxn
//
maldetmalwarewordpresspersistence

The MU-Plugin Menace: Five Malware Families Hiding in Plain Sight

Ryan MacDonald15 min read

WordPress's must-use plugin directory has quietly become one of the most reliable persistence anchors in the modern threat landscape. In late 2024 and through 2025, several independent research teams documented a cluster of malware families exploiting the directory's unique properties to maintain long-term, stealthy access across tens of thousands of sites. What began as a handful of isolated samples is now a recognizable ecosystem with distinct families, shared TTPs, and escalating sophistication.

This article covers five families: the Session Manager backdoor (documented by Monarx), the Fake Wordfence credential harvester, the Active WordPress Core cross-site propagator, the WP Security Helper user-hiding rootkit, and the three-piece Sucuri Trio documented by Sucuri. We credit and thank those research teams for their detailed public work. This post synthesizes their findings with our own analysis, adds additional IOCs, and documents the seventeen new Linux Malware Detect signatures we published to cover these families.

wp-content/mu-plugins/Auto-execute. No admin UI. No deactivate button. Survives updates.SOPHISTICATIONSucuri Trioredirect.php, index.php, custom-js-loader.phpSEO spam, redirects, injected JS. Three files working together.SPAM / SEOSession Managerdeveloper_sessions.php (cookie-gated shell)Arbitrary PHP eval, persistent backdoor access. 20k+ sites (Monarx).BACKDOORFake Wordfencewfplugin.php (impersonates security plugin)Rogue admin creation, DB credential theft, self-healing reinstall.CRED THEFTActive WordPress Coredeveloper-ede.php (propagates to other sites on server)Cross-site propagation: scans server for other WP installs, infects them.PROPAGATORWP Security Helperwp-developer-tools.php (7-layer persistence fortress)Admin hiding, plugin cloaking, wp-cron re-infection, REST API C2.ROOTKITSHARED PROPERTIESAuto-execute on every page loadFrontend, admin, REST, cronInvisible to plugin admin screenRequires filesystem access to findSurvives plugin/theme updatesmu-plugins/ is never touchedRuns before security pluginsLoads at muplugins_loaded hookNo deactivation UI existsMust delete the file manuallyMALDET COVERAGE17 signatures across all 5 familieshex patterns + compound behavioral rules

What Are MU-Plugins, and Why Attackers Love Them#

WordPress's must-use plugin system was designed for site operators who need code to run on every page load without the possibility of accidental deactivation. Files placed in wp-content/mu-plugins/ are loaded automatically by WordPress before any ordinary plugin, before any theme, and before most hooks that security plugins use to intercept execution. They do not appear in the WordPress admin plugin list. There is no UI toggle, no deactivate button, and no warning to site administrators.

This makes the directory ideal for malware with the following properties:

Auto-execution. Every page load (frontend, admin, REST API, wp-cron) triggers the malware without any user interaction.
Admin invisibility. The plugin management screen at wp-admin/plugins.php only shows plugins under wp-content/plugins/. MU-plugins are on a separate screen most administrators never visit.
Deactivation immunity. Unlike regular plugins, MU-plugins cannot be deactivated through the UI. Removing them requires filesystem access.
Upgrade survival. Plugin and theme updates do not touch wp-content/mu-plugins/. A backdoor planted there survives all normal maintenance operations.
Early hook access. MU-plugins run before the muplugins_loaded action, giving them access to WordPress internals before most security plugins register their own hooks.

The attack surface is wider than it first appears. The directory itself is often writable by the web server process, meaning any PHP code execution vulnerability (LFI, deserialization, SQLi to file write, or a compromised plugin) can drop a file there directly. Attackers who obtain credentials via phishing or credential stuffing can use the WordPress theme/plugin editor, a file manager plugin, or the Filesystem API to write into the directory without needing a shell.

Check your MU-plugins directory now

Most legitimate WordPress installations have an empty wp-content/mu-plugins/ directory, or at most one or two files placed intentionally by the hosting provider. Any PHP file you did not place there yourself warrants immediate investigation.

The Five Families#

The families documented in this article are distinct in capability and complexity, but share a common entry point. The table below provides an at-a-glance comparison before we go deeper into each one.

FamilyPrimary FileCapabilityScaleSource
Session Managersession-manager.phpRCE, credential harvest, 7-layer persistenceWidespreadMonarx 2026
Fake WordfenceWordfenceSecurity.phpAdmin trapdoor, credential theft, exfilTargetedMonarx
Active WP Coreactive-wordpress-core.phpRCE, server-wide propagation, self-healingShared hosting riskMonarx
WP Security Helperwp-security-helper.phpShadow admin, user hiding, gaslighting3,500+ sitesMonarx
Sucuri Trioredirect.php / index.php / custom-js-loader.phpRedirect, webshell, JS injectActive campaignsSucuri Mar 2025

Session Manager: The Seven-Layer Fortress#

Monarx describes the Session Manager backdoor as the most sophisticated WordPress backdoor they have documented. That description is earned. The malware implements seven distinct persistence layers across the filesystem, database, and scheduler, any one of which can rebuild the others. Standard incident response (removing the MU-plugin file and resetting passwords) fails to eradicate it because it restores itself from whichever layers survive.

Files

The infection typically presents as three MU-plugin files, though the names may vary:

FileRole
session-manager.phpCore backdoor: RCE channels, persistence orchestration, evasion logic
wp-user-query-filter.phpUser hiding: hooks pre_get_users, hides shadow admin accounts from all surfaces
health-check.phpPersistence monitor: verifies all layers are intact, triggers restoration

The Seven Persistence Layers

1
MU-plugin auto-load. The primary infection vector. session-manager.php, wp-user-query-filter.php, and health-check.php in wp-content/mu-plugins/ load on every WordPress request before any security plugin can intercept.
2
Database backup. The full backdoor payload is serialized and stored in wp_options under the key wp_session_tokens_config. If the filesystem files are removed, the database copy can restore them. This also means the infection survives a filesystem-only backup restore that doesn't also restore the database.
3
wp-login.php credential harvester. A modified wp-login.php captures plaintext credentials for every successful login. Restored hourly via wp-cron if deleted or reverted. The patch hooks into the authentication flow before password hashing.
4
functions.php dropper stub. A small require() stub is appended to the active theme's functions.php. If all MU-plugin files are removed, WordPress loads the theme and the stub re-drops the payload. Also restored hourly.
5
Five scattered PHP clones. Full payload copies written to directories that administrators rarely inspect, disguised as core WordPress files.
6
Regular plugin fallback. A copy of the backdoor is installed as a regular plugin at wp-content/plugins/wp-session-manager/. If MU-plugins are wiped, this activates on the next page load.
7
Auto-updates disabled. WordPress's automatic update mechanism is disabled by the backdoor. This prevents security patches from reaching the host while the infection is active, extending the window of opportunity.

The five scattered clones land at paths chosen to resemble legitimate WordPress internals:

text
wp-content/uploads/wp-cache-stats.php
wp-content/languages/wp-locale-handler.php
wp-content/upgrade/wp-update-verify.php
wp-includes/fonts/font-metrics.php
wp-content/uploads/woocommerce_uploads/wc-report-handler.php

Three RCE Channels

The backdoor exposes three independent remote code execution channels, each using a different authentication mechanism and delivery method:

ChannelTriggerAuth
GET parameter?wp_debug_session=<token>16-character token (hardcoded or per-install)
Cookiewp_cache_tokenHMAC-validated, hourly rotating window
REST APIPOST /wp-json/wp/v3/session/tokenX-WP-Session header (WP has no v3 API, fabricated route)

The fabricated /wp-json/wp/v3/ namespace is a deliberate choice. WordPress currently ships v2 of its REST API; a v3 route does not exist and would not appear suspicious to a cursory inspection of API traffic. Any POST to this endpoint with the correct X-WP-Session header triggers code execution.

Evasion Behaviors

The backdoor implements active evasion that goes beyond simple obfuscation. It monitors the environment and changes behavior in response to detection signals:

Pauses all activity for six hours after detecting an admin login, reducing the chance that an active administrator will notice anomalous behavior during their session
Enters a 30-minute dormancy window when scanner activity is detected. File access patterns, known scanner user-agents, or security plugin hooks trigger the pause
Disables 15+ caching plugins to prevent cached pages from revealing its injected content or obscuring its credential harvesting
Purges all WordPress caches every 15 minutes to ensure its modified wp-login.php and injected hooks take effect immediately
Suppresses its own database keys from wp_options queries by hooking the options API before security plugins can enumerate them

Hidden Admin Accounts

The backdoor creates shadow administrator accounts and hides them from every surface where an administrator might notice them. The user-hiding logic in wp-user-query-filter.php hooks pre_get_users to exclude shadow accounts from:

The wp-admin/users.php management screen
The WordPress REST API /wp/v2/users endpoint
The ?author= rewrite (author archive pages that can enumerate user IDs)
The XML sitemap author entries
WP-CLI user list output

Known shadow account naming patterns include log_agent*, system*, and dwanw98232h13ndwa*. These accounts are only visible via direct database queries, not through any WordPress API surface.

Database Footprint

The backdoor writes to numerous wp_options keys and creates custom transients and cron events. The full set of known keys:

text
# wp_options persistent keys
_wp_session_tokens_data
wp_session_tokens_config      ← serialized payload backup
_pre_user_id
_wp_mail_queue_log
_wc_analytics_data
_wp_site_activity

# Transients (auto-expire, but actively refreshed)
_transient__wp_session_*
_transient__tds_*
_transient__wp_scatter_check

# wp-cron event
_tds_15min                    ← fires every 15 minutes, triggers persistence checks

C2 Infrastructure

Exfiltrated credentials and telemetry are sent to C2 infrastructure operating under several domains, all designed to appear as legitimate analytics services. Confirmed C2 domains include webanalytics-cdn[.]sbs, webanalytics-cdn[.]cyou, webanalytics-cdn[.]cfd, and webanalytics-cdn[.]icu. These share a common domain pattern and likely rotate across TLDs as individual domains are blocked.

Incomplete removal is ineffective

Removing only the MU-plugin files will not eradicate this infection. The database backup in wp_session_tokens_config and the functions.php dropper stub will restore the filesystem files within minutes. All seven layers must be addressed simultaneously. See the Detection & Cleanup section for the complete procedure.

Fake Wordfence: Credential Theft via Impersonation#

The Fake Wordfence family impersonates one of WordPress's most trusted security plugins to install an MU-plugin that steals administrator credentials and maintains a persistent backdoor. The file name WordfenceSecurity.php (note the PascalCase, mimicking a class file) is chosen to appear legitimate to anyone who briefly inspects the mu-plugins directory. At just 2KB, it is easy to overlook.

The malware operates in three stages that work together to grant persistent access and exfiltrate credentials:

Stage 1: Admin Trapdoor

Any GET request to any page with the parameter ?wp_login present triggers an authentication bypass. The malware computes a SHA1-based token, then calls wp_set_auth_cookie() with the shadow administrator's user ID, instantly authenticating the attacker without requiring a password. This trapdoor works on any page, not just wp-login.php.

Stage 2: Credential Harvesting

The malware hooks wp_authenticate at priority 1 (before any other plugin processes the credentials) and captures the submitted plaintext username and password. Because MU-plugins load before all other plugins, this hook fires before Wordfence, before login security plugins, and before 2FA plugins can intervene.

Stage 3: Exfiltration

Captured credentials are JSON-encoded and sent to the C2 at phnwcloud[.]qpon, endpoint /myweb3/web/saveDomains.php. The request includes the compromised site's domain, the captured credentials, and server fingerprint data from $_SERVER.

Dropper

The primary MU-plugin file (2KB) is typically dropped by a larger 22KB installer script named dd.php. The dropper sets up the shadow administrator account, writes WordfenceSecurity.php into mu-plugins, and then attempts to delete itself. In cases where the dropper is not removed, it is recoverable as a second IOC.

Delivery IPs observed in the wild: 203.27.106.243, 203.27.106.245, 203.175.14.40, 154.186.30.3, 23.247.137.197. These IPs are also used for credential collection callbacks and should be blocked at the firewall level.

Active WordPress Core: Cross-Site Contamination#

The Active WordPress Core family is the most dangerous of the five for shared hosting environments. Its distinguishing capability is a scanner that traverses the entire server filesystem looking for other WordPress installations, then propagates the infection to each one it finds. A single compromised site on a shared server can infect every other WordPress site hosted by the same customer or provider.

The malware arrives as active-wordpress-core.php (3KB). Despite being compact, it packs three major capabilities:

Server-Wide Propagation (guard.php)

The propagation component, identified by Monarx as guard.php, scans the server for wp-config.php files to enumerate WordPress installations. For each installation found, it writes the backdoor payload into wp-content/mu-plugins/. This propagation runs on a schedule and will re-infect any sites that have been cleaned, as long as the original infection on one site remains active.

The propagated requests use the User-Agent WP-System/[version] to blend in with legitimate WordPress HTTP requests. Blocking or alerting on this User-Agent string at the web server level can reveal propagation activity in access logs.

Remote Code Execution

The backdoor receives commands from C2 IPs 216.126.233.40 and 74.50.126.253 and executes them via standard PHP execution functions. Server environment data from $_SERVER is base64-encoded and included in check-in requests, giving the operator a full picture of the hosting environment.

Self-Healing

The function wp_sys_integrity_handler() monitors the backdoor's own files and restores them if deleted. As an additional countermeasure, it sets its own files to chmod 0444 (read-only for all users) after writing them, preventing web server processes from overwriting them via normal file operations. Removal requires elevated permissions.

The backdoor persists two keys in the database: wp_sys_cache_nodes_config and wp_core_guard_backup_data, analogous to Session Manager's database backup layer.

Shared hosting impact

If you are on shared hosting and you find active-wordpress-core.php in your mu-plugins directory, other sites on the same server may already be infected. Notify your hosting provider immediately. Cleaning your site alone will not prevent re-infection if any neighboring site on the same filesystem remains compromised.

WP Security Helper: Gaslighting at Scale#

With over 3,500 documented infections, WP Security Helper is the most prevalent family in this group. It takes a different approach from the others: rather than providing remote code execution, it focuses entirely on making its shadow administrator accounts undetectable. The name is deliberately chosen to impersonate a legitimate security tool. An administrator investigating suspicious activity might install it thinking it will help.

The malware is distributed as a regular plugin at wp-content/plugins/wp-security-helper/wp-security-helper.php, but its primary payload typically migrates to mu-plugins for persistence.

The Gaslighting Technique

The malware hooks three WordPress functions to make shadow admin accounts invisible from every surface administrators use to manage users:

Hook / FunctionManipulation
pre_get_usersFilters WP_User_Query results to include ONLY the currently logged-in user; every other user is invisible
get_usersSecondary hook for any direct get_users() call not caught by the query filter
wp_count_users()Overwritten to always return { administrators: 1 }; the dashboard user count widget shows "1 Administrator" regardless of actual count

The effect is comprehensive. An administrator who logs in to investigate a compromise will see only their own account in Users > All Users, will see a count of one administrator in the dashboard, and will get no results from the REST API /wp/v2/users endpoint. The shadow accounts exist and are active, they just cannot be seen.

Obfuscation

The payload is obfuscated using PHP's goto statement combined with hex and octal character encoding. The string for users.php, for example, is encoded as \165\163\x65\x72\x73\x2e\160\150\120. The goto spaghetti makes static analysis of control flow significantly harder. Despite the obfuscation, the file is small enough that manual deobfuscation is tractable once the encoding pattern is identified.

Eleven delivery IPs have been documented: 216.229.112.25, 216.26.255.231, 216.26.255.180, 216.26.249.120, 213.232.122.23, 209.50.186.38, 194.99.27.172, 185.202.108.240, 185.202.108.233, 178.20.31.191, and 170.168.97.108.

The Sucuri Trio: Simple but Effective#

In March 2025, Sucuri documented three separate MU-plugin malware files being distributed in active campaigns. Unlike the Monarx-documented families, these are relatively simple in construction, but their placement in mu-plugins ensures they execute on every page load and survive standard remediation that targets only themes and plugins.

redirect.php: Visitor Hijacking

A redirect malware that intercepts visitors and sends them to attacker-controlled destinations. The redirection logic includes bot detection to avoid triggering against web crawlers and security scanners. Only real human visitors are redirected, which helps the infection persist longer without detection. C2 updates for redirect targets come from updatesnow[.]net.

index.php: Remote Webshell Fetcher

Rather than containing the webshell payload inline, this file fetches it at runtime from an external source. The payload is pulled from a GitHub repository at starkvps99812/upd/, filename BypassBest.php. Using GitHub as a payload host makes the fetch traffic look legitimate in network logs and allows the attacker to update the payload without modifying the files on the compromised server.

custom-js-loader.php: JavaScript Injection

Injects attacker-controlled JavaScript into every page on the site. The JavaScript source is loaded from imagex1[.]sx[.]cdn[.]live. Injected JavaScript is typically used for skimming, credential theft via fake login overlays, cryptocurrency mining, or drive-by exploit delivery.

The three files are often deployed together in the same campaign, serving different objectives: redirect.php monetizes traffic immediately, custom-js-loader.php enables browser-level attacks and longer-term data theft, and index.php provides the operator with on-demand server access.

maldet Signatures#

We have published seventeen new signatures in Linux Malware Detect (maldet) covering these five families. The signatures are organized into three tiers: high-confidence hex patterns that match unique IOC strings, compound rules that require both an identifier and malicious behavior, and exact SHA256 file hashes for the known samples with published hashes.

Hex Signatures (9): Standalone High-Confidence IOCs

These patterns match unique strings that have no legitimate use in WordPress code. A single match is sufficient to flag a file.

SignatureFamilyDetection Basis
php.backdoor.sessionmgr.fakeapiSession Managerwp/v3/session/token, fabricated REST namespace (WP has no v3 API)
php.backdoor.fakewordfence.c2Fake Wordfencephnwcloud C2 domain literal
php.inject.mupredirect.c2Sucuri Trioupdatesnow.net C2 domain literal
php.webshell.mupfetch.ghuserSucuri Triostarkvps99812 GitHub actor name
php.inject.mupjsload.c2Sucuri Trioimagex1...cdn.live JS injection C2

Compound Signatures (8): Identifier + Malicious Behavior

These rules require two or more conditions to match, reducing false positive risk while catching obfuscated or partially cleaned variants that still carry diagnostic artifacts. For a walkthrough of the compound signature language itself, see Compound Signatures: Building a Boolean Detection Language in Bash.

SignatureFamilyLogic
php.backdoor.sessionmgr.dbgsessSession Managerwp_debug_session AND (eval OR base64_decode OR system OR shell_exec)
php.backdoor.sessionmgr.c2Session Managerwebanalytics-cdn AND (eval OR base64_decode OR curl_exec OR file_get_contents)
php.backdoor.sessionmgr.deployerSession Manager__mu_deployer__ AND (eval OR base64_decode OR file_put_contents OR system)
php.backdoor.sessionmgr.dbbackupSession Managerwp_session_tokens_config AND (eval OR base64_decode OR system OR shell_exec)
php.backdoor.sessionmgr.variantSession Manager(wp_debug_session OR wp_cache_token) AND (wp_session_tokens_config OR _tds_15min) AND (eval OR base64_decode OR system)
php.backdoor.fakewordfence.credtheftFake WordfenceWordfenceSecurity AND wp_authenticate AND (wp_set_auth_cookie OR json_encode OR phnwcloud)
php.backdoor.wpactivecore.persistActive WP Corewp_sys_integrity_handler AND (eval OR base64_decode OR file_put_contents OR system)
php.backdoor.wpactivecore.dbkeyActive WP Corewp_core_guard_backup_data AND (eval OR base64_decode OR file_put_contents OR system)

SHA256 Hash Signatures (4): Exact File Matches

For the three families where Monarx published SHA256 hashes, we have added exact-match signatures. These cover the samples as documented at time of publication; variants will not match but the compound signatures above provide behavioral coverage for those cases.

SignatureSHA256
php.backdoor.fakewordfencec8657a45afd3f8ed650f1c18123d89c8b7c5cfa674e069b3230bd821f13f993a
php.backdoor.fakewordfence.dropper9a9fdd4a1546f381f0bea27cc36690833161be4869d935ce275ed64ab8418eb6
php.backdoor.wpactivecorea2ce35620fc9c81444c194168f8d9bdcfee2ea64b14167292ee007aa3a1ede2a
php.backdoor.wpsechelper0a26e477951896659dbc5b0b18929995303a9ab4e071288b40691e0b366b96a1

maldet users running maldet --update-sigs will receive all seventeen signatures automatically. For an immediate scan of a potentially compromised WordPress installation:

bash
maldet --update-sigs
maldet --scan-all /var/www/html/wp-content/mu-plugins/
maldet --scan-all /var/www/html/wp-content/

Detection & Cleanup#

Effective remediation requires checking all persistence layers, not just the most obvious ones. For Session Manager in particular, removing only the MU-plugin files will result in re-infection within minutes. The following procedure addresses all five families.

Step 1: Find PHP in MU-Plugins

bash
# List all PHP files in mu-plugins with details
find wp-content/mu-plugins/ -name '*.php' -ls

# Any PHP file you did not place there yourself is a compromise indicator.
# Legitimate hosting providers occasionally place files here, but they should
# be documented. When in doubt, compare against your hosting provider's documentation.

Step 2: Check Scattered Persistence (Session Manager)

bash
# Directories that should contain only legitimate WordPress core files
find wp-includes/fonts/ wp-includes/css/      wp-content/languages/ wp-content/upgrade/      wp-content/uploads/ -name '*.php' -ls

# Known Session Manager drop paths:
# wp-content/uploads/wp-cache-stats.php
# wp-content/languages/wp-locale-handler.php
# wp-content/upgrade/wp-update-verify.php
# wp-includes/fonts/font-metrics.php
# wp-content/uploads/woocommerce_uploads/wc-report-handler.php

Step 3: Check wp-login.php

bash
# Session Manager patches wp-login.php; compare against a known-good copy
# Option A: checksum against your WP version
md5sum wp-login.php

# Option B: look for credential capture patterns
grep -n 'wp_authenticate|$_POST[.log_pass.]|file_put_contents' wp-login.php

# If any hits return, restore wp-login.php from a fresh WordPress download:
# https://wordpress.org/download/

Step 4: Check functions.php

bash
# Session Manager appends a dropper stub to the active theme's functions.php
# The stub will be a require() or include() pointing to the mu-plugin payload
THEME_DIR=$(wp option get template 2>/dev/null)
tail -20 "wp-content/themes/${THEME_DIR}/functions.php"

# Look for require/include lines that were not there before.
# If found, remove the added lines and save.

Step 5: Database Audit

sql
-- Check for backdoor persistence keys in wp_options
SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE option_name IN (
    'wp_session_tokens_config',
    '_wp_session_tokens_data',
    '_pre_user_id',
    'wp_sys_cache_nodes_config',
    'wp_core_guard_backup_data',
    '_wp_mail_queue_log',
    '_wc_analytics_data',
    '_wp_site_activity'
);

-- Check for transient artifacts
SELECT option_name FROM wp_options
WHERE option_name LIKE '_transient__wp_session_%'
   OR option_name LIKE '_transient__tds_%'
   OR option_name LIKE '_transient__wp_scatter_%';

-- Check for shadow admin accounts
SELECT ID, user_login, user_email, user_registered
FROM wp_users
WHERE user_login LIKE 'log_agent%'
   OR user_login LIKE 'system%'
   OR user_login LIKE 'dwanw%';

-- Verify user count is consistent
SELECT COUNT(*) FROM wp_users;
SELECT COUNT(*) FROM wp_usermeta
WHERE meta_key = 'wp_capabilities'
  AND meta_value LIKE '%administrator%';

Step 6: Check Cron

bash
# Check for backdoor cron events (requires WP-CLI)
wp cron event list | grep -E 'tds|session|integrity'

# If _tds_15min or similar events appear, they will re-trigger persistence.
# Delete them:
wp cron event delete _tds_15min

Step 7: Run maldet

bash
# Update signatures to include the 17 new MU-plugin family sigs
maldet --update-sigs

# Scan the full wp-content directory
maldet --scan-all /var/www/html/wp-content/

# For a broader scan including wp-includes (for scattered clones)
maldet --scan-all /var/www/html/

Session Manager: Complete Eradication Checklist

Remove all three MU-plugin files (session-manager.php, wp-user-query-filter.php, health-check.php)
Remove all five scattered clone files from uploads/, languages/, upgrade/, fonts/
Delete the regular plugin at wp-content/plugins/wp-session-manager/ if present
Delete all backdoor wp_options keys listed in Step 5
Delete all _transient__tds_* and _transient__wp_session_* transients
Delete the _tds_15min cron event
Remove all shadow admin accounts (log_agent*, system*, dwanw* patterns)
Restore wp-login.php from a clean WordPress download
Remove the dropper stub from the active theme's functions.php
Re-enable WordPress automatic updates (disabled by the backdoor)
Rotate ALL WordPress admin passwords (all were likely captured)
Rotate database credentials and secret keys/salts in wp-config.php

Indicators of Compromise#

File Hashes

text
# Fake Wordfence: primary MU-plugin file (2KB)
SHA256: c8657a45afd3f8ed650f1c18123d89c8b7c5cfa674e069b3230bd821f13f993a
File:   wp-content/mu-plugins/WordfenceSecurity.php

# Fake Wordfence: dropper (22KB)
SHA256: 9a9fdd4a1546f381f0bea27cc36690833161be4869d935ce275ed64ab8418eb6
File:   dd.php (location varies)

# Active WordPress Core (3KB)
SHA256: a2ce35620fc9c81444c194168f8d9bdcfee2ea64b14167292ee007aa3a1ede2a
File:   wp-content/mu-plugins/active-wordpress-core.php

# WP Security Helper
SHA256: 0a26e477951896659dbc5b0b18929995303a9ab4e071288b40691e0b366b96a1
File:   wp-content/plugins/wp-security-helper/wp-security-helper.php

C2 Domains

text
# Session Manager C2 (multiple TLD variants, likely rotating)
webanalytics-cdn[.]sbs
webanalytics-cdn[.]cyou
webanalytics-cdn[.]cfd
webanalytics-cdn[.]icu

# Fake Wordfence credential exfiltration
phnwcloud[.]qpon               endpoint: /myweb3/web/saveDomains.php

# Sucuri Trio: redirect C2
updatesnow[.]net

# Sucuri Trio: JS injection source
imagex1[.]sx[.]cdn[.]live

# Sucuri Trio: webshell payload host (GitHub)
github.com/starkvps99812/upd/  payload: BypassBest.php

Network Infrastructure IPs

text
# Fake Wordfence delivery / C2
203.27.106.243
203.27.106.245
203.175.14.40
154.186.30.3
23.247.137.197

# Active WordPress Core C2
216.126.233.40
74.50.126.253

# WP Security Helper delivery
216.229.112.25
216.26.255.231
216.26.255.180
216.26.249.120
213.232.122.23
209.50.186.38
194.99.27.172
185.202.108.240
185.202.108.233
178.20.31.191
170.168.97.108

Database Keys

text
# Session Manager (wp_options)
wp_session_tokens_config       ← serialized payload backup
_wp_session_tokens_data
_pre_user_id
_wp_mail_queue_log
_wc_analytics_data
_wp_site_activity

# Session Manager (transients)
_transient__wp_session_*
_transient__tds_*
_transient__wp_scatter_check

# Session Manager (cron)
_tds_15min

# Active WordPress Core (wp_options)
wp_sys_cache_nodes_config
wp_core_guard_backup_data

User-Agent Strings

text
# Active WordPress Core propagation (guard.php)
WP-System/[version]            ← appears in access logs during cross-site propagation

Shadow Account Patterns

text
# Session Manager shadow admin username patterns
log_agent*
system*
dwanw98232h13ndwa*

# These accounts are invisible through the WordPress admin UI when the infection is active.
# Query the database directly (see Step 5 above) to detect them.

Filesystem Patterns

text
# Known malicious MU-plugin filenames
wp-content/mu-plugins/session-manager.php
wp-content/mu-plugins/wp-user-query-filter.php
wp-content/mu-plugins/health-check.php
wp-content/mu-plugins/WordfenceSecurity.php
wp-content/mu-plugins/active-wordpress-core.php
wp-content/mu-plugins/redirect.php
wp-content/mu-plugins/index.php
wp-content/mu-plugins/custom-js-loader.php

# Session Manager scattered clones
wp-content/uploads/wp-cache-stats.php
wp-content/languages/wp-locale-handler.php
wp-content/upgrade/wp-update-verify.php
wp-includes/fonts/font-metrics.php
wp-content/uploads/woocommerce_uploads/wc-report-handler.php

# Session Manager regular plugin fallback
wp-content/plugins/wp-session-manager/

# WP Security Helper (standard plugin location)
wp-content/plugins/wp-security-helper/wp-security-helper.php

Conclusion#

The MU-plugin vector is not new, but it has matured significantly. What started as a convenient persistence location for simple redirect malware is now home to multi-layer backdoors with active evasion, credential harvesting, and cross-site propagation capabilities. The Session Manager family in particular represents a qualitative step up in WordPress threat sophistication. Its combination of seven restoration paths and active scanner detection makes it genuinely difficult to eradicate without a systematic, layered approach.

Three properties make this threat category persistently dangerous. First, the attack surface is broad: any PHP execution vulnerability, any compromised credential, and any file-write capability can plant an MU-plugin. Second, standard remediation workflows (plugin reinstall, theme reinstall, password reset) are insufficient when the malware has seeded the database, patched wp-login.php, and placed clones across the filesystem. Third, the UI-invisible property means administrators may not know they are infected, especially for WP Security Helper, which actively manipulates the user management interface to prevent discovery.

Preventive Controls

Restrict filesystem write permissions on wp-content/mu-plugins/ so the web server process cannot write to this directory under normal operation
Enable WordPress automatic updates. The Session Manager backdoor explicitly disables them; a fresh infection may have already turned this off
Use file integrity monitoring that includes mu-plugins/. Any new PHP file there should trigger an alert
Enforce 2FA for all administrator accounts. Credential harvesting from these families feeds into future access attempts
On shared hosting: verify that your account cannot read or write to adjacent accounts' wp-content/ directories

Detection

Run maldet --update-sigs && maldet --scan-all /path/to/wp-content/. Seventeen signatures now cover all five families
Check wp-content/mu-plugins/ for any PHP files. Most sites have none; any unexpected file warrants investigation
Query wp_options directly for the database keys listed in the IOC section. The WordPress admin interface may be hiding them
Verify wp-login.php matches a known-clean copy for your WordPress version. Credential harvesting patches are invisible to the admin UI
Check for shadow admin accounts via direct database query, not through the WordPress admin interface

The maldet signatures referenced in this post are open source under the GPL v2 license and ship with Linux Malware Detect. Research credit for the original family documentation goes to Monarx and Sucuri. If you have additional samples, variant hashes, or new IOCs for any of these families, reach out via Keybase or email.