Omisión de la 2FA de Listmonk, corregida solo en la beta 7.0 (CVE-2026-128332)
listmonk 6.0.0 a 6.2.0 permite que un atacante con una contraseña omita la 2FA TOTP; la única versión corregida es 7.0.0-beta.2.
Resumen
- Dos fallos en listmonk anulan la autenticación de dos factores TOTP: uno se salta el código después de un restablecimiento de contraseña y el otro permite adivinar códigos sin límite. VulnCheck publicó CVE-2026-128333 y CVE-2026-128332 el 13 de noviembre de 2026.
- listmonk 6.0.0 a 6.2.0 y 7.0.0-beta.1 están afectados. Ambos fallos necesitan una contraseña válida o un restablecimiento completado, y una cuenta con TOTP activado.
- La puntuación es CVSS v4.0 9.1 Crítica y CVSS v3.1 7.4 Alta. No se conoce explotación y ninguna de las dos CVE está en el catálogo KEV de CISA a fecha del 13 de noviembre de 2026.
- Actualice a 7.0.0-beta.2, la única versión con las dos correcciones. La rama 6.x no tiene parche; si no puede pasar a la beta, limite la tasa de los endpoints de inicio de sesión de administración. Consulte «¿Cómo lo corrijo?».
¿Estoy afectado?
Está expuesto cuando se cumplen todas estas condiciones:
- Ejecuta listmonk 6.0.0 o posterior, hasta 6.2.0 incluida, o 7.0.0-beta.1.
- Al menos una cuenta, especialmente una de administración, tiene activada la autenticación de dos factores TOTP.
- Para CVE-2026-128332, el atacante conoce la contraseña de esa cuenta. Para CVE-2026-128333, el atacante puede completar un restablecimiento de contraseña para ella, por ejemplo leyendo su correo.
- La interfaz
/admines accesible para el atacante.
listmonk 5.x y anteriores quedan fuera del rango afectado por el aviso. Una cuenta sin TOTP no tiene un segundo factor que omitir, así que el fallo de restablecimiento deja solo la contraseña como barrera. El fallo de fuerza bruta necesita un código de seis dígitos que adivinar y una contraseña para empezar.
Solo una de las dos ramas tiene una versión corregida. La beta 7.0 está corregida; la rama 6.x no tiene retroportación.
| Rama | Versiones afectadas | Versión corregida |
|---|---|---|
| 6.x | 6.0.0 a 6.2.0 | Ninguna; la corrección solo está en 7.0.0-beta.2 |
| Beta 7.0 | 7.0.0-beta.1 | 7.0.0-beta.2 (10 de noviembre de 2026) |
Compruebe la versión en ejecución. El binario y la imagen oficial de Docker responden ambos a --version:
# Binary install
/path/to/listmonk --version
# Docker
docker exec <container> /listmonk/listmonk --version
Liste las cuentas que tienen la 2FA activada. Ejecute esta consulta sobre la base de datos 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 resultado vacío significa que ninguna cuenta usa TOTP, y ninguno de los dos fallos da al atacante más de lo que ya da la contraseña.
¿Cómo lo corrijo?
Actualice a 7.0.0-beta.2. Es la única versión que contiene las dos correcciones.
- Haga una copia de seguridad de la base de datos antes de empezar. La versión 7.0 cambia los ajustes y la interfaz de administración, y el proyecto pide una copia en sus notas de actualización:
docker exec -i <postgres-container> pg_dump -U listmonk listmonk > listmonk-backup.sql - Con Docker, cambie la etiqueta de la imagen en el archivo compose de
listmonk/listmonk:latestalistmonk/listmonk:betay ejecute la actualización:docker compose down docker compose pull docker compose run --rm app ./listmonk --upgrade docker compose up -d app db - Para una instalación con binario, detenga listmonk. Sustituya el binario por la versión 7.0.0-beta.2 de la página de la publicación. Ejecute la actualización de la base de datos e inicie listmonk de nuevo:
./listmonk --upgrade ./listmonk --version - Para una instalación desde el código fuente, cambie a la etiqueta
v7.0.0-beta.2y compile el binario con el Makefile del proyecto. Ejecute--upgradedespués de compilar, como en el paso 3. - Pruebe el inicio de sesión de administración después de la actualización, incluida la solicitud de la 2FA. Después confirme que la versión en ejecución muestra 7.0.0-beta.2.
La versión 7.0 es una beta con cambios incompatibles. La interfaz de administración se reescribe, la API de ajustes cambia la forma de app.from_email, y los webhooks de SES y Azure cambian sus requisitos. Lea las notas de la publicación antes de actualizar una instalación de producción y pruebe primero en un entorno de staging.
Si no puede pasar a la beta, limite la tasa de los dos endpoints de inicio de sesión en el proxy inverso y restrinja /admin a redes de confianza. Un límite sobre POST /admin/login y POST /admin/login/twofa frena la fuerza bruta. No cierra la omisión por restablecimiento, porque esa ruta nunca pide un código.
¿Cuál es la causa raíz?
listmonk guarda sus tokens de autenticación de corta duración en un mapa en memoria del paquete internal/tmptokens. El flujo de inicio de sesión de administración en cmd/auth.go usa ese mapa tanto para el token de restablecimiento como para el token de 2FA. Los dos fallos vienen de cómo se cuentan y se consumen esos tokens.
El fallo de restablecimiento está en doResetPassword. Después de un cambio de contraseña correcto, la función borra las sesiones del usuario y crea directamente una sesión nueva:
// 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 función nunca lee user.TwofaType. Un usuario que restablece la contraseña inicia sesión de inmediato, incluso cuando la cuenta tiene TOTP activado. Por tanto, un atacante que puede completar un restablecimiento obtiene una sesión completa sin código.
El fallo de fuerza bruta está en el contador de tokens. TwofaPage llama a tmptokens.Check(token) en cada petición a /admin/login/twofa. Check incrementa un contador propio del token y borra el token cuando la cuenta supera 15:
// Increment the rate limit counter.
token.Count++
// Check if max attempts exceeded.
if token.Count > maxTries {
delete(tokens, id)
return nil, Err
}
Ese límite parece una limitación de tasa, pero está ligado al token, no al usuario. doLogin crea un token nuevo en cada inicio de sesión con contraseña:
// Set the token.
tmptokens.Set(token, twofaTokenTTL, user.ID)
Set crea un Token nuevo con Count a cero, y el endpoint de inicio de sesión no tiene un tope de intentos propio. Un atacante con la contraseña puede pedir un token nuevo después de cada serie de 15 intentos, así que el contador vuelve a cero una y otra vez.
¿Cómo se explota?
La ruta de restablecimiento necesita un restablecimiento de contraseña completado. La ruta de fuerza bruta necesita la contraseña de la cuenta. Ambas terminan en una sesión para una cuenta protegida por TOTP.
CVE-2026-128333, la omisión por restablecimiento:
- El atacante pide a listmonk que envíe un correo de restablecimiento para la cuenta objetivo, u obtiene de otra forma un enlace de restablecimiento ya enviado.
- El atacante abre el enlace y envía una contraseña nueva a
/admin/resetcon el token recibido por correo. doResetPasswordverifica el token, actualiza la contraseña y borra las sesiones existentes.- La misma función llama directamente a
SaveSession, así que la respuesta establece una cookie de sesión sin pedir el TOTP. - El atacante tiene ahora una sesión autenticada como esa cuenta.
CVE-2026-128332, la fuerza bruta del TOTP:
- El atacante envía el nombre de usuario y la contraseña conocidos a
POST /admin/login. - listmonk acepta la contraseña, crea un token de 2FA con el contador a cero y redirige a
/admin/login/twofa. - El atacante envía hasta 15 códigos a
POST /admin/login/twofa. El token se borra después del decimoquinto intento. - El atacante repite desde el paso 1. Cada inicio de sesión con contraseña crea un token nuevo y otros 15 intentos.
- Un código correcto crea la sesión y el atacante se detiene.
No se ha publicado ninguna prueba de concepto pública, y ninguna de las dos rutas necesita una petición malformada. El único freno en /admin/login es un tiempo de respuesta mínimo de 100 milisegundos.
¿Cómo se desarrolla el ataque?
Un atacante que conoce una contraseña, o que puede completar un restablecimiento, convierte cualquiera de las dos rutas en una sesión de administración sin código TOTP.
El bucle es el fallo de fuerza bruta: un nuevo inicio de sesión con contraseña reinicia el contador de intentos. La arista directa es el fallo de restablecimiento: el inicio de sesión posterior al restablecimiento se salta por completo la página de 2FA.
¿Qué puede hacer un atacante?
Un atacante que alcanza una sesión de administración controla la instalación de listmonk que gestiona esa cuenta. Eso incluye las listas de suscriptores y sus datos personales, las campañas, las plantillas, los ajustes y las demás cuentas de usuario. El rol Super Admin puede cambiar los ajustes de toda la instalación, incluida la configuración de envío de correo.
La ruta de restablecimiento exige que el atacante complete un restablecimiento, lo que normalmente implica leer el correo de la víctima. La ruta de fuerza bruta exige la contraseña de la cuenta y muchas peticiones de inicio de sesión.
Ambas necesitan acceso de red a /admin, y ambas funcionan solo en una cuenta con TOTP activado. Sin TOTP no hay un segundo factor que omitir, y el impacto es el mismo que el de una contraseña robada.
El aviso de VulnCheck no describe una ruta desde la sesión de administración hasta la ejecución de código en el host. Considere el impacto como el control completo de los datos y la configuración de listmonk, no del servidor.
¿Por qué funciona la corrección?
Los dos commits cierran las dos rutas. El flujo de restablecimiento pasa ahora por la misma función que un inicio de sesión normal. El contador del TOTP está ligado al usuario en lugar de a un solo token.
La corrección del restablecimiento envía el inicio de sesión posterior al restablecimiento por 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 lee user.TwofaType y, para una cuenta con TOTP, crea un token de 2FA y redirige a /admin/login/twofa en lugar de guardar una sesión. Un restablecimiento de contraseña ya no puede saltarse el código.
La corrección de la fuerza bruta da a cada usuario un presupuesto de intentos compartido:
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 crea la clave solo si no existe o ha caducado, así que un inicio de sesión nuevo no reinicia el contador. También se cambió Check para conservar un token agotado hasta su caducidad en lugar de borrarlo. Eso impide que SetNX vuelva a crear la clave del presupuesto.
El mismo commit sustituye la comparación Get del token de restablecimiento por un Match de tiempo constante. Esa comparación consume el token solo cuando el valor coincide.
Los dos commits cubren los dos avisos. Los cambios del token de restablecimiento son un endurecimiento del mismo flujo, no una tercera vulnerabilidad.
¿Cómo compruebo si estuve comprometido?
Ni VulnCheck ni el mantenedor han publicado indicadores de compromiso. Un intento fallido puede no dejar rastro, porque listmonk no registra cada comprobación fallida del TOTP.
Busque tráfico de inicio de sesión repetido en los registros del proxy inverso o del servidor web delante de listmonk. Un ataque de fuerza bruta muestra muchas peticiones POST /admin/login seguidas de peticiones POST /admin/login/twofa desde una misma dirección, y muchas redirecciones a /admin/login/twofa:
grep -E 'POST /admin/login(/twofa)?' access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
Revise la tabla de sesiones para encontrar sesiones creadas a horas en las que nadie trabajaba, y compárelas con los valores loggedin_at de los usuarios:
docker exec -i <postgres-container> psql -U listmonk -d listmonk \
-c "SELECT created_at, data FROM sessions ORDER BY created_at DESC LIMIT 20;"
Pregunte a los administradores si recibieron un correo de restablecimiento que no solicitaron. Un restablecimiento completado cambia la contraseña de la cuenta y destruye las sesiones anteriores. Un restablecimiento que nadie pidió es la señal más clara de la ruta de restablecimiento.
Cronología
- 2026-06-26: se publica listmonk 6.2.0, la última versión estable antes de las correcciones.
- 2026-11-01: el commit 14b25edc envía el inicio de sesión posterior al restablecimiento por la solicitud de 2FA.
- 2026-11-09: el commit 3f62a55c añade el presupuesto de intentos TOTP por usuario y el endurecimiento del token de restablecimiento.
- 2026-11-10: se publica listmonk 7.0.0-beta.2 con las dos correcciones.
- 2026-11-13: VulnCheck publica CVE-2026-128333 y CVE-2026-128332.
Preguntas frecuentes
¿Afecta esto a todas las instalaciones de listmonk?
No. Solo están afectadas las versiones 6.0.0 a 6.2.0 y 7.0.0-beta.1, y solo las cuentas con TOTP tienen un segundo factor que omitir. Una instalación sin 2FA sigue expuesta al robo de contraseña.
¿Hay una corrección para la rama 6.x?
No. El mantenedor corrigió los dos fallos en 7.0.0-beta.2, y ninguna versión 6.x los contiene. La última versión estable, 6.2.0, se publicó el 26 de junio de 2026.
¿Tengo que ejecutar una beta?
Para tener una versión corregida, sí. La versión 7.0.0-beta.2 es una beta con cambios incompatibles en los ajustes y los webhooks. Haga una copia de la base de datos y pruébela antes de una actualización de producción.
Un límite de tasa en el proxy inverso y una restricción de red sobre /admin reducen la exposición pero no cierran la ruta de restablecimiento.
¿Puedo mitigar sin actualizar?
En parte. Limite la tasa de POST /admin/login y POST /admin/login/twofa, y restrinja /admin a direcciones de confianza. Eso frena la fuerza bruta. La omisión por restablecimiento sigue ahí, porque el código vulnerable se ejecuta después de un restablecimiento correcto y nunca pide un código.
¿Debo rotar las credenciales después de actualizar?
Si no puede descartar una intrusión, sí. Restablezca las contraseñas de administración, rote los secretos de los ajustes como las credenciales SMTP y cierre todas las sesiones. Los fallos dan al atacante una sesión, no un implante persistente.
¿Cuántos intentos consigue un atacante?
Quince por inicio de sesión con contraseña, y cada inicio de sesión crea un token nuevo. Un código TOTP de seis dígitos tiene 1,000,000 de valores posibles. El atacante necesita muchos inicios de sesión, pero la aplicación no detiene la repetición.
Referencias
- Registro de CVE-2026-128332, fuerza bruta del TOTP a través del endpoint de 2FA: https://www.cve.org/CVERecord?id=CVE-2026-128332 (verificado el 13 de noviembre de 2026)
- Registro de CVE-2026-128333, omisión de la 2FA a través del flujo de restablecimiento: https://www.cve.org/CVERecord?id=CVE-2026-128333 (verificado el 13 de noviembre de 2026)
- Aviso de VulnCheck para CVE-2026-128332, con el rango afectado y el crédito: https://www.vulncheck.com/advisories/listmonk-6.0.0-before-7.0.0-beta.2-totp-brute-force-via-admin-login-twofa (verificado el 13 de noviembre de 2026)
- Aviso de VulnCheck para CVE-2026-128333, con el rango afectado y el crédito: https://www.vulncheck.com/advisories/listmonk-6.0.0-before-7.0.0-beta.2-2fa-bypass-via-password-reset-flow (verificado el 13 de noviembre de 2026)
- Aviso de GitHub GHSA-p42g-4rv2-xg2f para CVE-2026-128332: https://github.com/advisories/GHSA-p42g-4rv2-xg2f (verificado el 13 de noviembre de 2026)
- Aviso de GitHub GHSA-h254-fw2f-gggx para CVE-2026-128333: https://github.com/advisories/GHSA-h254-fw2f-gggx (verificado el 13 de noviembre de 2026)
- Commit de corrección 14b25edc, que envía el inicio de sesión posterior al restablecimiento a la solicitud de 2FA: https://github.com/knadh/listmonk/commit/14b25edc31614a1d661e3af72b3d46b887931534 (verificado el 13 de noviembre de 2026)
- Commit de corrección 3f62a55c, que añade el presupuesto de intentos TOTP compartido: https://github.com/knadh/listmonk/commit/3f62a55c13dbc679b29238e890e4b90d6d4996d8 (verificado el 13 de noviembre de 2026)
- Publicación de listmonk 7.0.0-beta.2, con los comandos de actualización y los cambios incompatibles: https://github.com/knadh/listmonk/releases/tag/v7.0.0-beta.2 (verificado el 13 de noviembre de 2026)
- Publicación de listmonk 6.2.0, la última estable antes de las correcciones: https://github.com/knadh/listmonk/releases/tag/v6.2.0 (verificado el 13 de noviembre de 2026)
- Código vulnerable de
tmptokens.CheckySeten la etiqueta 7.0.0-beta.1: https://github.com/knadh/listmonk/blob/v7.0.0-beta.1/internal/tmptokens/tmptokens.go (verificado el 13 de noviembre de 2026) - Código vulnerable de
doResetPasswordydoLoginen la etiqueta 7.0.0-beta.1: https://github.com/knadh/listmonk/blob/v7.0.0-beta.1/cmd/auth.go (verificado el 13 de noviembre de 2026) - Página de listmonk en Docker Hub, fuente del número de descargas: https://hub.docker.com/r/listmonk/listmonk (verificado el 13 de noviembre de 2026)
- Repositorio de listmonk, fuente del número de estrellas: https://github.com/knadh/listmonk (verificado el 13 de noviembre de 2026)
- Catálogo Known Exploited Vulnerabilities de CISA, consultado para las dos CVE: https://www.cisa.gov/known-exploited-vulnerabilities-catalog (verificado el 13 de noviembre de 2026, versión del catálogo 2026.11.10)