WordPress SC: Self-Healing Backdoor Mesh Hunt

Threat Overview

Sucuri published a September 30, 2026 analysis of a WordPress compromise in which a backdoor family labeled “SC” survived removal by storing mutually restoring copies across at least eight locations. The recovered mesh used .user.ini/auto_prepend_file, a visible shim and hidden dot-file loader, wp-content/db.php, wp-content/advanced-cache.php, an injected theme functions.php, duplicate normal and must-use plugins, an encoded database option, System V shared memory, WordPress/system cron, and in related variants database triggers. Any surviving component could recreate the others on the next request or scheduled execution.

Sucuri observed the backdoor hiding itself from plugin and user views, generating a hidden administrator and authentication cookies, removing security plugins, fingerprinting the site, collecting administrator session tokens, fetching additional PHP or front-end JavaScript, and communicating through roughly twenty public Ethereum RPC gateways to obtain command-and-control instructions. On ecommerce sites, the returned JavaScript could support checkout skimming. The precise initial-access vector, campaign scale, affected WordPress/plugin versions, actor identity, smart contract, and full RPC endpoint list were not disclosed; those unknowns must not be inferred from the persistence analysis.

References

Impacted Systems

  • Vendor/product: Self-managed WordPress sites on Linux/PHP; the observed compromise is malware, not a vulnerability assigned to a specific WordPress version.
  • Components: PHP-FPM/web runtime; wp-content drop-ins, plugins, must-use plugins, active theme, WordPress options/cron/users tables, and potentially System V shared memory and database triggers.
  • Configuration dependencies: .user.ini, php.ini, or .htaccess must permit auto_prepend_file for that branch; WP_CACHE enables the early advanced-cache.php load path; System V shared memory must be available for the RAM-resident copy. Other mesh components can remain viable when one dependency is absent.
  • Exposure/prerequisite: The site is already compromised. The original entry point and required vulnerable plugin/theme/version are unknown.
  • Observed examples: wp-content/db.php, wp-content/advanced-cache.php, an active theme’s functions.php, duplicate fake plugins in wp-content/mu-plugins and wp-content/plugins, random hexadecimal ZIP restore bundles, and a hidden dot-prefixed PHP loader. Example names such as c1b12371.php and hyper-engine-kit.php varied by site and are not universal.
  • Fixed/unaffected versions: No version-based fix or explicitly unaffected WordPress release was identified. Remediation requires coordinated eradication and closure of the unknown original entry path.

Why this matters

The malware defeats file-only cleanup and can persist outside the filesystem. It combines redundant persistence, hidden administrative access, session-token collection, security-control deletion, remote payload delivery, and potentially payment skimming. These behaviors are material to MSSP customers hosting public WordPress sites even though broad campaign prevalence is not yet established.

Exploitation Status

Confirmed malware observed during an incident cleanup; initial exploitation and prevalence unknown. Sucuri directly analyzed the recovered components and regeneration behavior. There is no disclosed CVE, exploit request path, attributable actor, public hash set, or evidence that every WordPress site is exposed. Filename examples are variable; structural behaviors and paired persistence locations are more reliable than exact names.

What this hunt looks for

High-risk WordPress drop-in changes, paired normal and must-use plugins, PHP prepend configuration, random restore ZIPs, PHP-to-shell execution, rare web-process egress, shared-memory and cron interaction, rapid file regeneration, and database or hidden-admin artifacts.

Required logs

Microsoft Defender for Endpoint or equivalent Linux telemetry providing DeviceProcessEvents, DeviceFileEvents, and DeviceNetworkEvents; web/PHP and Linux Syslog; database audit logs; and firewall or proxy telemetry for web-server egress.

Hunt 1 — High-risk WordPress drop-in and theme file changes

let lookback = 30d;
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where ActionType in~ ("FileCreated", "FileModified", "FileRenamed", "Created", "Modified", "Renamed")
| where (FolderPath endswith "/wp-content" and FileName in~ ("db.php", "advanced-cache.php")) or (FolderPath has "/wp-content/themes/" and FileName =~ "functions.php")
| project Timestamp, DeviceName, ActionType, FolderPath, FileName, SHA256, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 2 — Same PHP payload written into normal and must-use plugin trees

let lookback = 30d;
let pluginWrites = DeviceFileEvents
| where Timestamp >= ago(lookback)
| where ActionType in~ ("FileCreated", "FileModified", "FileRenamed", "Created", "Modified", "Renamed")
| where FileName endswith ".php"
| where FolderPath has "/wp-content/plugins/" or FolderPath has "/wp-content/mu-plugins/"
| where isnotempty(SHA256)
| extend Tree = iff(FolderPath has "/wp-content/mu-plugins/", "mu-plugins", "plugins");
pluginWrites
| summarize FirstSeen=min(Timestamp), LastSeen=max(Timestamp), Trees=make_set(Tree), Paths=make_set(strcat(FolderPath, "/", FileName), 20), Initiators=make_set(InitiatingProcessFileName, 10) by DeviceName, SHA256
| where array_length(Trees) > 1
| order by LastSeen desc

Hunt 3 — PHP prepend configuration created or changed under a web root

let lookback = 30d;
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where FileName in~ (".user.ini", "php.ini", ".htaccess")
| where FolderPath has_any ("/var/www/", "/srv/www/", "/htdocs/", "/public_html/")
| where ActionType in~ ("FileCreated", "FileModified", "FileRenamed", "Created", "Modified", "Renamed")
| project Timestamp, DeviceName, ActionType, FolderPath, FileName, SHA256, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 4 — Commands writing auto_prepend_file or hidden PHP loaders

