Fifty compromised Joomla sites landed on my desk in two days. Every one showed the same symptom: a full-screen dark overlay with a skull icon and a “Hacked by” message where the homepage used to be. The file scanner came back clean on all of them. The index.php matched its original checksum on most. There was no webshell, no backdoor, no rogue file anywhere on disk.
The payload was in the database.
What I was looking at was the live aftermath of CVE-2026-49049 — a critical unauthenticated vulnerability in JoomShaper’s Helix3 template framework that lets anyone on the internet write arbitrary content into a Joomla site’s template configuration, directly in the database, without logging in. A botnet had weaponized it within days of the patch dropping, and it was hitting everything: Joomla 3, 4, 5 — any version running Helix3 below 3.1.2.
But the Helix3 exploit was only half the story. The other half of these fifty sites weren’t running Helix3 at all. They were plain Joomla 3 installations — end-of-life since August 2023, running unpatched for three years, wide open to every known exploit in the book. Same defacement signature, same attacker handles, different entry points. The botnet wasn’t picky about how it got in. It just needed Joomla.
The Vulnerability: CVE-2026-49049
JoomShaper’s Helix3 is one of the most widely installed template frameworks in the Joomla ecosystem. It ships with a companion AJAX plugin (plg_ajax_helix3) that handles template customization requests — saving settings, uploading images, importing configurations.
The problem: none of these handlers check authentication.
Joomla’s com_ajax component dispatches requests to plugin handlers for any visitor, including unauthenticated ones. It delegates authorization responsibility entirely to the plugin. Helix3 never implemented it. Every sensitive operation the plugin exposes — file writes, file deletions, template parameter overwrites — is accessible to anyone who can reach the site over HTTP.
The vulnerability exposes four distinct attack primitives:
- Template parameter overwrite (
importaction) — overwrite the entire template style configuration, including custom code injection fields. This is the vector used in the defacement wave. - Arbitrary file write (
saveaction) — write files into the template directory, with path traversal allowing writes outside the intended directory tree. - Arbitrary file deletion (
remove/remove_imageactions) — delete any file the web server user can access. - PHP file upload — upload executable PHP files through the image upload functionality, enabling full remote code execution.
The defacement botnet uses the import action — the least destructive option, ironically — to inject JavaScript into the template’s custom code fields. It’s a single HTTP request. No exploit chain, no payload staging, no authentication bypass trickery. Just a POST to com_ajax with the right parameters.
Where the Payload Lives
This is what makes the attack invisible to traditional file-based malware scanners. The injected code lands in the #__template_styles table, inside the params column — a JSON blob that stores the template’s configuration. Helix3 templates have several custom code fields that get rendered on every page load:
custom_js— Custom JavaScriptcustom_css— Custom CSSbefore_head— Before</head>before_body— Before</body>
The attacker overwrites one or more of these fields. The injected JavaScript executes on every single page load because Joomla renders these template parameters as part of the HTML output. No file is modified. No PHP is executed. The malicious code flows from database to browser through the template engine’s normal rendering pipeline.
-- Find injected defacement code in your Joomla database
SELECT id, template, title
FROM `#__template_styles`
WHERE `params` LIKE '%innerHTML%'
OR `params` LIKE '%document.title%'
OR `params` LIKE '%position:fixed%'
OR `params` LIKE '%z-index:2147483647%';
The Defacement Payload
The JavaScript payload is straightforward — no obfuscation, no evasion, just brute-force page replacement:
document.addEventListener("DOMContentLoaded", function() {
document.title = "Hacked by [REDACTED]";
document.body.innerHTML = '<div style="position:fixed;inset:0;' +
'background:#0a0a0a;z-index:2147483647;display:flex;' +
'align-items:center;justify-content:center;' +
'flex-direction:column;color:#fff;font-family:monospace">' +
'<div style="font-size:80px">💀</div>' +
'<h1>HACKED BY [REDACTED]</h1>' +
'<p>Your security is weak</p></div>';
});
It waits for the DOM to finish loading, then replaces the entire page body with a fixed-position overlay: black background, skull emoji, attacker attribution, mocking message. The z-index: 2147483647 (the maximum 32-bit integer) ensures nothing renders on top of it. The original page content is gone — not hidden, replaced.
Two primary handles have been circulating: “AntonKill” and “trenggalek6etar.” Both appear automated — the same payload structure, the same injection method, likely the same botnet with different operator tags.
What the Logs Show
Across the fifty compromised sites, the access logs told a consistent story. The botnet operates in two phases: a rapid version probe, followed immediately by the injection request if the target is vulnerable.
Phase 1: Reconnaissance
The scanner probes for the Helix3 AJAX handler with a lightweight GET request:
172.94.9.113 - - [06/Jul/2026:03:14:22 +0000] "GET /index.php?option=com_ajax&plugin=helix3&task=version&format=json HTTP/1.1" 200 847 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
172.94.9.113 - - [06/Jul/2026:03:14:23 +0000] "GET /index.php?option=com_ajax&plugin=helix3&task=version&format=json HTTP/1.1" 200 847 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
172.94.9.113 - - [06/Jul/2026:03:14:23 +0000] "GET /index.php?option=com_ajax&plugin=helix3&task=version&format=json HTTP/1.1" 200 847 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
Three rapid-fire requests in under two seconds — likely probing multiple virtual hosts on the same IP. A 200 response confirms Helix3 is installed and responding. The response body contains the version number, telling the scanner whether the target is below 3.1.2.
Phase 2: Injection
Within seconds of a successful probe, the injection request follows:
172.94.9.113 - - [06/Jul/2026:03:14:25 +0000] "POST /index.php?option=com_ajax&plugin=helix3&task=import&format=json HTTP/1.1" 200 1247 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
The task=import action overwrites the template style parameters. One POST request. One 200 response. The site is now defaced. Total time from probe to compromise: three seconds.
The Joomla 3 Variant
On the non-Helix3 Joomla 3 sites, the access logs showed a different pattern — broader reconnaissance followed by exploitation of known endpoints:
47.250.162.41 - - [07/Jul/2026:14:32:08 +0000] "GET /administrator/manifests/files/joomla.xml HTTP/1.1" 200 1043 "-" "python-requests/2.31.0"
47.250.162.41 - - [07/Jul/2026:14:32:09 +0000] "GET /language/en-GB/langmetadata.xml HTTP/1.1" 200 492 "-" "python-requests/2.31.0"
47.250.162.41 - - [07/Jul/2026:14:32:11 +0000] "GET /api/index.php/v1/config/application?public=true HTTP/1.1" 200 4891 "-" "python-requests/2.31.0"
47.250.162.41 - - [07/Jul/2026:14:32:15 +0000] "POST /index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml(1,concat(0x7e,(select+@@version)),1) HTTP/1.1" 200 28734 "-" "python-requests/2.31.0"
The first two requests fingerprint the Joomla version from publicly accessible XML manifests. The third probes for CVE-2023-23752 — the unauthenticated API endpoint that leaks database credentials in Joomla 4.0–4.2.7 (and returns useful version information on all versions). The fourth is a SQL injection attempt through com_fields, a vulnerability that has been public since 2017 and remains unpatched in Joomla 3.
The python-requests user-agent makes no attempt to disguise itself. When you’re scanning the entire internet for end-of-life software, stealth is unnecessary.
Post-Compromise Activity
On sites where the attacker achieved deeper access (filesystem write via Helix3 or credential theft on Joomla 3), the logs showed follow-up requests confirming the compromise:
172.94.9.113 - - [06/Jul/2026:03:14:41 +0000] "GET /templates/shaper_developer/cox.json HTTP/1.1" 200 42 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
172.94.9.113 - - [06/Jul/2026:03:15:02 +0000] "GET / HTTP/1.1" 200 8234 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
First, a GET request to cox.json — verifying the reconnaissance artifact was successfully written. Then a GET to the homepage — confirming the defacement is rendering. Automated quality assurance for vandalism.
The Silent Variant
The skull overlay is the noisy version — the one that gets reported immediately because the site owner can see it. But during investigation, I found a more dangerous variant on several of the compromised sites: silent injections that leave the page looking completely normal while loading secondary payloads at runtime.
These silent variants inject a small script loader instead of a page replacement. The loader fetches external JavaScript that renders fake “I’m not a robot” verification overlays and Web3 wallet-drainer pages. The site looks and functions normally until a visitor hits a specific trigger condition — a geo-targeted IP, a specific referrer, a particular user-agent — at which point the overlay appears.
This is the real threat. The defacement is vandalism. The silent variant is theft. And because no files change on disk, the server’s security scanner never flags it.
Beyond Helix3: The Joomla 3 EOL Problem
Half of my fifty cases weren’t running Helix3 at all. These were Joomla 3.x installations — end-of-life since August 17, 2023, with the last extended security patches ending in February 2025. They’ve been running without any security support for over a year.
On these sites, the attackers didn’t need a fancy AJAX handler exploit. They had their pick of unpatched vulnerabilities:
- Direct SQL injection against known Joomla 3 core and extension flaws, allowing arbitrary database manipulation without filesystem access
configuration.phptampering — once they had filesystem access through any vector, they modified Joomla’s main configuration file to alter database credentials, debug settings, or inject additional code pathsindex.phpreplacement — the classic defacement: replace the site’s entry point with a static HTML page showing the “Hacked by” message- Template file modification — injecting code into theme PHP files (
index.php,error.php,component.phpinside the template directory) so every page renders the defacement even if the rootindex.phpis restored
The pattern was consistent: the botnet probes for the Joomla version, identifies whether the site is running Helix3 (exploit the AJAX handler) or plain Joomla 3 (exploit known unpatched CVEs), and deploys the same defacement payload through whichever path is available. Same result, different vector.
Why Joomla 3 Is a Sitting Duck
Joomla 3’s end-of-life status creates a compounding vulnerability problem. Every new CVE discovered in Joomla’s codebase or its extensions is a permanent, unfixable hole for Joomla 3 sites. The Joomla project will never release another 3.x patch. And because Joomla 3 sites are typically managed by site owners who don’t actively maintain their infrastructure (if they did, they would have migrated), they tend to also run outdated PHP versions, outdated extensions, and weak admin credentials.
This is the lifecycle of every end-of-life CMS: the sites that remain on the old version are, by selection, the ones least likely to be maintained — making them the most vulnerable to exactly the kind of automated exploitation that targets them.
The Attack Timeline
June 29, 2026 JoomShaper releases Helix3 v3.1.1 patch
(silently fixes the AJAX handler auth check)
July 5, 2026 Botnet begins mass exploitation
(~1 week after patch = ~1 week to reverse-engineer the fix)
July 5-11 Defacement wave hits thousands of Joomla sites
"Hacked by AntonKill" / "Hacked by trenggalek6etar"
50 sites land on my desk in 48 hours
July 8, 2026 JoomShaper releases Helix3 v3.1.2
(second patch, likely addressing bypass of initial fix)
The pattern is textbook: the patch was public before the exploit was weaponized. Attackers diffed the 3.1.0 and 3.1.1 releases, identified exactly what was fixed, and wrote a scanner targeting the pre-patch behavior. The week-long gap between patch release and mass exploitation is consistent with automated vulnerability research workflows — diff the patch, write the PoC, spray the internet.
Why Scanners Missed It
Every server I examined had at least one file-based malware scanner installed. None of them caught the Helix3 injection. The reason is architectural:
- File scanners read files on disk. The defacement payload lives in a database column. No file was created, modified, or replaced on the Helix3-exploited sites. A clean-file restore — from backup, from the Joomla update manager, from a fresh download — changes nothing because the files were never the problem.
- Integrity checkers compare file hashes. Every file matches its expected hash because no file was touched. The malicious content flows through Joomla’s template rendering engine, from database to HTML output, using exactly the same code path that legitimate custom JavaScript uses.
- WAF rules look for exploit payloads in requests. The
com_ajaxrequest that delivers the injection looks identical to a legitimate template customization request. There’s no SQL injection syntax, no PHP code, no shell commands in the POST body — just JSON containing JavaScript that the template engine is designed to render.
The Joomla 3 sites fared slightly better for detection — the attackers modified actual files (index.php, template files), which file scanners could theoretically catch. But on sites where the attacker used SQL injection to modify database content without touching the filesystem, the same blind spot applied.
The cox.json Artifact
Several compromised sites had a file named cox.json dropped in the web root or /tmp directory via the Helix3 save action with path traversal. This appears to be a reconnaissance artifact — the botnet writes the file to confirm write access before deploying the template parameter injection. It serves no functional purpose in the defacement itself, but its presence is a reliable indicator of compromise for sites running Helix3.
# Check for the cox.json reconnaissance artifact
find /path/to/joomla/ -name "cox.json" -type f 2>/dev/null
# Check for unauthorized files in template directories
find /path/to/joomla/templates/ -name "*.json" -newer /path/to/joomla/configuration.php
Investigation & Cleanup
Step 1: Confirm the Infection Vector
# Check if Helix3 is installed and which version
find /path/to/joomla/plugins/ -path "*helix3*" -name "*.xml" \
-exec grep -l "version" {} \; \
-exec grep "version" {} \;
# Check for vulnerable Helix3 versions (below 3.1.2)
# If found: CVE-2026-49049 is your vector
# For Joomla 3 sites without Helix3: check the version
grep -oP 'RELEASE.*' /path/to/joomla/libraries/src/Version.php 2>/dev/null
cat /path/to/joomla/administrator/manifests/files/joomla.xml \
| grep '<version>'
Step 2: Find the Database Payload
-- Search for defacement indicators in template styles
SELECT id, template, title,
CHAR_LENGTH(params) AS param_size
FROM `#__template_styles`
WHERE `params` LIKE '%innerHTML%'
OR `params` LIKE '%document.title%'
OR `params` LIKE '%position:fixed%'
OR `params` LIKE '%2147483647%'
OR `params` LIKE '%hacked%'
OR `params` LIKE '%document.body%';
-- Check for injected content in custom code fields
SELECT id, template,
SUBSTRING(params,
LOCATE('"custom_js"', params),
200) AS custom_js_preview
FROM `#__template_styles`
WHERE `params` LIKE '%custom_js%'
AND `params` NOT LIKE '%""custom_js":""%';
Step 3: Clean the Database
For Helix3 sites, the cleanup is in the database, not on the filesystem. Access the Joomla administrator panel, navigate to Extensions → Templates → Styles, open each template style, and clear the Custom JavaScript, Custom CSS, and Before </head> fields of any injected code.
If you don’t have admin access, clean it directly in MySQL:
-- IMPORTANT: Back up the params column FIRST
SELECT id, params INTO OUTFILE '/tmp/template_styles_backup.csv'
FROM `#__template_styles`;
-- Then surgically remove the injected JavaScript
-- (adjust the JSON field names to match your findings)
For Joomla 3 sites with file-level defacement: restore index.php and template files from a known-clean backup or fresh Joomla download. Check configuration.php for tampering — compare database credentials against the originals, look for injected eval() calls or modified error reporting settings.
Step 4: Patch or Migrate
- Helix3 sites: Update to version 3.1.2 or later immediately. If the Joomla Update Manager doesn’t show the update, download and install manually from JoomShaper.
- Joomla 3 sites: There is no patch. Joomla 3 has been EOL since August 2023. The only real fix is migration to Joomla 4, 5, or 6. If migration isn’t immediately possible, restrict admin access by IP, disable unused extensions, and monitor the database for changes — but understand that this is a temporary bandage on a permanent wound.
Step 5: Check for Secondary Compromise
The defacement payload gets the attention, but CVE-2026-49049 also enables file writes and PHP uploads. Even if the visible defacement is cleaned, check for:
# Webshells dropped via the save/upload actions
find /path/to/joomla/templates/ -name "*.php" -newer /path/to/joomla/configuration.php
find /path/to/joomla/ -name "cox.json" -type f
# Recently modified PHP files outside normal update patterns
find /path/to/joomla/ -name "*.php" -mtime -7 -not -path "*/cache/*" \
-not -path "*/tmp/*" | head -50
# Check for new admin users created post-compromise
SELECT id, name, username, email, registerDate
FROM `#__users`
WHERE registerDate > DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY registerDate DESC;
Detection Signatures
If you manage multiple Joomla installations, here are the markers to scan for across your fleet:
# Filesystem indicators
find /path/to/ -name "cox.json" -type f # Recon artifact
find /path/to/ -name "*.php" -path "*/templates/*" \
-exec grep -l 'innerHTML\|hacked\|document.body' {} \; # Template PHP injection
# Database indicators (run against each Joomla database)
SELECT COUNT(*) FROM `#__template_styles`
WHERE `params` REGEXP 'innerHTML|document\\.title|2147483647|hacked';
# Log indicators (look for com_ajax Helix3 requests)
grep -c 'com_ajax.*helix3\|option=com_ajax.*plugin=helix3' \
/path/to/access-logs/*.log
The Bigger Picture
Three things stand out from processing fifty of these in two days:
First, database-level attacks are the blind spot. The entire web application security scanning industry is built around file analysis — signature matching, integrity checking, behavior analysis of PHP/JS files on disk. When the payload lives in a database column and gets rendered through the application’s own template engine, that entire detection layer is irrelevant. This isn’t a new problem (I’ve written about database-level persistence before), but CVE-2026-49049 demonstrates it at mass scale. Thousands of sites compromised, zero file-based indicators.
Second, the patch-to-exploit window is closing. Seven days from patch release to mass exploitation. The Helix3 3.1.1 changelog didn’t scream “critical security fix” — it was a routine update. But attackers are diffing every release of every popular extension, and a one-line authentication check added to an AJAX handler is trivial to spot in a diff. If you’re not updating extensions within days of release, you’re operating in the exploitation window.
Third, end-of-life software is not “old but working” — it’s actively exploited. The Joomla 3 sites in this batch weren’t targeted because someone had a grudge. They were found by automated scanners that probe every IP range for known vulnerable software. Every month that passes after EOL adds more unpatched CVEs to the attack surface. There is no maintenance posture that makes running Joomla 3 in production safe. The only mitigation is migration.
Your malware scanner can only find what it knows to look for. When the attack surface is a JSON column in a database table, and the delivery mechanism is the application’s own template engine, the only thing that catches it is an analyst who checks the database — not just the disk.