CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27575
9.10.42%

The Zombie Session: Breaking Vikunja's Auth with CVE-2026-27575

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 26, 2026·6 min read·13 visits

PoC Available

Executive Summary (TL;DR)

Vikunja versions prior to 2.0.0 allow persistent account takeover. Due to a lack of input validation, passwords could be reset to a single character. Worse, changing a password did not invalidate existing JSON Web Tokens (JWTs). An attacker with a stolen token remains logged in indefinitely, regardless of the victim's remediation attempts. Fix: Upgrade to v2.0.0 immediately.

CVE-2026-27575 represents a catastrophic failure in the authentication lifecycle of Vikunja, a popular self-hosted task management platform. The vulnerability is a two-headed beast: first, it allowed users (and attackers) to set passwords with a single character, bypassing security policies during updates. Second, and far more critical, it failed to invalidate active sessions upon password changes. This means an attacker who steals a session token retains permanent access to the victim's data, even after the victim explicitly resets their credentials to 'lock them out.' It is a classic case of stateless JWTs being deployed without a revocation strategy.

The Hook: Task Management or Task Mismanagement?

We love self-hosted software. There is something primal about owning your data, running your own infrastructure, and telling the big cloud providers to shove it. Vikunja has carved out a nice niche here as the slick, Go-based alternative to Trello or Todoist. It holds your life: your business plans, your personal goals, your grocery lists, and perhaps your sensitive API keys stored in task descriptions.

But here is the thing about 'rolling your own' auth: it is hard. Really hard. And when you decide to use stateless authentication because it scales better (spoiler: you are not Google, you do not need that scale), you walk into a minefield. CVE-2026-27575 is what happens when you step on two mines at once.

This isn't just a 'bypass'. This is a fundamental misunderstanding of how session lifecycles should interact with credential management. It turns your private task manager into a public bulletin board for anyone who grabbed your token three weeks ago.

The Flaw: A Comedy of Errors

The vulnerability is actually two distinct logic flaws that, when combined, create a perfect storm for persistence.

1. The Screen Door Password Policy (CWE-521) When you register a new account in Vikunja, the bouncer checks your ID. You need 8 characters, special symbols, the blood of a virgin, etc. But prior to version 2.0.0, if you went to the Change Password or Reset Password settings, the bouncer was on a smoke break. The API endpoints for updating credentials simply forgot to apply the validation rules. You could change your password to "a". Literally just the letter 'a'. This makes brute-forcing a specific account trivial if you know they recently rotated credentials.

2. The Zombie Session (CWE-613) This is the meat of the exploit. Vikunja used long-lived, stateless JSON Web Tokens (JWTs). The problem with stateless JWTs is that once they are signed, they are valid until they expire. The server doesn't remember issuing them; it just trusts the signature.

So, what happens when a user gets hacked, panics, and changes their password? In a sane world, all existing sessions are nuked. In Vikunja's world (pre-v2.0.0), the database updated the password hash, but the attacker's existing JWT—signed with the server's secret—remained perfectly valid. The lock on the door was changed, but the attacker was already inside the house, sleeping on the couch.

The Code: The Missing Validators

Let's look at the code. In Go, we love struct tags for validation. It's declarative and clean. But it only works if you actually put the tags there.

Here is the UserPassword struct used in the update flow before the patch:

// BEFORE: The "Trust Me Bro" Struct
type UserPassword struct {
    OldPassword string `json:"old_password"`
    NewPassword string `json:"new_password"`
}

Notice anything missing? There are no length checks. No complexity requirements. The handler would take NewPassword, bcrypt it, and shove it into the database.

Now, look at the patch in commit 89c17d3b. The developers realized they left the barn door open:

// AFTER: The "We Actually Check" Struct
type UserPassword struct {
    OldPassword string `json:"old_password"`
    NewPassword string `json:"new_password" valid:"minLength:8"` // <--- The Fix
}

But the bigger code change happened in commit 25268530. The entire session handling mechanism had to be rewritten. They moved from pure stateless JWTs to a stateful model where the token is checked against a list of valid sessions in the database.

