VYPR
High severity7.1NVD Advisory· Published Apr 2, 2026· Updated Apr 15, 2026

CVE-2026-34828

CVE-2026-34828

Description

listmonk is a standalone, self-hosted, newsletter and mailing list manager. From version 4.1.0 to before version 6.1.0, a session management vulnerability allows previously issued authenticated sessions to remain valid after sensitive account security changes, specifically password reset and password change. As a result, an attacker who has already obtained a valid session cookie can retain access to the account even after the victim changes or resets their password. This weakens account recovery and session security guarantees. This issue has been patched in version 6.1.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/knadh/listmonkGo
>= 1.1.1-0.20241028090858-319053dd7a90, < 1.1.1-0.20260329113754-1b5e8d38c7781.1.1-0.20260329113754-1b5e8d38c778

Affected products

1
  • cpe:2.3:a:nadh:listmonk:*:*:*:*:*:*:*:*
    Range: >=4.1.0,<6.1.0

Patches

1
db82035d6193

Wipe user sessions from DB on password reset/change.

https://github.com/knadh/listmonkKailash NadhMar 23, 2026via ghsa
6 files changed · +53 10
  • cmd/auth.go+5 0 modified
    @@ -687,6 +687,11 @@ func (a *App) doResetPassword(c echo.Context, token, email string) error {
     		return echo.NewHTTPError(http.StatusInternalServerError, a.i18n.T("globals.messages.internalError"))
     	}
     
    +	// 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
    
  • cmd/users.go+14 0 modified
    @@ -173,6 +173,13 @@ func (a *App) UpdateUser(c echo.Context) error {
     	// Blank out the password hash in the response.
     	user.Password = null.String{}
     
    +	// If password was changed by admin, destroy all sessions for the given user.
    +	if u.Password.String != "" {
    +		if err := a.core.DeleteUserSessions(id, ""); err != nil {
    +			a.log.Printf("error destroying sessions on admin password change for user_id=%d: %v", id, err)
    +		}
    +	}
    +
     	// Cache the API token for in-memory, off-DB /api/* request auth.
     	if _, err := cacheUsers(a.core, a.auth); err != nil {
     		return err
    @@ -263,6 +270,13 @@ func (a *App) UpdateUserProfile(c echo.Context) error {
     		return err
     	}
     
    +	// If password was changed, destroy all existing sessions for the user except for the current one.
    +	if u.Password.String != "" {
    +		if err := a.core.DeleteUserSessions(user.ID, auth.GetSessionID(c)); err != nil {
    +			a.log.Printf("error destroying sessions after profile password change for user_id=%d: %v", user.ID, err)
    +		}
    +	}
    +
     	// Blank out the password hash in the response.
     	out.Password = null.String{}
     
    
  • internal/auth/auth.go+10 0 modified
    @@ -382,6 +382,16 @@ func (o *Auth) SaveSession(u User, oidcToken string, c echo.Context) error {
     	return nil
     }
     
    +// GetSessionID returns the current session ID from the echo context.
    +func GetSessionID(c echo.Context) string {
    +	sess, ok := c.Get(SessionKey).(*simplesessions.Session)
    +	if !ok || sess == nil {
    +		return ""
    +	}
    +
    +	return sess.ID()
    +}
    +
     // validateSession checks if the cookie session is valid (in the DB) and returns the session and user details.
     func (o *Auth) validateSession(c echo.Context) (*simplesessions.Session, User, error) {
     	// Cookie session.
    
  • internal/core/users.go+10 0 modified
    @@ -4,6 +4,7 @@ import (
     	"database/sql"
     	"encoding/json"
     	"net/http"
    +	"strconv"
     
     	"github.com/knadh/listmonk/internal/auth"
     	"github.com/knadh/listmonk/internal/utils"
    @@ -132,6 +133,15 @@ func (c *Core) SetTwoFA(id int, twofaType, twofaKey string) error {
     	return nil
     }
     
    +// DeleteUserSessions deletes all sessions for a given user ID, optionally
    +// excluding a specific session ID (to keep the current session alive).
    +func (c *Core) DeleteUserSessions(userID int, excludeID string) error {
    +	if _, err := c.q.DeleteUserSessions.Exec(strconv.Itoa(userID), excludeID); err != nil {
    +		return err
    +	}
    +	return nil
    +}
    +
     // DeleteUsers deletes a given user.
     func (c *Core) DeleteUsers(ids []int) error {
     	res, err := c.q.DeleteUsers.Exec(pq.Array(ids))
    
  • models/queries.go+11 10 modified
    @@ -117,16 +117,17 @@ type Queries struct {
     	DeleteBouncesBySubscriber   *sqlx.Stmt `query:"delete-bounces-by-subscriber"`
     	GetDBInfo                   string     `query:"get-db-info"`
     
    -	CreateUser        *sqlx.Stmt `query:"create-user"`
    -	UpdateUser        *sqlx.Stmt `query:"update-user"`
    -	UpdateUserProfile *sqlx.Stmt `query:"update-user-profile"`
    -	UpdateUserLogin   *sqlx.Stmt `query:"update-user-login"`
    -	SetUserTwoFA      *sqlx.Stmt `query:"set-user-twofa"`
    -	DeleteUsers       *sqlx.Stmt `query:"delete-users"`
    -	GetUsers          *sqlx.Stmt `query:"get-users"`
    -	GetUser           *sqlx.Stmt `query:"get-user"`
    -	GetAPITokens      *sqlx.Stmt `query:"get-api-tokens"`
    -	LoginUser         *sqlx.Stmt `query:"login-user"`
    +	CreateUser         *sqlx.Stmt `query:"create-user"`
    +	UpdateUser         *sqlx.Stmt `query:"update-user"`
    +	UpdateUserProfile  *sqlx.Stmt `query:"update-user-profile"`
    +	UpdateUserLogin    *sqlx.Stmt `query:"update-user-login"`
    +	SetUserTwoFA       *sqlx.Stmt `query:"set-user-twofa"`
    +	DeleteUsers        *sqlx.Stmt `query:"delete-users"`
    +	GetUsers           *sqlx.Stmt `query:"get-users"`
    +	GetUser            *sqlx.Stmt `query:"get-user"`
    +	GetAPITokens       *sqlx.Stmt `query:"get-api-tokens"`
    +	LoginUser          *sqlx.Stmt `query:"login-user"`
    +	DeleteUserSessions *sqlx.Stmt `query:"delete-user-sessions"`
     
     	CreateRole            *sqlx.Stmt `query:"create-role"`
     	GetUserRoles          *sqlx.Stmt `query:"get-user-roles"`
    
  • queries/users.sql+3 0 modified
    @@ -150,3 +150,6 @@ UPDATE users SET loggedin_at=NOW(), avatar=(CASE WHEN $2 != '' THEN $2 ELSE avat
     
     -- name: set-user-twofa
     UPDATE users SET twofa_type=$2::twofa_type, twofa_key=$3, updated_at=NOW() WHERE id=$1;
    +
    +-- name: delete-user-sessions
    +DELETE FROM sessions WHERE data->>'user_id' = $1 AND ($2 = '' OR id != $2);
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.