Skip to content

Vikunja Vulnerable to Account Takeover via Password Reset Token Reuse

Critical severity GitHub Reviewed Published Feb 27, 2026 in go-vikunja/vikunja • Updated Feb 28, 2026

Package

gomod code.vikunja.io/api (Go)

Affected versions

<= 0.24.6

Patched versions

None

Description

Summary
A critical business logic vulnerability exists in the password reset mechanism of vikunja/api that allows password reset tokens to be reused indefinitely. Due to a failure to invalidate tokens upon use and a critical logic bug in the token cleanup cron job, reset tokens remain valid forever.

This allows an attacker who intercepts a single reset token (via logs, browser history, or phishing) to perform a complete, persistent account takeover at any point in the future, bypassing standard authentication controls.

Technical Analysis
The vulnerability stems from two distinct logic errors in the pkg/user/ package that confirm the tokens are never removed.

  1. Logic Error in Password Reset (No Invalidation)
    In pkg/user/user_password_reset.go, the ResetPassword function successfully updates the user's password but fails to delete the reset token used to authorize the request. Instead, it attempts to delete a TokenEmailConfirm token, leaving the TokenPasswordReset active.

Vulnerable Code: pkg/user/user_password_reset.go (Lines 36-94)

func ResetPassword(s *xorm.Session, reset *PasswordReset) (userID int64, err error) {
    // ... [Validation and User Lookup] ...

    // Hash the password
    user.Password, err = HashPassword(reset.NewPassword)
    if err != nil {
        return
    }

    // FLAW: Deletes 'TokenEmailConfirm' instead of the current 'TokenPasswordReset'
    err = removeTokens(s, user, TokenEmailConfirm)
    if err != nil {
        return
    }

    // ... [Update User Status and Return] ...
    // The reset token is never removed and remains valid in the DB.
}
  1. Logic Error in Token Cleanup (Inverted Expiry)
    The background cron job intended to expire old tokens contains an inverted comparison operator. It deletes tokens newer than 24 hours instead of older ones.

Vulnerable Code: pkg/user/token.go (Lines 125-151)

func RegisterTokenCleanupCron() {
    // ...
    err := cron.Schedule("0 * * * *", func() {
        // ...
        // FLAW: "created > ?" selects tokens created AFTER 24 hours ago.
        // This deletes NEW valid tokens and keeps OLD expired tokens forever.
        deleted, err := s.
            Where("created > ? AND (kind = ? OR kind = ?)", 
            time.Now().Add(time.Hour*24*-1), 
            TokenPasswordReset, TokenAccountDeletion).
            Delete(&Token{})
        // ...
    })
}

Impact
Persistent Account Takeover: An attacker with a single valid token can reset the victim's password an unlimited number of times.

Bypass of Remediation: Even if the victim notices suspicious activity and changes their password, the attacker can use the same old token to reset it again immediately.

Infinite Attack Window: Because the cleanup cron is broken, the token effectively has a generic TTL of "forever," allowing exploitation months or years after the token was issued.

Remediation

  1. Invalidate Token on Use
    Update ResetPassword to delete the specific reset token upon successful completion.
    // Recommended Fix err = removeTokens(s, user, TokenPasswordReset) // Correct TokenKind
  2. Fix Cleanup Logic
    Update the SQL query in RegisterTokenCleanupCron to target tokens created before the cutoff time.
    // Recommended Fix Where("created < ? ...", time.Now().Add(time.Hour*24*-1), ...) // Use Less Than (<)

A fix is available at https://github.com/go-vikunja/vikunja/releases/tag/v2.1.0

References

@kolaente kolaente published to go-vikunja/vikunja Feb 27, 2026
Published by the National Vulnerability Database Feb 27, 2026
Published to the GitHub Advisory Database Feb 28, 2026
Reviewed Feb 28, 2026
Last updated Feb 28, 2026

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(47th percentile)

Weaknesses

Incomplete Cleanup

The product does not properly clean up and remove temporary or supporting resources after they have been used. Learn more on MITRE.

Weak Password Recovery Mechanism for Forgotten Password

The product contains a mechanism for users to recover or change their passwords without knowing the original password, but the mechanism is weak. Learn more on MITRE.

CVE ID

CVE-2026-28268

GHSA ID

GHSA-rfjg-6m84-crj2

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.