# Nginx UI: Authentication bypass: password login does not enforce a passkey-only second factor (2FA bypass)

Nginx UI's password login only enforced TOTP, so accounts with a passkey but no TOTP logged in with password alone, granting a full JWT.

- Payout: Research
- Severity: high
- Program: Nginx UI
- Found by: Voilater
- Disclosed: 2026-10-09
- CVE: CVE-2026-107808
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-45gv-9wjv-xh7p
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-45gv-9wjv-xh7p
- Tags: area:web domain:web impact:ato impact:privesc kind:research platform:ghsa sev:high tech:go technique:source-review vuln:access-control vuln:auth-bypass vuln:mfa-bypass

**Target:** POST /api/login authentication endpoint of nginx-ui web panel
**Root cause:** 2FA policy (Enabled2FA) treats OTP or passkey as valid, but the Login handler and RequireSecureSession gate only on EnabledOTP(), so passkey-only accounts skip the second factor.
**Steps:**
1. Register a passkey on a victim account and ensure no TOTP secret is set.
2. Confirm state via `GET /api/2fa_status` returning `{"enabled":true,"otp_status":false,"passkey_status":true}`.
3. Send `POST /api/login` with only the victim's name and password (encrypted params as the client normally sends).
4. Observe `HTTP 200` with `{"message":"ok","code":200,"token":"eyJ..."}` — a valid JWT without any WebAuthn assertion.
5. Compare: same account with TOTP returns `{"message":"The user has enabled 2FA","code":199}`.
**How found:** Code reading: noticed the policy definition Enabled2FA() = EnabledOTP() || EnabledPasskey() diverges from the Login handler's enforcement check u.EnabledOTP(), then verified empirically by comparing login responses for passkey-only vs TOTP accounts.
**Exploitation:** Attacker who obtains the password (phishing, reuse, leak) posts it to /api/login and receives a full session JWT despite the registered security key; since all nginx-ui users are effectively admins and the terminal feature grants a host shell, this yields control of the managed nginx instance/host.
**Escalation:** Account takeover escalates to host shell via the built-in terminal feature, as all authenticated users are administrators.
**Insight:** When a codebase defines a policy predicate (Enabled2FA) and separately hand-rolls enforcement checks, grep every enforcement site for the narrower primitive (EnabledOTP) instead of the policy predicate — mismatches silently downgrade auth. Also check step-up middleware (RequireSecureSession) for the same divergence.

