
The short version: A backdoor disguised as a legitimate Joomla plugin called “MetaCache” lived on two websites for sixteen months without triggering any scanner. The attacker used it to walk back in this past weekend. We found it, removed it, and traced the persistence pattern. If you run a Joomla site, this is one worth understanding.
Read the full technical breakdown ↓
What happened
For the last year we’ve been tracking a wave of attacks against some Joomla sites belonging to one of our resellers. Every time we cleaned one up, the same site would get hit again a few days later. That’s the tell for a real backdoor — not just a compromised page, but something buried deeper that gives the attacker a way back in.
This past week, on two sites belonging to one of our reseller partners, we found what was giving the attacker their return ticket. It was a file sitting in the Joomla plugins folder called metacache.php, pretending to be a caching plugin. It carried a plausible name, a matching XML manifest, and the same folder structure a real Joomla plugin uses. It even passed the standard Joomla security check at the top of the file.
Underneath the disguise, it was a purpose-built backdoor from a well-known Chinese webshell family (called Godzilla, in the security community). Anyone who knew the secret key could send commands to the site through a normal-looking Joomla URL and get full control of the account.
The file had been sitting on those two sites since February of 2025. Sixteen months. Every scanner passed it by.
Why nothing caught it
A few things worked in the attacker’s favour.
The filename sounded real. “MetaCache” sounds like it belongs. If you were browsing a plugin folder and saw it, you’d assume it was there for a reason.
The file was small — about five and a half kilobytes. Most scanners flag oversized or heavily obfuscated files first, and this one didn’t look like either at a glance.
The malicious call was buried inside a conditional branch, deep in the file, wrapped in what looked like normal PHP class structure. Scanners that skim the top of a file for obvious signs (base64 chunks, eval statements, hex-decoded strings) walked right past it.
And it lived in the right place. Joomla plugins live in a folder called plugins/system/. Everything about this file looked like it was meant to be there.
The specific detection rule that finally caught it was only added to the scanner’s signature set recently. Earlier scans on the affected sites literally didn’t have the signature yet.
What the attacker did with it
The rescan turned up four fresh files on one of the affected accounts, all dated the previous Sunday between two-thirty and three-fifteen in the morning. That’s a single attacker session, and the backdoor is what let them in.
They dropped a full webshell control panel (a large PHP file that presents a graphical interface for browsing files, running commands, and downloading data), a smaller uploader, a defacement page, and one more staging file in the account’s temporary folder. Sixteen months of unmonitored access, and they were still using it two days ago.
What we did
Removed the backdoor from both affected sites. Kept forensic copies so we can compare signatures if something similar shows up elsewhere. Cleaned out the four fresh files the attacker had just dropped. Ran a server-wide search for the specific pattern this variant uses (and a broader search for the same template with rotated keys) and confirmed the compromise stayed contained to those two sites.
We also passed the reseller partner a short list of things they should do on their side, because the backdoor gave the attacker unlimited code execution as their cPanel users for the whole sixteen-month window. Anything those users could read — including database credentials and any locally-stored account details — should be treated as known-compromised. Passwords rotated, users audited for stealth admin accounts, dangling database entries cleaned up.
What this means for you
If you run a Joomla site (or your site does — someone else set it up for you), this incident is a good reminder that the plugins folder is a place worth knowing about. Most Joomla owners never look in there. Legitimate plugins install themselves through the admin interface; that’s how it’s meant to work. But if an attacker gets a foothold on your site through some other vulnerability, dropping a fake plugin in that folder is one of the ways they’ll try to make sure they can come back.
A few practical things.
Keep Joomla and its extensions up to date. Most compromises start with an outdated extension or a known vulnerability, not something exotic. The attacker who left this backdoor almost certainly got in through a JCE editor vulnerability that had a patch available at the time.
Have someone check your plugins folder every so often — either you (browsing it via cPanel File Manager) or your web person. You’re looking for anything that shouldn’t be there. If a plugin name doesn’t ring a bell, ask the person who built the site.
Use a security scanner that gets its signature set updated regularly. Every scanner has blind spots — the reason this one survived sixteen months is because no scanner had a signature for it yet. Regular signature updates close that gap.
If you’d like us to check your own Joomla site for the same pattern, or to review your plugin folder in general, reply here and we’ll take a look. Same offer for anyone we already host — just ask.
The technical breakdown
The rest of this post is for fellow admins and security folks who want the receipts. If you came for the summary, you’re done — thanks for reading.
Discovery
Two files, byte-for-byte identical, planted one day apart:
/plugins/system/metacache/metacache.php (site A, mtime 2025-02-15)
/plugins/system/metacache/metacache.php (site B, mtime 2025-02-16)
MD5: adea75621d80d0cb9be5d52b02a0784d
Same reseller, adjacent cPanel accounts, cross-planted during the same attacker session. That matches the pattern this actor has been using across the reseller’s portfolio for months — hit one site, hop laterally to the next one owned by the same user.
Findings
The file presents as a fully-installed Joomla 4+ system plugin:
- Passes the standard
defined(’_JEXEC’) or die;guard at the top - Ships with a plausible
metacache.xmlmanifest and aservices/provider.phpstub - Exposed via Joomla’s built-in
com_ajaxdispatcher as anonAjaxMetacacheevent handler — reachable at any/index.php?option=com_ajax&plugin=metacache&format=raw&metacache_debug=<key> - Fixed 32-hex trigger key plus XOR key for the payload channel
The author left comments in the source. One of them literally reads:
/* ---------- Godzilla (PHP_XOR_BASE64) ---------- */
Godzilla is a Chinese-authored PHP webshell family that’s been in active development since around 2020. The author’s own notes in the file discuss adapting the shell for Joomla 6’s session-regeneration behaviour, so this isn’t opportunistic — someone spent time customising it for Joomla.
Why the file survived sixteen months of scans
Five factors, in combination:
- Plausible filename in a plausible location.
plugins/system/metacache/looks exactly like a real caching plugin’s install path. Signature scanners that use path allowlists don’t flag it. - Small file size. 5.5 KB is well below the “unusually large PHP file” threshold most heuristics use.
- No top-of-file red flags. No
eval(base64_decode(...)), no hex-decoded strings at the top, no obvious obfuscation. A scanner walking file text sees clean class and method structure. - The
eval()call is buried deep in a conditional branch, only reached after the correct XOR key and trigger key are supplied. A signature that pattern-matches onevalin isolation would still trigger, but any scanner that requires context (nearby suspicious tokens) walks past. - Old mtime. February 2025 pushed the file below any “recent files only” scan window that some hosting-side scanners default to.
The scanner rule that finally caught it was added to the vendor’s signature set some months after the file was planted. Every scan before that rule shipped came back clean.
Fresh activity two days before discovery
The rescan flagged four files on one of the affected accounts with mtimes clustered in a single Sunday-night session:
public_html/images/readme.php 620 KB PHP webshell control panel
public_html/images/default.php 77 KB dropper
public_html/index.html 13 KB defacement page
public_html/.cagefs/tmp/<file> ----- staging
All within a ~40-minute window in the small hours of the morning. The dropper’s XOR payload channel matched the metacache backdoor’s key — same actor, same session, using the backdoor to stage new tooling.
Remediation
Per-site actions on both affected accounts:
- Removed
plugins/system/metacache/in its entirety (forensic copy preserved server-side under a locked path outside the account’s docroot) - Removed the four fresh files from the recent session on the actively-abused account
- Cleared an earlier round of SSI-style droppers on the same account plus a handful of stubs on a sibling account from a February 2025 sweep that previous cleanup passes had missed
- Server-wide grep across all customer home directories for the specific Godzilla trigger key and a broader “same-template rotated-key” hunt: zero hits outside these two sites
- Reseller partner briefed on credential rotation (Joomla admin passwords on both sites, cPanel passwords, database credentials treated as known-compromised for the 16-month window) and the
#__extensionstable cleanup needed on the affected Joomla databases
Practical takeaways for other admins
- Diff
/plugins/system/against a fresh Joomla install. Anything you don’t recognise is worth a second look, even if the folder structure looks legitimate. - Grep for
com_ajaxdispatcher hooks in extensions you didn’t install intentionally. It’s a favourite exposure pattern because it doesn’t need any additional routing setup. - If you’re on Joomla and using the JCE editor, make sure you’re on 2.9.99.7 or later. Everything earlier has an actively-exploited vulnerability.
- Re-scan sites that haven’t been scanned in the last month. Old “clean” results don’t guarantee current clean.
← Back to the plain-English summary
Add comment