
Persistance de session de listmonk après réinitialisation du mot de passe et changement de mot de passe
Persistance de session dans listmonk après la réinitialisation et le changement de mot de passe
J'ai trouvé ce problème en analysant listmonk, un gestionnaire open-source de lettres d'information et de listes de diffusion, en gardant à l'esprit une simple question de sécurité :
Lorsqu'un utilisateur change ou réinitialise son mot de passe, l'application met-elle réellement fin aux sessions déjà émises ?
Dans ce cas, la réponse était non.
Les sessions authentifiées précédemment émises restaient valides après :
Cela signifiait qu'un cookie de session volé pouvait survivre aux événements de sécurité mêmes sur lesquels les utilisateurs comptent pour récupérer leur compte.
Le problème a été accepté et s'est vu attribuer le CVE-2026-34828.
Projet : listmonk sur GitHub
CVE : CVE-2026-34828
Cela touchait listmonk, un projet largement adopté avec 5M+ téléchargements Docker.
stolen authenticated session → victim resets or changes password → old session remains valid → attacker retains account access after credential recovery
listmonk est un gestionnaire auto-hébergé de listes de diffusion et de lettres d'information.
Il fournit :
Cela signifie que son modèle de session constitue une véritable frontière de sécurité.
La question importante ici n'était pas de savoir si listmonk prend en charge la réinitialisation du mot de passe.
La vraie question était :
La réinitialisation ou le changement de mot de passe révoque-t-il réellement la persistance de l'attaquant si une session a déjà été volée ?
Dans ce cas, non.
Beaucoup de revues de sécurité se concentrent de manière trop étroite sur les contournements de connexion et les escalades de privilèges évidentes.
Elles passent à côté d'une classe importante de faiblesses :
les échecs de récupération
Si un utilisateur change ou réinitialise son mot de passe, cette action est censée avoir un sens.
Elle est censée réduire la confiance accordée aux anciens identifiants et à l'ancien état d'authentification.
Si un attaquant dispose déjà d'une session valide et que cette session survit à l'événement de récupération, alors la victime n'a pas réellement récupéré son compte en totalité.
C'était le problème ici.
Ce n'était pas un bug de validation de connexion. Ce n'était pas un problème cryptographique. Ce n'était pas une défaillance du hachage des mots de passe.
C'était une défaillance du cycle de vie des sessions :
C'est suffisant pour créer une véritable vulnérabilité.
Je n'ai pas abordé listmonk en frappant des points de terminaison au hasard en espérant que l'un d'eux cède.
La voie la plus solide consistait à identifier d'abord la frontière de confiance à plus haute valeur.
Pour les logiciels fortement orientés authentification, l'une des meilleures frontières à tester est celle-ci :
Les modifications de compte sensibles à la sécurité révoquent-elles les sessions précédemment approuvées ?
Cette question devient généralement intéressante autour de :
Dans listmonk, le signal le plus fort provenait des deux premiers.
C'est là que le problème est devenu clair.
Le bug n'était pas que les changements de mot de passe échouaient.
Le bug était que les sessions leur survivaient.
D'après la revue du code source, le flux de réinitialisation du mot de passe :
mais aucune révocation visible des sessions plus anciennes n'était effectuée.
Le même schéma apparaissait dans le flux de changement de mot de passe authentifié :
Ce comportement correspondait exactement aux résultats constatés en conditions réelles.
Les portions de code pertinentes que j'ai examinées étaient :
cmd/auth.go pour le comportement lié à l'oubli et à la réinitialisation du mot de passecmd/users.go pour les mises à jour de profil authentifiéesinternal/core/users.go pour la gestion de la mise à jour du mot de passeParce que le vol de session est une condition d'attaque réelle.
Dès qu'un attaquant obtient un cookie de session authentifié valide par quelque moyen que ce soit, tel que :
la victime devrait pouvoir mettre fin à cette persistance de l'attaquant en changeant ou en réinitialisant son mot de passe.
Ici, elle ne le pouvait pas.
La chaîne d'attaque était simple :
C'est là toute la vulnérabilité.
La distinction importante est la persistance après la récupération.
Beaucoup d'applications traitent le changement de mot de passe comme un événement purement lié aux identifiants. Ce n'est pas suffisant.
La vraie question n'est pas :
« La valeur du mot de passe a-t-elle changé en stockage ? »
La vraie question est :
« La relation de confiance attachée aux sessions plus anciennes a-t-elle été révoquée ? »
Dans listmonk, non.
Cela transforme ce qui aurait pu être une maintenance de compte ordinaire en une récupération de sécurité incomplète.
C'est la différence entre :
J'ai validé le problème dans deux flux distincts.
Tout d'abord, j'ai créé un utilisateur de test normal et je me suis connecté, en enregistrant le cookie de session authentifié.
Ensuite, j'ai déclenché le flux mot de passe oublié, capturé le lien de réinitialisation et réinitialisé le mot de passe.
Après la réinitialisation :
Une requête de validation représentative ressemblait à ceci :
GET /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<old_pre_reset_session>
Et le serveur renvoyait toujours :
HTTP/1.1 200 OK
Content-Type: application/json
avec le profil authentifié.
Cela établissait l'affirmation centrale :
J'ai ensuite validé la même classe de bug dans le flux de changement de mot de passe authentifié.
Je me suis connecté deux fois avec le même utilisateur et j'ai enregistré deux sessions authentifiées valides :
En utilisant la session A, j'ai changé le mot de passe via le point de terminaison de mise à jour du profil.
Exemple de requête :
PUT /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<session_A>
Content-Type: application/json
{
"name":"victim1",
"email":"[email protected]",
"password":"VictimChanged123"
}
Après cela :
Une requête de suivi utilisant la session B renvoyait toujours des données authentifiées depuis /api/profile.
Cela prouvait que le problème ne se limitait pas au flux d'oubli/réinitialisation.
Il affectait également les changements de mot de passe authentifiés normaux.
Une seule reproduction aurait déjà suffi à montrer un problème.
Mais valider les deux flux était important pour deux raisons.
Cela montrait que le bug n'était pas confiné à un unique chemin de récupération marginal.
La même propriété de sécurité échouait dans :
Cela rendait le problème plus difficile à écarter comme une simple logique métier accidentelle.
C'était clairement une faiblesse plus large de la gestion des sessions :
Cela donnait au problème un poids de sécurité beaucoup plus fort.
J'ai également testé le flux de réinitialisation sur un compte avec TOTP activé, car je voulais savoir si la réinitialisation du mot de passe affaiblirait ou contournerait silencieusement les attentes liées à la 2FA.
Ce que j'ai confirmé, c'est :
C'était une vérification de frontière utile.
Cela a correctement circonscrit le problème.
La vulnérabilité n'était pas :
Le véritable problème restait :
C'est une découverte plus propre et plus défendable.
Ce problème a été raisonnablement classé High.
L'impact clé ici est un accès non autorisé persistant après les actions de récupération de la sécurité du compte.
La classification de l'avis était :
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:NCela a du sens.
L'affirmation n'est pas qu'un attaquant peut se connecter sans identifiants à partir de rien. L'affirmation est qu'une fois qu'un attaquant a obtenu une session authentifiée valide, la victime ne peut pas entièrement mettre fin à cet accès en effectuant exactement les actions de sécurité censées récupérer le compte, à savoir la réinitialisation et le changement du mot de passe.
C'est une vulnérabilité de gestion de session réelle et défendable.
Certaines personnes sous-estiment les bugs de persistance de session en supposant que le vol de session est déjà « game over ».
C'est trop simpliste.
La vraie question est ce qui se passe après que la victime a remarqué que quelque chose ne va pas et a pris des mesures.
Si :
alors la récupération du compte est incomplète.
Ce n'est pas seulement un comportement maladroit. C'est une défaillance de sécurité dans le modèle de récupération.
Surtout sur une plateforme orientée administration, c'est un problème significatif avec un fort impact sur la confidentialité.
Le mainteneur a corrigé le problème dans le commit :
db82035
L'axe principal du correctif est exactement ce dont ce bug avait besoin :
C'est la remédiation correcte, car elle cible la véritable propriété de sécurité qui a échoué :
l'ancienne confiance doit mourir lorsque les identifiants changent
Un bon correctif pour cette classe de bug ne consiste pas à modifier la validation du mot de passe. Il s'agit de révoquer l'état de session précédemment actif attaché au compte.
C'est cela qui rétablit la récupération réelle.
Ce problème a été signalé de manière privée via le flux de signalement de sécurité de GitHub.
Le mainteneur :
CVE-2026-34828
Une chose qui est ressortie lors du traitement de l'avis de sécurité concernait la portée.
Le rapport initial incluait les deux éléments :
GitHub a initialement traité ces éléments comme des problèmes pouvant être corrigés indépendamment pour l'attribution du CVE. C'est un rappel utile que la portée de l'avis compte, même lorsque la faiblesse sous-jacente est conceptuellement similaire.
Le résultat final a été CVE-2026-34828.
La leçon clé ici est simple :
changer les identifiants ne suffit pas si l'ancienne confiance authentifiée est toujours vivante.
Beaucoup de développeurs pensent en termes de :
Ces choses comptent.
Mais la véritable frontière de sécurité est plus large :
lorsqu'un événement de compte à haut risque se produit, quel état précédemment approuvé doit cesser d'être approuvé ?
Dans ce cas, la réponse aurait dû être :
Et listmonk ne le faisait pas.
C'est le véritable enseignement.
Cette vulnérabilité ne concernait pas des payloads élaborés ou des astuces de parseur ingénieuses.
Il s'agissait de poser la bonne question sur la frontière de confiance.
Dans listmonk, le mot de passe a changé.
L'action de récupération a été menée à bien.
Mais l'ancienne session de l'attaquant vivait toujours.
C'est pourquoi cela est devenu CVE-2026-34828.
