Mailcow est un serveur de messagerie open source et auto-hébergé très répandu qui offre tout ce dont vous avez besoin pour gérer vous-même vos boîtes mail. Afin d'évaluer sa sécurité, nous avons mis en place une instance locale et avons effectué notre pentest IA . Nous avons découvert trois vulnérabilités XSS, dont une vulnérabilité critique qui permettait à des attaquants non authentifiés de prendre le contrôle des comptes administrateurs tout en consultant leurs journaux dans l'interface utilisateur.
L'accès à une boîte mail peut avoir de graves conséquences en matière de sécurité. Les données sensibles contenues dans les e-mails peuvent être interceptées, mais cet accès permet également aux pirates d'utiliser la fonctionnalité de réinitialisation du mot de passe pour compromettre les autres comptes connectés de la victime. C'est exactement ce qui aurait pu se produire si quelqu'un avait exploité ces vulnérabilités.
Toutes les vulnérabilités ont été signalées de manière responsable à Mailcow et ont été corrigées depuis la version 2026-03b (publiée le 31 mars 2026). Nous tenons à remercier le responsable du projet, FreddleSpl0it, pour le bon déroulement du processus et la rapidité de la correction.
Journaux Autodiscover non échappés
Signalée sur GitHub sous la référence GHSA-f9xf-vc72-rcgm, cette vulnérabilité permettait à des attaquants non authentifiés d'envoyer une requête Autodiscover contenant une adresse e-mail malveillante, qui apparaissait ensuite dans les journaux. Lorsqu'un administrateur consultait ces journaux par la suite, le champ de l'adresse e-mail s'affichait sans échappement, ce qui ouvrait la voie à une injection HTML et à des attaques XSS.
Commençons par le début. Mailcow permet de consulter les journaux Autodiscover sous forme de tableau dans le panneau d'administration, qui est généré à l'aide de DataTables dans le fichier dashboard.js. Cette bibliothèque présente un écueil courant : elle interprète par défaut toutes les valeurs de données comme du code HTML. Il est donc nécessaire d'échapper correctement toutes les valeurs au préalable.
var table = $('#autodiscover_log').DataTable({
...
ajax: {
type: "GET",
url: "/api/v1/get/logs/autodiscover/100",
dataSrc: function(data){
return process_table_data(data, 'autodiscover_log');
}
},
Le process_table_data() la fonction tente d'escape les données saisies escape dans chaque ligne. Pour les journaux Autodiscover, item.ua (Agent utilisateur) est correctement échappé à l'aide de escapeHtml (dashboard.js) :
} else if (table == 'autodiscover_log') {
$.each(data, function (i, item) {
if (item.ua == null) {
item.ua = 'unknown';
} else {
item.ua = escapeHtml(item.ua);
}
item.ua = '<span style="font-size:small">' + item.ua + '</span>';Cependant, une colonne, « item.name », n'est pas échappée. Il s'agit de l'adresse e-mail envoyée dans la requête Autodiscover.
Le plus dangereux dans tout ça ? Une requête Autodiscover n'est, par nature, pas authentifiée , et dans ce cas précis, l'adresse e-mail n'est pas validée. Elle se retrouvera dans les journaux, et dès qu'un administrateur les consultera, elle sera traitée par ces fonctions vulnérables pour être affichée sous forme de code HTML arbitraire.
Voici un exemple de requête qui injecte <img src=x onerror=alert(origin)> dans le tableau :
POST /Autodiscover/Autodiscover.xml HTTP/2
Host: 127.0.0.1
Content-Type: text/xml
Content-Length: 384
<?xml version='1.0' encoding='utf-8'?>
<Autodiscover xmlns='http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006'>
<Request>
<EMailAddress><img src=x onerror=alert(origin)></EMailAddress>
<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
</Request>
</Autodiscover>
Lorsqu'un administrateur consulte désormais les journaux Autodiscover (accessibles via Tableau de bord -> Journaux -> Autodiscover), le code JavaScript s'exécute et affiche une fenêtre contextuelle d'alerte, ce qui prouve l'existence d'une vulnérabilité XSS au niveau de l'origine Mailcow.

Comme cela ne concerne que les administrateurs, les pirates peuvent accéder à la boîte de réception de n'importe quel utilisateur et reconfigurer l'instance. C'est pourquoi cette vulnérabilité a été classée comme critique.
Ce numéro a été corrigé en ajoutant un escapeHtml() appeler la fonction item.user lors du traitement des lignes.
Insertion des noms de fichiers joints en quarantaine
Signalée sous la référence GHSA-2xjc-rg88-jvpp, cette vulnérabilité se trouve dans la fonctionnalité « Quarantaine » de Mailcow, qui permet aux administrateurs d'examiner les pièces jointes signalées. Les noms de fichiers de ces pièces jointes s'affichaient sans échappement HTML, ouvrant ainsi la voie à une attaque XSS pour tout administrateur qui les consultait. L'exploitation de cette faille ne nécessitait aucune authentification de la part de l'attaquant, mais exigeait que la fonctionnalité « Quarantaine » soit activée sur l'instance ciblée.
Cette fois-ci, le problème est résolu. Dans le fichier quarentine.js, il y a un bout de code qui concatène du code HTML avec des données dynamiques :
$.each(data.attachments, function(index, value) {
qAtts.append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
Si l'un de ces éléments est contrôlé par l'utilisateur, cela pourrait à nouveau entraîner une injection HTML. Il s'agit respectivement du nom du fichier, du type MIME, de la taille du fichier et de la valeur SHA-256 de VirusTotal. Parmi ces éléments, le nom du fichier semble le plus susceptible de contenir des données fournies par l'utilisateur, car un attaquant peut envoyer un e-mail avec une pièce jointe dont le nom de fichier comporte des balises HTML.
When testing, it turns out that, as expected, no strict validation takes place on this filename. It can contain <> characters, which is all that's needed to inject an XSS payload. The remaining challenge is getting the email into the quarantine queue in the first place, so an admin will open and inspect it. The attacker solves this by attaching an EICAR antivirus test file, which is a standardized string that every antivirus engine is guaranteed to flag. This reliably routes the email to quarantine without requiring the attacker to send actual malware.
Le code ci-dessous génère une pièce jointe dont le contenu est la chaîne EICAR et dont le nom de fichier est une charge utile XSS (voir l'avis de sécurité pour le script complet) :
# Malicious filename attachment with EICAR to trigger quarantine
att = MIMEApplication(b'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*', _subtype='octet-stream')
att.add_header('Content-Disposition', 'attachment', filename='<img src=x onerror=alert(origin)>.exe')
msg.attach(att)
Une fois l'e-mail envoyé à Mailcow et mis en quarantaine, l'administrateur peut ouvrir la page « Quarantaine » pour le consulter. Lorsqu'il clique sur « Afficher l'élément », notre nom de fichier s'affiche et l'attaque XSS se déclenche :

Il convient de noter que cette faille ne nécessite pas non plus d'authentification sur Mailcow pour être exploitée. Il suffit d'envoyer un e-mail malveillant au domaine Mailcow et que l'administrateur l'examine. À partir de là, l'instance tout entière pourrait être prise en main à l'aide de JavaScript, permettant ainsi de consulter à nouveau les boîtes de réception et de configurer l'instance.
Ce problème a été résolu en ajoutant des appels à la fonction escapeHtml() autour des valeurs des pièces jointes.
Exploitation d'une vulnérabilité Self-XSS sur une adresse IP figurant dans l'historique de connexion
La dernière vulnérabilité XSS, référencée sous le code GHSA-jprq-w83q-q62h, présentait un intérêt technique plus marqué en termes d'exploitation. La charge utile XSS est stockée sous le compte de l'attaquant lui-même, ce qui signifie que, normalement, seul l'attaquant pourrait la déclencher (un « Self-XSS » sans véritable impact). L'attaque devient dangereuse lorsqu'elle est combinée à un CSRF de connexion, qui force le navigateur de la victime à se connecter au compte de l'attaquant, exposant ainsi la charge utile stockée à la mauvaise personne.
La vulnérabilité commence par un autre mécanisme de concaténation HTML simple dans le fichier user.js:
var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.tools/prefix/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";Le real_rip (Adresse IP réelle) apparaît dans le tableau des connexions récentes sans être masquée. En temps normal, cela ne devrait pas poser de problème ; les adresses IP respectent un format très strict qui ne laisse aucune place à des charges utiles XSS.
Cependant, les adresses IP ne sont pas vérifiées par Mailcow et proviennent directement du X-Real-IP: en-tête par défaut. Certains proxys attribuent à cet en-tête l'adresse IP distante de l'utilisateur, mais en l'absence d'un tel proxy, toute valeur qui lui est attribuée sera considérée comme valide. Vous pouvez donc vous connecter tout en définissant X-Real-IP: "><img src onerror=alert(origin)> ce qui enregistre la valeur dans l'historique des connexions récentes. Lorsqu'on le consulte (via /utilisateur), la charge utile XSS s'affiche et déclenche l'alerte.
POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-Real-Ip: "><img src onerror=alert(origin)>
login_user=attacker&pass_user=attacker
Mais l'histoire ne s'arrête pas là. En effet, une victime ne va pas se connecter en utilisant cet en-tête IP falsifié, ni se connecter au compte de l'attaquant sans raison. Pour l'instant, il s'agit d'un Self-XSS.
Deux aspects de cette vulnérabilité permettent l'escalade des privilèges. Premièrement, la charge utile est stockée de manière persistante sous le compte de l'attaquant. Deuxièmement, la requête de connexion ne dispose d'aucune protection contre les attaques CSRF, ce qui signifie qu'elle peut être déclenchée de manière intersite. Un attaquant peut héberger un formulaire sur son propre domaine qui soumet automatiquement ses identifiants, connectant ainsi la victime sans aucune interaction de sa part, si ce n'est la simple consultation de la page.
Un pirate peut héberger le formulaire suivant sur son domaine malveillant :
<form action="http://mailcow.local/" method="POST">
<input type="text" name="login_user" value="attacker">
<input type="text" name="pass_user" value="attacker">
</form>
<script>
document.forms[0].submit();
</script>
Dès qu'une victime ouvre ce formulaire hébergé sur le site web malveillant, elle est automatiquement connectée au compte du pirate. Nous pouvons alors la rediriger vers /utilisateur pour déclencher le XSS que nous avons enregistré précédemment. Mais attendez un instant : la victime est désormais connectée au compte de l'attaquant, alors comment ce dernier peut-il voler de la victime des données ?
Dans ce cas, l'attaquant peut recourir à une astuce ingénieuse : laisser la boîte de réception de la victime ouverte jusqu'à ce que l'attaque CSRF et XSS de connexion soit terminée, puis en extraire le contenu grâce à l'accès « same-origin ».
Le nouveau flux se présentera comme suit :
- La victime accède à notre page malveillante (onglet 1)
- L'onglet 1 ouvre une deuxième page malveillante (onglet 2) contenant le formulaire CSRF de connexion, avec un léger délai avant son exécution
- L'onglet 1 redirige vers la boîte de réception de la victime et charge les données que le pirate souhaite voler
- L'onglet 2 envoie le formulaire CSRF de connexion, ouvrant ainsi un nouvel onglet 3 qui connecte la victime au compte de l'attaquant
- L'onglet 2 redirige vers
/utilisateur, là où le XSS stocké est déclenché - La faille XSS de l'onglet 2 utilise une référence à ouverture pour lire et extraire
document.body.innerHTMLdans l'onglet 1, où la boîte aux lettres de la victime est toujours ouverte

Cela permet de lire les e-mails du compte de la victime, car ceux-ci ont été récupérés alors qu'elle était encore connectée à son compte !
Consultez l'avis de sécurité pour obtenir tous les détails sur le fonctionnement concret de cette chaîne d'exploitation. Grâce à plusieurs fichiers HTML permettant un contrôle précis du déroulement de la chaîne, un pirate peut dérober des e-mails en seulement deux clics (nécessaires pour ouvrir les deux onglets supplémentaires).
Ce problème a été résolu en ajoutant des appels à la fonction escapeHtml() autour du rendu des adresses IP.
Correction
Mettez à jour Mailcow vers la version 2026-03b ou une version ultérieure, publiée le 31 mars 2026. Les trois vulnérabilités XSS ont été corrigées dans cette version. Si vous utilisez Aikido, les instances Mailcow vulnérables sont automatiquement signalées dans votre flux de surveillance de la surface d'attaque comme des incidents critiques.

Vous n'êtes pas Aikido inscrit sur Aikido ? Créez un compte gratuit pour commencer — aucune carte bancaire n'est requise.
Conclusion
Une tendance qui ressort de ces révélations est que la concaténation de chaînes HTML reste une mauvaise idée. Les moteurs de modèles HTML modernes ont permis de grandes améliorations dans la plupart des applications, mais les applications plus anciennes, dépourvues de frameworks sécurisés, ne bénéficient pas de cet avantage. Elles ont parfois recours à la concaténation de chaînes en JavaScript, ce qui, comme on le voit ici, conduit rapidement à oublier escape les données escape .
Les applications de messagerie contiennent des informations extrêmement sensibles ; elles doivent donc être traitées avec le plus grand soin. Ces applications doivent faire l'objet d'audits rigoureux, car la compromission de l'une d'entre elles peut entraîner la mise en œuvre de mécanismes de réinitialisation des mots de passe, ce qui peut compromettre de nombreux autres comptes associés.
Aikido (pentest IA) détecte ce type de vulnérabilités dans les applications, de manière entièrement automatisée. Si ces résultats vous amènent à vous interroger sur les vulnérabilités XSS de vos propres applications, inscrivez-vous ou contactez-nous !

