Feb 28, 2026·6 min read·3 visits
Vikunja versions < 2.1.0 fail to delete password reset tokens after use and fail to clean up expired tokens due to a logic bug in the cron job. This allows valid tokens to be reused indefinitely for account takeover. Fixed in v2.1.0.
A critical authentication bypass vulnerability exists in Vikunja, an open-source task management platform, affecting versions prior to 2.1.0. The vulnerability stems from two concurrent logic errors in the password reset workflow: a failure to invalidate reset tokens upon successful use and a malformed background cleanup process that failed to purge expired tokens. These flaws allow an attacker who obtains a password reset token—via interception, logs, or history—to reuse it indefinitely to reset the target user's password, facilitating persistent account takeover. The issue is addressed in version 2.1.0 by correcting the token deletion logic and fixing the expiration query.
Vikunja is a self-hosted, open-source task management platform written in Go. Authentication mechanisms in such systems are critical for maintaining tenant isolation and data integrity. CVE-2026-28268 identifies a catastrophic failure in the application's password recovery workflow, specifically within the pkg/user module.
The vulnerability is classified as a business logic error involving incomplete cleanup (CWE-459) and weak password recovery mechanisms (CWE-640). Normally, a password reset token functions as a one-time nonce: once a user successfully resets their password, the token must be invalidated immediately to prevent replay attacks. Additionally, tokens should have a short lifespan enforced by a background process.
In affected versions of Vikunja, neither of these safety mechanisms functioned correctly. The application failed to delete the specific password reset token after use, and the background cleanup job contained an inverted logic operator that preserved expired tokens indefinitely. Consequently, a single leaked token grants an attacker a permanent backdoor into the victim's account, bypassing all subsequent authentication checks.
The vulnerability is the result of two distinct implementation errors in the Go backend that compounded to create a persistent threat.
1. Incorrect Token Invalidation Target
The primary flaw resides in pkg/user/user_password_reset.go. The ResetPassword function is responsible for verifying the token, updating the user's password, and performing cleanup. However, the cleanup call was misconfigured. Instead of requesting the deletion of TokenPasswordReset type tokens, the code instructed the system to delete TokenEmailConfirm tokens. This logic error meant that while the password was successfully changed, the reset token remained active in the database, marked as valid.
2. Inverted Expiration Logic
A secondary but equally critical flaw existed in the background cron job responsible for database hygiene, located in pkg/user/token.go. The SQL query intended to delete expired tokens used an inverted comparison operator (> instead of <). Instead of deleting tokens where the expiration date was in the past, the system attempted to delete tokens where the expiration date was in the future. This effectively preserved all old, expired tokens while potentially interfering with valid, newly created tokens. This ensured that even if a token 'expired' logically according to its timestamp, it was never physically removed from the database, allowing the validation logic (which presumably checked existence first) to potentially accept it depending on how strict the read-time expiration check was implemented.
The remediation of this vulnerability provides a clear view of the logic error. The fix was applied in pkg/user/user_password_reset.go within the ResetPassword function. The code below illustrates the critical difference between the vulnerable and patched states.
Vulnerable Code (Before Patch):
In the vulnerable version, the developer inadvertently passed the constant TokenEmailConfirm to the removal function. This constant maps to a different integer value than the password reset token type, leaving the actual reset token untouched in the persistence layer.
// pkg/user/user_password_reset.go
// ... password update logic ...
// CRITICAL BUG: Removes email confirmation tokens instead of reset tokens
err = removeTokens(s, user, TokenEmailConfirm)
if err != nil {
return err
}Patched Code (Version 2.1.0):
The fix involves a one-line change to target the correct token type constant (TokenPasswordReset). This ensures that once the ResetPassword function completes, the token used to authorize that request is permanently destroyed.
// pkg/user/user_password_reset.go
// ... password update logic ...
// FIX: Correctly targets the password reset token type for deletion
err = removeTokens(s, user, TokenPasswordReset)
if err != nil {
return err
}By correctly identifying the token type, the system enforces the one-time-use policy required for secure authentication workflows.
The exploitation of CVE-2026-28268 is trivial once a token is acquired, as it requires no special tooling or race conditions. The difficulty lies solely in the initial acquisition of the token.
Token Leakage Channels Since the token is part of a URL, it is susceptible to leakage via several standard channels:
Attack Lifecycle
vikunja.example.com/reset/TOKEN_STRING.The impact of this vulnerability is rated as Critical (CVSS 9.8) due to the complete bypass of authentication controls it affords.
Confidentiality Impact (High): An attacker can access all tasks, projects, and private notes stored within the Vikunja instance. In a business context, this could include proprietary roadmaps, sensitive client data, or internal operational details.
Integrity Impact (High): The attacker has full write access to the user's account. They can modify tasks, delete projects, or inject malicious content into shared workspaces, potentially affecting other users or the organization's workflow.
Availability Impact (High): The attacker can change the password and change the associated email address (if functionality permits), permanently locking the legitimate user out of their account. In a self-hosted instance without active administration, this results in a permanent denial of service for that user identity.
The only effective remediation is to upgrade the Vikunja instance to a version that includes the patch.
Immediate Action:
Upgrade to Vikunja v2.1.0 or later. This release includes the fix for the removeTokens call and corrects the background cron job logic.
Post-Upgrade Cleanup: Simply upgrading the code prevents future tokens from persisting, but it may not automatically clean up the backlog of existing, valid tokens left behind by the bug. Administrators should perform a database cleanup.
Database Cleanup Command (SQL): Execute a query against the Vikunja database to remove old reset tokens. Ensure you backup the database before running delete operations.
-- Example for PostgreSQL/MySQL
-- Delete password reset tokens (type ID usually 1 or 2, verify in code)
-- that are older than 24 hours.
DELETE FROM user_tokens
WHERE token_type = 1 -- Assuming 1 is TokenPasswordReset
AND created_at < NOW() - INTERVAL '24 HOURS';Verification: After patching, administrators should verify the fix by generating a password reset token, using it to change a password, and then inspecting the database to confirm the token row has been removed.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Vikunja go-vikunja | < 2.1.0 | 2.1.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-459 |
| Attack Vector | Network |
| CVSS Score | 9.8 |
| Remediation Level | Official Fix |
| Exploit Status | Poc Available |
| Report Confidence | Confirmed |
The software does not properly clean up sensitive data, such as a password reset token, after it has been used or has expired.