Contournement de la 2FA de Listmonk, corrigé uniquement dans la bêta 7.0 (CVE-2026-128332)
listmonk 6.0.0 à 6.2.0 laisse un attaquant muni d'un mot de passe contourner la 2FA TOTP ; la seule version corrigée est 7.0.0-beta.2.
En bref
- Deux failles de listmonk neutralisent l'authentification à deux facteurs TOTP : l'une saute le code après une réinitialisation de mot de passe, l'autre laisse un attaquant deviner les codes sans limite. VulnCheck a publié CVE-2026-128333 et CVE-2026-128332 le 13 novembre 2026.
- listmonk 6.0.0 à 6.2.0 et 7.0.0-beta.1 sont concernés. Les deux failles exigent un mot de passe valide ou une réinitialisation aboutie, et un compte dont la 2FA est activée.
- Le score est de 9,1 Critique en CVSS v4.0 et de 7,4 Élevé en CVSS v3.1. Aucune exploitation n'est connue et aucune des deux CVE n'est dans le catalogue KEV de la CISA au 13 novembre 2026.
- Passez à 7.0.0-beta.2, la seule version qui contient les deux correctifs. La branche 6.x n'a pas de correctif ; si vous ne pouvez pas passer à la bêta, limitez le débit des points de connexion d'administration. Voir « Comment corriger ? ».
Suis-je concerné ?
Vous êtes exposé lorsque toutes ces conditions sont réunies :
- Vous utilisez listmonk 6.0.0 ou une version ultérieure, jusqu'à 6.2.0 incluse, ou 7.0.0-beta.1.
- Au moins un compte, en particulier un administrateur, a l'authentification à deux facteurs TOTP activée.
- Pour CVE-2026-128332, l'attaquant connaît le mot de passe de ce compte. Pour CVE-2026-128333, il peut mener à bien une réinitialisation de mot de passe pour ce compte, par exemple en lisant ses courriels.
- L'interface
/adminest joignable par l'attaquant.
listmonk 5.x et les versions antérieures sont hors de la plage concernée par l'avis. Un compte sans TOTP n'a pas de second facteur à contourner : la faille de réinitialisation ne laisse alors que le mot de passe comme obstacle. La faille de force brute demande un code à six chiffres à deviner et un mot de passe pour commencer.
Une seule des deux branches dispose d'une version corrigée. La bêta 7.0 est corrigée ; la branche 6.x n'a pas de rétroportage.
| Branche | Versions concernées | Version corrigée |
|---|---|---|
| 6.x | 6.0.0 à 6.2.0 | Aucune ; le correctif n'existe que dans 7.0.0-beta.2 |
| Bêta 7.0 | 7.0.0-beta.1 | 7.0.0-beta.2 (10 novembre 2026) |
Vérifiez la version en cours. Le binaire et l'image Docker officielle répondent tous deux à --version :
# Binary install
/path/to/listmonk --version
# Docker
docker exec <container> /listmonk/listmonk --version
Listez les comptes dont la 2FA est activée. Exécutez cette requête sur la base de données de listmonk :
docker exec -i <postgres-container> psql -U listmonk -d listmonk \
-c "SELECT username, type, twofa_type FROM users WHERE twofa_type <> 'none';"
Un résultat vide signifie qu'aucun compte n'utilise TOTP : aucune des deux failles ne donne alors à un attaquant plus que le mot de passe ne donne déjà.
Comment corriger ?
Passez à 7.0.0-beta.2. C'est la seule version qui contient les deux correctifs.
- Sauvegardez la base de données avant de commencer. La version 7.0 modifie les paramètres et l'interface d'administration, et le projet demande une sauvegarde dans ses notes de mise à jour :
docker exec -i <postgres-container> pg_dump -U listmonk listmonk > listmonk-backup.sql - Avec Docker, remplacez le tag de l'image dans le fichier compose de
listmonk/listmonk:latestparlistmonk/listmonk:beta, puis lancez la mise à jour :docker compose down docker compose pull docker compose run --rm app ./listmonk --upgrade docker compose up -d app db - Pour une installation par binaire, arrêtez listmonk. Remplacez le binaire par la version 7.0.0-beta.2 de la page de publication. Lancez la mise à jour de la base, puis redémarrez listmonk :
./listmonk --upgrade ./listmonk --version - Pour une installation depuis les sources, positionnez-vous sur le tag
v7.0.0-beta.2et compilez le binaire avec le Makefile du projet. Lancez--upgradeaprès la compilation, comme à l'étape 3. - Testez la connexion d'administration après la mise à jour, y compris l'invite de la 2FA. Vérifiez ensuite que la version en cours affiche 7.0.0-beta.2.
La version 7.0 est une bêta avec des changements incompatibles. L'interface d'administration est réécrite, l'API des paramètres change la forme de app.from_email, et les webhooks SES et Azure modifient leurs exigences. Lisez les notes de publication avant de mettre à jour une installation de production, et faites d'abord un essai dans un environnement de test.
Si vous ne pouvez pas passer à la bêta, limitez le débit des deux points de connexion sur le reverse proxy et restreignez /admin aux réseaux de confiance. Une limite sur POST /admin/login et POST /admin/login/twofa ralentit la force brute. Elle ne ferme pas le contournement par réinitialisation, car ce chemin ne demande jamais de code.
Quelle est la cause racine ?
listmonk conserve ses jetons d'authentification de courte durée dans une table en mémoire du paquet internal/tmptokens. Le flux de connexion d'administration dans cmd/auth.go utilise cette table à la fois pour le jeton de réinitialisation et pour le jeton de 2FA. Les deux failles viennent de la façon dont ces jetons sont comptés et consommés.
La faille de réinitialisation se trouve dans doResetPassword. Après un changement de mot de passe réussi, la fonction supprime les sessions de l'utilisateur puis crée directement une nouvelle session :
// Invalidate all existing sessions for the user after password reset.
if err := a.core.DeleteUserSessions(user.ID, ""); err != nil {
a.log.Printf("error destroying sessions after password reset for user_id=%d: %v", user.ID, err)
}
// Log the user in directly without forcing a manual login right after password change.
if err := a.auth.SaveSession(user, "", c); err != nil {
return err
}
// Redirect to the admin page.
return c.Redirect(http.StatusFound, uriAdmin)
La fonction ne lit jamais user.TwofaType. Un utilisateur qui réinitialise son mot de passe est connecté immédiatement, même lorsque le compte a TOTP activé. Un attaquant qui peut mener à bien une réinitialisation obtient donc une session complète sans code.
La faille de force brute se trouve dans le compteur de jetons. TwofaPage appelle tmptokens.Check(token) à chaque requête vers /admin/login/twofa. Check incrémente un compteur propre au jeton et supprime le jeton lorsque le compte dépasse 15 :
// Increment the rate limit counter.
token.Count++
// Check if max attempts exceeded.
if token.Count > maxTries {
delete(tokens, id)
return nil, Err
}
Cette limite ressemble à une limitation de débit, mais elle est attachée au jeton et non à l'utilisateur. doLogin crée un nouveau jeton à chaque connexion par mot de passe :
// Set the token.
tmptokens.Set(token, twofaTokenTTL, user.ID)
Set crée un Token neuf avec un Count à zéro, et le point de connexion n'a pas de plafond de tentatives propre. Un attaquant qui possède le mot de passe peut demander un nouveau jeton après chaque série de 15 essais. Le compteur repart de zéro encore et encore.
Comment la faille est-elle exploitée ?
Le chemin de réinitialisation exige une réinitialisation de mot de passe aboutie. Le chemin de force brute exige le mot de passe du compte. Les deux aboutissent à une session pour un compte protégé par TOTP.
CVE-2026-128333, le contournement par réinitialisation :
- L'attaquant demande à listmonk d'envoyer un courriel de réinitialisation pour le compte ciblé, ou obtient autrement un lien de réinitialisation déjà envoyé.
- L'attaquant ouvre le lien et soumet un nouveau mot de passe à
/admin/resetavec le jeton reçu par courriel. doResetPasswordvérifie le jeton, met à jour le mot de passe et supprime les sessions existantes.- La même fonction appelle directement
SaveSession, donc la réponse pose un cookie de session sans invite de TOTP. - L'attaquant détient maintenant une session authentifiée pour ce compte.
CVE-2026-128332, la force brute du TOTP :
- L'attaquant soumet le nom d'utilisateur et le mot de passe connus à
POST /admin/login. - listmonk accepte le mot de passe, crée un jeton de 2FA au compteur à zéro et redirige vers
/admin/login/twofa. - L'attaquant soumet jusqu'à 15 codes à
POST /admin/login/twofa. Le jeton est supprimé après le quinzième essai. - L'attaquant reprend à l'étape 1. Chaque connexion par mot de passe crée un nouveau jeton et 15 essais supplémentaires.
- Un code correct crée la session, et l'attaquant s'arrête.
Aucune preuve de concept publique n'est publiée, et aucun des deux chemins n'exige de requête malformée. Le seul frein sur /admin/login est un temps de réponse minimal de 100 millisecondes.
Comment se déroule l'attaque ?
Un attaquant qui connaît un mot de passe, ou qui peut mener à bien une réinitialisation, transforme l'un ou l'autre chemin en session d'administration authentifiée sans code TOTP.
La boucle est la faille de force brute : une nouvelle connexion par mot de passe remet le compteur d'essais à zéro. L'arête directe est la faille de réinitialisation : la connexion qui suit la réinitialisation saute entièrement la page de 2FA.
Que peut faire un attaquant ?
Un attaquant qui atteint une session d'administration contrôle l'installation de listmonk que ce compte gère. Cela comprend les listes d'abonnés et leurs données personnelles, les campagnes, les modèles, les paramètres et les autres comptes utilisateurs. Le rôle Super Admin peut modifier les paramètres de toute l'installation, y compris la configuration d'envoi des courriels.
Le chemin de réinitialisation exige que l'attaquant mène à bien une réinitialisation, ce qui suppose normalement de lire les courriels de la cible. Le chemin de force brute exige le mot de passe du compte et de nombreuses requêtes de connexion.
Les deux exigent un accès réseau à /admin, et les deux ne fonctionnent que sur un compte dont la 2FA est activée. Sans TOTP, il n'y a pas de second facteur à contourner, et l'impact est le même que celui d'un mot de passe volé.
L'avis de VulnCheck ne décrit pas de chemin menant de la session d'administration à l'exécution de code sur l'hôte. Considérez l'impact comme le contrôle complet des données et de la configuration de listmonk, pas du serveur.
Pourquoi le correctif fonctionne-t-il ?
Les deux commits ferment les deux chemins. Le flux de réinitialisation passe désormais par la même fonction qu'une connexion normale, et le compteur TOTP est lié à l'utilisateur plutôt qu'à un jeton unique.
Le correctif de réinitialisation fait passer la connexion qui suit la réinitialisation par finishPasswordLogin :
// Log the user in directly without forcing a manual login right after password change.
- if err := a.auth.SaveSession(user, "", c); err != nil {
- return err
- }
-
- // Redirect to the admin page.
- return c.Redirect(http.StatusFound, uriAdmin)
+ return a.finishPasswordLogin(c, user, uriAdmin)
}
finishPasswordLogin lit user.TwofaType et, pour un compte TOTP, crée un jeton de 2FA et redirige vers /admin/login/twofa au lieu d'enregistrer une session. Une réinitialisation de mot de passe ne peut plus sauter le code.
Le correctif de force brute donne à chaque utilisateur un budget d'essais partagé :
func (a *App) doTwofaVerify(c *echo.Context, token string, userID int, next string) error {
+ // Share a budget across all login tokens for this user. Only requests with a
+ // valid password-authenticated token reach here. New logins do not reset it.
+ attemptKey := fmt.Sprintf("twofa-attempts:%d", userID)
+ tmptokens.SetNX(attemptKey, twofaTokenTTL, nil)
+ if _, err := tmptokens.Check(attemptKey); err != nil {
+ return echo.NewHTTPError(http.StatusTooManyRequests, a.i18n.T("globals.messages.invalidValue"))
+ }
+
totpCode := strings.TrimSpace(c.FormValue("totp_code"))
SetNX ne crée la clé que si elle n'existe pas ou si elle a expiré : une nouvelle connexion ne remet donc pas le compteur à zéro. Check a aussi été modifié pour conserver un jeton épuisé jusqu'à son expiration au lieu de le supprimer. Cela empêche SetNX de recréer la clé du budget.
Le même commit remplace la comparaison Get du jeton de réinitialisation par un Match à temps constant. Cette comparaison ne consomme le jeton que si la valeur correspond.
Les deux commits couvrent les deux avis. Les changements sur le jeton de réinitialisation sont un durcissement du même flux, pas une troisième vulnérabilité.
Comment vérifier si j'ai été compromis ?
Ni VulnCheck ni le mainteneur n'ont publié d'indicateurs de compromission. Une tentative ratée peut ne laisser aucune trace, car listmonk ne journalise pas chaque échec de vérification TOTP.
Cherchez des connexions répétées dans les journaux du reverse proxy ou du serveur web placés devant listmonk. Une attaque par force brute montre de nombreuses requêtes POST /admin/login depuis une même adresse, suivies de requêtes POST /admin/login/twofa. Elle montre aussi de nombreuses redirections vers /admin/login/twofa :
grep -E 'POST /admin/login(/twofa)?' access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
Examinez la table des sessions pour repérer des sessions créées à des heures où personne ne travaillait, et comparez-les avec les valeurs loggedin_at des utilisateurs :
docker exec -i <postgres-container> psql -U listmonk -d listmonk \
-c "SELECT created_at, data FROM sessions ORDER BY created_at DESC LIMIT 20;"
Demandez aux administrateurs s'ils ont reçu un courriel de réinitialisation qu'ils n'ont pas demandé. Une réinitialisation aboutie change le mot de passe du compte et détruit les anciennes sessions. Une réinitialisation que personne n'a demandée est le signe le plus clair du chemin de réinitialisation.
Chronologie
- 2026-06-26 : listmonk 6.2.0 est publié, la dernière version stable avant les correctifs.
- 2026-11-01 : le commit 14b25edc fait passer la connexion qui suit la réinitialisation par l'invite de 2FA.
- 2026-11-09 : le commit 3f62a55c ajoute le budget d'essais TOTP par utilisateur et le durcissement du jeton de réinitialisation.
- 2026-11-10 : listmonk 7.0.0-beta.2 est publié avec les deux correctifs.
- 2026-11-13 : VulnCheck publie CVE-2026-128333 et CVE-2026-128332.
FAQ
Cette faille concerne-t-elle toutes les installations de listmonk ?
Non. Seules les versions 6.0.0 à 6.2.0 et 7.0.0-beta.1 sont concernées, et seuls les comptes dont la 2FA est activée exposent un second facteur à contourner. Une installation sans 2FA reste exposée au vol de mot de passe.
Existe-t-il un correctif pour la branche 6.x ?
Non. Le mainteneur a corrigé les deux failles dans 7.0.0-beta.2, et aucune version 6.x ne les contient. La dernière version stable, 6.2.0, a été publiée le 26 juin 2026.
Dois-je exécuter une bêta ?
Pour disposer d'une version corrigée, oui. La version 7.0.0-beta.2 est une bêta avec des changements incompatibles sur les paramètres et les webhooks. Sauvegardez la base et testez-la avant une mise à jour de production. Une limitation de débit sur le reverse proxy et une restriction réseau sur /admin réduisent l'exposition mais ne ferment pas le chemin de réinitialisation.
Puis-je atténuer sans mettre à jour ?
En partie. Limitez le débit de POST /admin/login et de POST /admin/login/twofa. Restreignez aussi /admin aux adresses de confiance. Cela ralentit la force brute. Le contournement par réinitialisation reste, car le code vulnérable s'exécute après une réinitialisation réussie et ne demande jamais de code.
Dois-je changer les identifiants après la mise à jour ?
Si vous ne pouvez pas exclure une compromission, oui. Réinitialisez les mots de passe des administrateurs, changez les secrets des paramètres comme les identifiants SMTP, et déconnectez toutes les sessions. Les failles donnent une session à l'attaquant, pas un implant persistant.
Combien d'essais un attaquant obtient-il ?
Quinze par connexion par mot de passe, et une nouvelle connexion crée un nouveau jeton. Un code TOTP à six chiffres a 1 000 000 de valeurs possibles : l'attaquant doit donc multiplier les connexions, mais l'application n'arrête pas la répétition.
Références
- Enregistrement CVE-2026-128332, force brute du TOTP via le point de 2FA : https://www.cve.org/CVERecord?id=CVE-2026-128332 (vérifié le 13 novembre 2026)
- Enregistrement CVE-2026-128333, contournement de la 2FA via le flux de réinitialisation : https://www.cve.org/CVERecord?id=CVE-2026-128333 (vérifié le 13 novembre 2026)
- Avis VulnCheck pour CVE-2026-128332, avec la plage concernée et le crédit : https://www.vulncheck.com/advisories/listmonk-6.0.0-before-7.0.0-beta.2-totp-brute-force-via-admin-login-twofa (vérifié le 13 novembre 2026)
- Avis VulnCheck pour CVE-2026-128333, avec la plage concernée et le crédit : https://www.vulncheck.com/advisories/listmonk-6.0.0-before-7.0.0-beta.2-2fa-bypass-via-password-reset-flow (vérifié le 13 novembre 2026)
- Avis GitHub GHSA-p42g-4rv2-xg2f pour CVE-2026-128332 : https://github.com/advisories/GHSA-p42g-4rv2-xg2f (vérifié le 13 novembre 2026)
- Avis GitHub GHSA-h254-fw2f-gggx pour CVE-2026-128333 : https://github.com/advisories/GHSA-h254-fw2f-gggx (vérifié le 13 novembre 2026)
- Commit correctif 14b25edc, qui envoie la connexion suivant la réinitialisation vers l'invite de 2FA : https://github.com/knadh/listmonk/commit/14b25edc31614a1d661e3af72b3d46b887931534 (vérifié le 13 novembre 2026)
- Commit correctif 3f62a55c, qui ajoute le budget d'essais TOTP partagé : https://github.com/knadh/listmonk/commit/3f62a55c13dbc679b29238e890e4b90d6d4996d8 (vérifié le 13 novembre 2026)
- Publication listmonk 7.0.0-beta.2, avec les commandes de mise à jour et les changements incompatibles : https://github.com/knadh/listmonk/releases/tag/v7.0.0-beta.2 (vérifié le 13 novembre 2026)
- Publication listmonk 6.2.0, la dernière version stable avant les correctifs : https://github.com/knadh/listmonk/releases/tag/v6.2.0 (vérifié le 13 novembre 2026)
- Code vulnérable de
tmptokens.ChecketSetau tag 7.0.0-beta.1 : https://github.com/knadh/listmonk/blob/v7.0.0-beta.1/internal/tmptokens/tmptokens.go (vérifié le 13 novembre 2026) - Code vulnérable de
doResetPasswordetdoLoginau tag 7.0.0-beta.1 : https://github.com/knadh/listmonk/blob/v7.0.0-beta.1/cmd/auth.go (vérifié le 13 novembre 2026) - Page Docker Hub de listmonk, source du nombre de téléchargements : https://hub.docker.com/r/listmonk/listmonk (vérifié le 13 novembre 2026)
- Dépôt listmonk, source du nombre d'étoiles : https://github.com/knadh/listmonk (vérifié le 13 novembre 2026)
- Catalogue Known Exploited Vulnerabilities de la CISA, consulté pour les deux CVE : https://www.cisa.gov/known-exploited-vulnerabilities-catalog (vérifié le 13 novembre 2026, version du catalogue 2026.11.10)