This allowed them to implement RevokeAllSessions(user), which effectively kills the zombie tokens.

The Exploit: Immortality Achieved

Let's walk through a realistic attack scenario. You are the attacker. You have phished a Vikunja admin's credentials.

Step 1: Infiltration You log in using the stolen credentials. The server issues you a JWT valid for 30 days. You stash this token in your local storage or a raw HTTP client. You are now authenticated.

Step 2: The Setup Just for kicks, you use the broken password policy to set the user's password to "1". This ensures that if you ever do get kicked out, getting back in is a trivial brute-force exercise. But you don't stop there.

Step 3: The 'Remediation' The admin notices the password change notification. Panic ensues. They log in (using your password "1"), and immediately reset their password to a 50-character random string generated by their password manager. They sigh in relief, thinking they have secured the breach.

Step 4: The Prestige You, the attacker, sip your coffee. You send a request to /api/v1/projects using the JWT you obtained in Step 1.

Response: 200 OK.

The server checks the signature. Valid. The server checks the expiration. Valid. The server does not check if the password has changed since the token was issued. You still have full read/write access to the entire instance. You can exfiltrate data, delete backups, or modify tasks to inject XSS payloads for other users.

The Impact: Why This Matters

The impact here is High Confidentiality and High Integrity with a side of psychological horror.

For a business using Vikunja, this means that once an account is compromised, it stays compromised. There is no 'kill switch'. Even deleting the user might not immediately stop requests depending on how the caching layer handles JWT validation (though usually, user existence is checked).

This vulnerability highlights the danger of implementing authentication without considering the full lifecycle of a session. It is not enough to just issue a token; you must have a mechanism to destroy it. By failing to couple password rotation with session invalidation, Vikunja effectively broke the contract of trust between the application and the user. When a user changes their password, they expect the session to be reset. Violating that expectation leads to persistent compromises that are incredibly difficult for a standard admin to debug.

The Fix: Killing the Zombies

The fix provided in version 2.0.0 is substantial. It is not just a patch; it is a refactor.

  1. Strict Validation: The UserPassword and PasswordReset structs now enforce minLength:8 and other complexity requirements.
  2. Stateful Sessions: Vikunja now tracks sessions server-side. When a token is presented, the server verifies that the session ID inside the token actually exists in the database.
  3. Explicit Revocation: The UserChangePassword handler now includes a call to wipe all session records associated with that user ID.

How to remediate:

If you are running the Docker container, you need to pull the specific tag:

docker pull vikunja/vikunja:2.0.0
# OR
docker pull vikunja/vikunja:latest

> [!WARNING]
> This update includes database migrations. Backup your database before applying the container update. The move to stateful sessions involves schema changes.

If you cannot update immediately, your only mitigation is to manually edit the database to change the user's salt or secret (if accessible), or fully delete and recreate the compromised user account—though simply updating is far safer.

Official Patches

VikunjaVikunja v2.0.0 Release

Fix Analysis (2)

Technical Appendix

CVSS Score
9.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
EPSS Probability
0.42%

Affected Systems

Vikunja < 2.0.0

Affected Versions Detail

Product
Affected Versions
Fixed Version
Vikunja
Vikunja
< 2.0.02.0.0
AttributeDetail
CWE IDsCWE-521 (Weak Password), CWE-613 (Insufficient Session Expiration)
CVSS Score9.1 (Critical)
Attack VectorNetwork (API)
Privileges RequiredNone (for initial access via weak policy logic)
Exploit StatusPoC Available / Trivial
Patch Date2026-02-25

MITRE ATT&CK Mapping

T1110Brute Force
Credential Access
T1098Account Manipulation
Persistence
T1550Use Alternate Authentication Material: Application Access Token
Defense Evasion
CWE-613
Insufficient Session Expiration

Known Exploits & Detection

NucleiDetection Template Available

References & Sources

  • [1]GHSA Advisory
  • [2]Session Migration Docs

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.