← All write-ups
January 8, 2024CRITICALFINDING MISSION

XSS stocké sur une plateforme EDI : impact de masse

Finding sur une plateforme EDI (client/éditeur anonymisés). Découvert par 34zY / Hamza Terir. Divulgation tentée, restée sans réponse. Domaines et nom produit retirés.

Contexte

Plateforme EDI multi-utilisateurs. Un champ de profil (« bio » / « à propos ») est réaffiché aux autres utilisateurs qui consultent le profil.

Comment je l’ai trouvée

Le champ de profil accepte du HTML arbitraire, sans sanitisation ni CSP. Le payload est stocké en base et réexécuté automatiquement dans le navigateur de chaque utilisateur qui affiche le profil :

'></script><script>/* PoC : lecture cookie de session */</script>
<img src=x onerror="/* exfiltration vers serveur attaquant */"><!--

Contrairement à un XSS reflété (qui exige un clic sur un lien piégé), ici la victime est touchée simplement en consultant la page.

Injection du payload dans le champ de profil. 1: Injection du payload dans le champ de profil.

Payload stocké après sauvegarde du profil. 2: Payload persisté en base après sauvegarde.

Profil contaminé rendu : defacement injecté. 3: Consultation du profil, le HTML injecté s’exécute (defacement).

Exécution JavaScript : lecture du cookie de session. 4: Le script s’exécute dans la session de la victime (lecture du cookie).

Validation technique sécurisée

Le retest utilise un document inerte et un marqueur sans script :

curl --fail-with-body --max-time 10 \
  -H 'Authorization: Bearer TEST_TOKEN' \
  -F 'file=@fixtures/benign-document.txt;type=text/plain' \
  'https://AUTHORIZED_HOST/api/import'

Le rendu doit encoder toute valeur issue du document avant insertion dans HTML :

import html

def safe_text(value: str) -> str:
    return html.escape(value, quote=True)

Les preuves de retest couvrent l’import, la consultation par un autre rôle et les en-têtes de réponse; aucun contenu actif n’est publié dans cet article.

La faille

XSS stocké (CWE-79) : absence d’encodage en sortie et de validation en entrée sur un champ persistant, sans CSP. Le formulaire d’édition n’a pas de token CSRF (CWE-352).

Impact

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H = 9.3 (Critical).

Remédiation

CWE-79 (Stored XSS) · CWE-352 (CSRF).