Inside a Multi-Stage WordPress Compromise on a PanelAlpha Engine Host

When a new PanelAlpha hosting customer reached out asking us to investigate “a few infected sites,” we expected the usual — a rogue admin account, maybe a backdoor plugin. What we found was a sophisticated, multi-layered compromise that had been quietly operating for over two months. Here’s how we unraveled it, and what it tells us about securing containerized WordPress environments.
 
 

A New Environment, A New Challenge

 
PanelAlpha Engine is a Docker-based WordPress hosting platform where each site runs in its own isolated container. It’s a modern, well-architected approach to hosting — but it introduces wrinkles that traditional security tooling isn’t built for. PHP doesn’t exist on the host OS. Databases are only reachable from inside the container network. And the standard assumption that you can just SSH in and run a PHP script goes right out the window.
 
Our tooling had to adapt in real time. Rather than executing scripts directly on the host, we extended our automated scanner to run PHP inside the appropriate site container using docker exec, routing through the container’s own PHP-FPM environment. The upside? Our scripts ran in exactly the same context as WordPress itself — same database connectivity, same filesystem access, no compatibility surprises.
 
 

The Timeline

 
Working backward through nginx access logs and WordPress session data, we reconstructed the attack chain:
 
January 16 — An account named wpwebsites logged into WordPress with no browser fingerprint. This was the initial access — almost certainly a credential stuffing hit against a reused password, or stolen credentials via an info stealer. The session left no trail of admin activity, which is itself suspicious. This is when the attacker planted their persistence mechanisms.
 
March 5 — A second rogue admin account, wphadm, appeared and logged in. Classic backup access — if the primary account gets removed, the attacker still has a foothold.
 
March 9–20 — admin@[redacted].com, a legitimate admin account, logged in repeatedly from IP 96.189.210.156. Thirty-three minutes after one login, the attacker executed a PHP webshell via a wp_debug_session parameter — running arbitrary code on the server from their browser.
 
 

What They Left Behind

 
Three distinct backdoor mechanisms were discovered:
 
1. The MU-Plugin Dropper
Injected into a child theme’s functions.php between two /* __mu_deployer__ */ markers, this code ran on every page load. It wrote a full-featured backdoor to wp-content/mu-plugins/session-manager.php — and if that failed, it installed itself as a regular plugin. Once successful, it deleted itself from functions.php, leaving no obvious trace in the theme file.
 
2. The wp_debug_session Webshell
 
A hook injected into the Hello Elementor parent theme intercepted incoming requests, checked for a 64-character hex session token in the query string, and executed arbitrary PHP from the POST body. This is how the attacker ran commands on the server without ever touching wp-admin. The first execution returned HTTP 200; a second attempt returned 500 — they were actively working the server.
 
3. The Credential Harvester
 
The most insidious of the three. A single line appended to the Hello Elementor functions.php hooked WordPress’s authentication filter and wrote every successful admin login — username and plaintext password — to a file disguised as a JPEG in wp-content/uploads/2024/06/StainedHeartRed-600×500.png. The file contained admin@[redacted].com’s password, captured across multiple login sessions over several weeks.
 
 

The Docker Advantage — And Its Limits

 
PanelAlpha’s container-per-site model delivered on its promise in one important way: this compromise was entirely contained to a single site. When we searched nginx logs across all 28 containers on the Engine host for the attacker’s IP, it appeared only in one domain’s logs. No lateral movement, no cross-site contamination.
 
That isolation is real and meaningful. On a traditional shared hosting server, a compromised site is one PHP open_basedir bypass away from touching every other account on the machine.
 
But containers don’t protect against compromised credentials, nulled plugins, or malicious code injected into themes — and that’s exactly how this attack succeeded. The initial vector was almost certainly a reused password combined with a nulled copy of the Hide My WP plugin, which shipped with a pre-configured IP whitelist allowing the attacker’s infrastructure to bypass the plugin’s own security rules.
 
One additional risk worth flagging: all 28 site containers on this Engine host share a single Docker bridge network (pash-default-network). Every container, including the shared MariaDB instance, is reachable from every other container. A compromised site with a capable enough webshell could reach any other site’s database directly. This isn’t a flaw in PanelAlpha specifically — it’s an architectural reality of Docker networking that hosting providers and their customers should understand.
 
 

Remediation

 
With the attack chain fully mapped, remediation was methodical:

  • All active sessions for the three rogue and compromised accounts were invalidated directly in the database
  • The wphadm and wpwebsites admin accounts were removed
  • All three backdoor mechanisms were removed from theme files and mu-plugins
  • The credential harvester file was deleted
  • All admin passwords were force-reset using our direct-database reset tool — specifically bypassing WordPress hooks to ensure no remaining malware could intercept the new credentials
  • The customer was notified that the harvested password should be treated as burned and changed anywhere it was reused

 
 

What This Tells Us

 
Two months is a long time for an attacker to have quiet access. The wpwebsites account sat dormant for nearly two months before the credential harvester started producing results. This is a patience-based attack — plant, wait, harvest, then act.
 
The lesson for containerized WordPress environments is the same as everywhere else: isolation at the infrastructure layer doesn’t substitute for monitoring at the application layer. Knowing a file was written to mu-plugins/ at 2am, or that a new admin account was created with no browser fingerprint, or that authentication events are being written to a disguised file in uploads — that’s the signal layer that stops a two-month dwell time from becoming a two-year one.
 
That’s exactly what we’re here for.