
Persistencia de sesión de listmonk después del restablecimiento y cambio de contraseña
Persistencia de sesión de listmonk tras el restablecimiento y cambio de contraseña
Encontré este problema al revisar listmonk, un gestor de boletines y listas de correo de código abierto, con una simple pregunta de seguridad en mente:
Cuando un usuario cambia o restablece una contraseña, ¿la aplicación realmente termina las sesiones ya emitidas?
En este caso, la respuesta fue no.
Las sesiones autenticadas emitidas anteriormente siguieron siendo válidas después de:
Eso significaba que una cookie de sesión robada podía sobrevivir a los mismos eventos de seguridad en los que los usuarios confían para recuperar su cuenta.
El problema fue aceptado y se le asignó CVE-2026-34828.
Proyecto: listmonk en GitHub
CVE: CVE-2026-34828
Esto afectó a listmonk, un proyecto ampliamente adoptado con más de 5M de pulls de Docker.
sesión autenticada robada → la víctima restablece o cambia la contraseña → la sesión antigua sigue siendo válida → el atacante conserva el acceso a la cuenta tras la recuperación de credenciales
listmonk es un gestor de listas de correo y boletines autohospedado.
Ofrece:
Eso significa que su modelo de sesión es un límite de seguridad real.
La pregunta importante aquí no era si listmonk soporta el restablecimiento de contraseña.
La pregunta real era:
¿El restablecimiento o el cambio de contraseña revocan realmente la persistencia del atacante si ya se ha robado una sesión?
En este caso, no lo hacía.
Muchas revisiones de seguridad se centran demasiado en los bypass de inicio de sesión y en la escalada de privilegios obvia.
Eso pasa por alto una clase importante de debilidades:
fallos de recuperación
Si un usuario cambia o restablece una contraseña, se supone que esa acción debe ser significativa.
Se supone que debe reducir la confianza en credenciales antiguas y en el estado de autenticación previo.
Si un atacante ya tiene una sesión válida y esa sesión sobrevive al evento de recuperación, entonces la víctima no ha recuperado realmente la cuenta por completo.
Ese era el problema aquí.
Esto no era un fallo de validación de inicio de sesión. No era un problema de criptografía. No era un fallo en el hash de contraseñas.
Era un fallo del ciclo de vida de la sesión:
Eso es suficiente para crear una vulnerabilidad real.
No abordé listmonk golpeando endpoints al azar esperando que alguno cediera.
El camino más sólido era identificar primero el límite de confianza de mayor valor.
Para software con un fuerte componente de autenticación, uno de los mejores límites para probar es este:
¿Los cambios de cuenta sensibles para la seguridad revocan las sesiones previamente confiables?
Esa pregunta suele volverse interesante en torno a:
En listmonk, la señal más fuerte provino de los dos primeros.
Ahí es donde el problema se volvió evidente.
El fallo no era que los cambios de contraseña fallaran.
El fallo era que las sesiones les sobrevivían.
Según la revisión del código fuente, el flujo de restablecimiento de contraseña:
pero no había una revocación visible de las sesiones anteriores.
El mismo patrón aparecía en el flujo autenticado de cambio de contraseña:
Ese comportamiento coincidía exactamente con los resultados en vivo.
Las áreas de código relevantes que revisé fueron:
cmd/auth.go para el comportamiento de olvido/restablecimientocmd/users.go para actualizaciones autenticadas del perfilinternal/core/users.go para el manejo de la actualización de contraseñaPorque el robo de sesión es una condición de ataque real.
Una vez que un atacante obtiene una cookie de sesión autenticada válida por cualquier medio, como:
la víctima debería poder terminar esa persistencia del atacante cambiando o restableciendo la contraseña.
Aquí, no podía.
La cadena de ataque era sencilla:
Esa es toda la vulnerabilidad.
La distinción importante es la persistencia después de la recuperación.
Muchas aplicaciones tratan el cambio de contraseña como un evento puramente a nivel de credenciales. Eso no es suficiente.
La pregunta real no es:
"¿Cambió el valor de la contraseña en el almacenamiento?"
La pregunta real es:
"¿Se revocó la relación de confianza asociada a las sesiones antiguas?"
En listmonk, no se revocó.
Eso convierte lo que podría haber sido un mantenimiento ordinario de la cuenta en una recuperación de seguridad incompleta.
Esa es la diferencia entre:
Validé el problema en dos flujos separados.
Primero, creé un usuario de prueba normal e inicié sesión, guardando la cookie de sesión autenticada.
Luego activé el flujo de contraseña olvidada, capturé el enlace de restablecimiento y restablecí la contraseña.
Después del restablecimiento:
Una solicitud de validación representativa tenía este aspecto:
GET /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<old_pre_reset_session>
Y el servidor seguía devolviendo:
HTTP/1.1 200 OK
Content-Type: application/json
con el perfil autenticado.
Eso confirmó la afirmación principal:
Luego validé la misma clase de fallo en el flujo autenticado de cambio de contraseña.
Inicié sesión dos veces como el mismo usuario y guardé dos sesiones autenticadas válidas:
Usando la sesión A, cambié la contraseña a través del endpoint de actualización de perfil.
Ejemplo de solicitud:
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"
}
Después de eso: