Vikunja, Business Logic Flaw, CVE-2026-28268 (Critical)

Listen to this Post

The vulnerability in Vikunja versions prior to 2.1.0 stems from two distinct logic errors in the password reset mechanism, allowing indefinite token reuse. First, in the `ResetPassword` function (pkg/user/user_password_reset.go), the code correctly updates the user’s password but fails to invalidate the specific `TokenPasswordReset` used for the request. Instead, it erroneously attempts to delete a different token type, TokenEmailConfirm, leaving the actual reset token untouched and still valid in the database. Second, the background cron job intended to clean up old tokens contains an inverted comparison operator. In pkg/user/token.go, the SQL query `Where(“created > ? …”, time.Now().Add(time.Hour24-1), …)` uses a “greater than” (>) condition. This logic deletes tokens created after the cutoff time (i.e., newer tokens) while preserving tokens older than 24 hours. Consequently, a password reset token, once generated, is never invalidated upon use and is never deleted by the broken cron job, rendering it perpetually valid. An attacker who intercepts a single token can use it at any future time to reset the victim’s password and achieve persistent account takeover .
Platform: Vikunja
Version: < 2.1.0
Vulnerability : Business Logic
Severity: Critical
date: Feb 27, 2026

Prediction: Already Patched (v2.1.0)

What Undercode Say:

Analytics

The vulnerability is caused by two independent programming errors in the `go-vikunja/vikunja` repository. The first is a copy-paste error in the token deletion logic, and the second is a logical mistake in a cron job query.

Vulnerable Code Snippet (No Invalidation):

// File: pkg/user/user_password_reset.go (Lines 36-94)
// FLAW: Deletes 'TokenEmailConfirm' instead of the current 'TokenPasswordReset'
err = removeTokens(s, user, TokenEmailConfirm)
if err != nil {
return
}
// The reset token is never removed and remains valid in the DB.

Vulnerable Code Snippet (Inverted Expiry):

// File: pkg/user/token.go (Lines 125-151)
// 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.Hour24-1),
TokenPasswordReset, TokenAccountDeletion).
Delete(&Token{})

Corrected Code (Invalidation):

// Recommended Fix: Use correct token kind
err = removeTokens(s, user, TokenPasswordReset) // Correct TokenKind

Corrected Code (Cleanup):

// Recommended Fix: Use Less Than (<) to target old tokens
Where("created < ? ...", time.Now().Add(time.Hour24-1), ...)

Detection Query (SQL for token table):

-- Find password reset tokens older than 24 hours that should have been deleted
SELECT FROM tokens
WHERE kind = 'TokenPasswordReset'
AND created < NOW() - INTERVAL '24 hours';

How Exploit:

  1. Intercept Token: The attacker first needs a valid password reset token for a target user. This can be obtained by viewing the victim’s browser history, accessing server logs, or using a phishing link that contains the token.
  2. Craft Request: The attacker prepares a request to the password reset endpoint, providing the stolen token and a new password of their choice.
  3. Execute Takeover: The attacker sends the request to the Vikunja API. Since the token is still valid (never invalidated) and the system accepts it, the password is changed.
  4. Maintain Persistence: The attacker can now log in with the new password. If the victim somehow regains access and changes the password again, the attacker can repeat step 3 using the same old, perpetually valid token to take over the account again immediately.

Proof of Concept (cURL command):

Assume the stolen token is "stolen-reset-token-value-123"
Assume the API endpoint is https://vikunja-instance.com/api/v1/password/reset
curl -X POST https://vikunja-instance.com/api/v1/password/reset \
-H "Content-Type: application/json" \
-d '{
"token": "stolen-reset-token-value-123",
"new_password": "AttackerNewPassword123!"
}'

Protection from this CVE

  1. Immediate Patching: Upgrade Vikunja to version 2.1.0 or later immediately .
    If using Docker
    docker pull vikunja/api:2.1.0
    docker-compose up -d
    If using binary
    wget https://dl.vikunja.io/vikunja/2.1.0/vikunja-2.1.0-linux-amd64.zip
    unzip vikunja-2.1.0-linux-amd64.zip
    Replace old binary
    
  2. Manual Mitigation (If cannot patch): As a temporary workaround, manually delete all rows from the `tokens` table where `kind` is `TokenPasswordReset` to invalidate all existing reset tokens.
    -- Connect to the Vikunja database and run:
    DELETE FROM tokens WHERE kind = 'TokenPasswordReset';
    
  3. Code Fix (Self-Hosted Development): Apply the two code changes mentioned in the Analytics section to your local fork: change `TokenEmailConfirm` to `TokenPasswordReset` in the `ResetPassword` function, and change the `>` operator to `<` in the cron job's SQL query .

Impact

Account Takeover: Complete and persistent compromise of any Vikunja user account .
Data Breach: Unauthorized access to all tasks, projects, files, and sensitive information managed within the compromised account.
Privilege Escalation: If the compromised account is an administrator, the attacker gains full control over the entire Vikunja instance, including user management and system configuration.
Bypass of Security Controls: Standard authentication mechanisms like username/password are rendered useless, as the attacker can always override them with a single, eternally valid token.
Supply Chain Risk: Organizations using Vikunja for internal project management risk exposing proprietary plans and sensitive communications.

🎯Let’s Practice Exploiting & Learn Patching For Free:

Sources:

Reported By: nvd.nist.gov
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top