let lookback = 30d;
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| extend Cmd = tolower(ProcessCommandLine)
| where Cmd has "auto_prepend_file" or (Cmd matches regex @"wp-content/\.[a-z0-9_-]+\.php" and Cmd has_any ("printf", "echo", "tee", "sed", "cp", "mv"))
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 5 — Random hexadecimal ZIP restore bundles in WordPress paths

let lookback = 30d;
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where ActionType in~ ("FileCreated", "FileModified", "FileRenamed", "Created", "Modified", "Renamed")
| where FolderPath has_any ("/wp-content/", "/wp-content/uploads/", "/wp-content/themes/")
| where FileName matches regex @"(?i)^[0-9a-f]{8,}\.zip$"
| project Timestamp, DeviceName, ActionType, FolderPath, FileName, FileSize, SHA256, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 6 — PHP or web-server process launching a native shell

let lookback = 30d;
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| where FileName in~ ("sh", "bash", "dash", "zsh", "ksh")
| where InitiatingProcessFileName in~ ("php", "php-fpm", "apache2", "httpd", "nginx") or InitiatingProcessCommandLine has_any ("wp-cron.php", "wp-content", "wordpress")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 7 — Rare outbound destinations from PHP or web-server processes

let baseline = 30d;
let recent = 2d;
let known = DeviceNetworkEvents
| where Timestamp between (ago(baseline) .. ago(recent))
| where InitiatingProcessFileName in~ ("php", "php-fpm", "apache2", "httpd", "nginx")
| summarize by DeviceId, RemoteIP, RemoteUrl;
DeviceNetworkEvents
| where Timestamp >= ago(recent)
| where InitiatingProcessFileName in~ ("php", "php-fpm", "apache2", "httpd", "nginx")
| summarize FirstSeen=min(Timestamp), LastSeen=max(Timestamp), Connections=count(), Ports=make_set(RemotePort, 20), Processes=make_set(InitiatingProcessFileName, 10), Commands=make_set(InitiatingProcessCommandLine, 10) by DeviceId, DeviceName, RemoteIP, RemoteUrl
| join kind=leftanti known on DeviceId, RemoteIP, RemoteUrl
| order by LastSeen desc

Hunt 8 — Shared-memory inspection and WordPress cron redeployment behavior

let lookback = 30d;
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| extend Cmd = tolower(ProcessCommandLine)
| where (FileName in~ ("ipcs", "ipcrm") or Cmd has_any ("shmop_", "sysvshm", "wp-cron.php", "wp cron event run"))
| where AccountName in~ ("www-data", "apache", "nginx") or InitiatingProcessFileName in~ ("php", "php-fpm", "cron", "crond", "apache2", "httpd", "nginx")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 9 — Rapid regeneration of high-risk WordPress persistence files

let lookback = 30d;
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where ActionType in~ ("FileCreated", "FileModified", "FileDeleted", "Created", "Modified", "Deleted")
| where (FolderPath endswith "/wp-content" and FileName in~ ("db.php", "advanced-cache.php")) or FolderPath has "/wp-content/mu-plugins/" or FileName in~ (".user.ini", ".htaccess")
| summarize Events=count(), Actions=make_set(ActionType), FirstSeen=min(Timestamp), LastSeen=max(Timestamp), Initiators=make_set(InitiatingProcessFileName, 20), Hashes=make_set(SHA256, 20) by bin(Timestamp, 1h), DeviceName, FolderPath, FileName
| where Events >= 3 and array_length(Actions) >= 2
| order by LastSeen desc

Hunt 10 — Database, hidden-admin, and SC marker artifacts in Syslog/audit data

let lookback = 90d;
Syslog
| where TimeGenerated >= ago(lookback)
| where SyslogMessage has_any ("SC_AUTO_PREPEND", "SC_", "auto_prepend_file", "advanced-cache.php", "hyper-engine-kit", "wp_capabilities", "information_schema.TRIGGERS")
| where SyslogMessage has_any ("wp_options", "wp_users", "wp_usermeta", "INSERT", "UPDATE", "CREATE TRIGGER", "mu-plugins", "gzip", "base64")
| project TimeGenerated, Computer, HostName, Facility, SeverityLevel, ProcessName, SyslogMessage
| order by TimeGenerated desc

Detection Notes

  • Hunts 1, 2, 4, 6, and 9 are the strongest behavioral starting points. Legitimate cache plugins use advanced-cache.php, database plugins use db.php, and administrators modify themes; prioritize unexpected initiators, paired paths, hashes, and rapid recreation.
  • The published filenames c1b12371.php and hyper-engine-kit.php are examples from one site and may change. This package uses structural locations and behavior rather than relying on those names except as a low-cost Syslog clue.
  • Hunt 7 will include legitimate WordPress update, payment, analytics, CDN, and API traffic. Establish an allowlist and investigate novel destinations associated with simultaneous file or account changes. Public Ethereum RPC use is suspicious only when it is not an approved application dependency.
  • DeviceFileEvents does not expose file contents. It cannot validate SC markers, gzip/base64 blobs, or malicious code in a legitimate filename without a corresponding hash or external content inspection.
  • Shared-memory payloads, database options/triggers, and forged cookies are invisible unless audit/content telemetry is collected. Standard web access logs may show only normal-looking requests because the backdoor runs before WordPress completes loading.
  • Hunt 9 accepts both Defender-style and normalized action labels; tune to the actual connector values. File-event aggregation identifies regeneration behavior but does not prove this malware family.
  • Deleting visible files first can trigger recovery or disrupt PHP because auto_prepend_file is cached. Eradication must be coordinated as an incident-response change, not performed from Sentinel results alone.