{"id":"GHSA-45gv-9wjv-xh7p","revision":null,"records":[{"revision":"ca0aced88e654fd983c149b868630eb0","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-107808","aliases":[],"title":"Nginx UI: Authentication bypass: password login does not enforce a passkey-only second factor (2FA bypass)","description":"Nginx UI is a web user interface for the Nginx web server. From 2.0.0 until 2.5.0, POST /api/login checks EnabledOTP but does not require a WebAuthn assertion when EnabledPasskey is true and no TOTP secret is configured. A passkey-only account is therefore issued a session after password verification, despite Enabled2FA reporting that the account has a second factor. An attacker who obtains the password can take over the account and reach administrative functionality without the registered passkey. This issue is fixed in version 2.5.0.","state":0,"publishedAt":"2026-10-09T15:11:44.387+00:00","modifiedAt":"2026-10-09T17:19:53.438+00:00","affected":[{"ecosystem":"Vendor product","name":"nginx-ui","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"0xJacky\",\n                    \"product\": \"nginx-ui\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 2.0.0, < 2.5.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","score":8.1,"severity":"HIGH"}],"references":["https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-45gv-9wjv-xh7p","https://github.com/0xJacky/nginx-ui/commit/95cd21b70814e5d9a48a359aa238aeea1ac97429","https://github.com/0xJacky/nginx-ui/releases/tag/v2.5.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-107808","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-107808","identifiers":["CVE-2026-107808"]},"retained":false,"historicalAffected":[]},{"revision":"481fed2c3bad421788c3a15cf423fd69","observedAt":"2026-11-07T05:38:27.012+00:00","statement":{"source":"euvd","id":"EUVD-2026-95891","aliases":["CVE-2026-107808","GHSA-45gv-9wjv-xh7p"],"title":"Nginx UI is a web user interface for the Nginx web server. From 2.0.0 until 2.5.0, POST /api/login checks EnabledOTP but does not require a WebAuthn assertion when EnabledPasskey is true and no TOTP secret is configured. A passkey-only acco…","description":"Nginx UI is a web user interface for the Nginx web server. From 2.0.0 until 2.5.0, POST /api/login checks EnabledOTP but does not require a WebAuthn assertion when EnabledPasskey is true and no TOTP secret is configured. A passkey-only account is therefore issued a session after password verification, despite Enabled2FA reporting that the account has a second factor. An attacker who obtains the password can take over the account and reach administrative functionality without the registered passkey. This issue is fixed in version 2.5.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","score":8.1,"severity":null}],"references":["https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-45gv-9wjv-xh7p","https://github.com/0xJacky/nginx-ui/commit/95cd21b70814e5d9a48a359aa238aeea1ac97429","https://github.com/0xJacky/nginx-ui/releases/tag/v2.5.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-95891","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 9, 2026, 3:11:44 PM","dateUpdated":"Oct 9, 2026, 5:19:53 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-95891","identifiers":["EUVD-2026-95891","CVE-2026-107808","GHSA-45gv-9wjv-xh7p"]},"retained":false,"historicalAffected":[]},{"revision":"d441f373ca0d4378b2f8b11c73493930","observedAt":"2026-10-25T05:17:13.11+00:00","statement":{"source":"nvd","id":"CVE-2026-107808","aliases":[],"title":"Nginx UI is a web user interface for the Nginx web server. From 2.0.0 until 2.5.0, POST /api/login checks EnabledOTP but does not require a WebAuthn assertion when EnabledPasskey is true and no TOTP secret is configured. A passkey-only acco…","description":"Nginx UI is a web user interface for the Nginx web server. From 2.0.0 until 2.5.0, POST /api/login checks EnabledOTP but does not require a WebAuthn assertion when EnabledPasskey is true and no TOTP secret is configured. A passkey-only account is therefore issued a session after password verification, despite Enabled2FA reporting that the account has a second factor. An attacker who obtains the password can take over the account and reach administrative functionality without the registered passkey. This issue is fixed in version 2.5.0.","state":0,"publishedAt":"2026-10-09T16:17:25.237+00:00","modifiedAt":"2026-10-20T15:24:07.787+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","score":8.1,"severity":"HIGH"}],"references":["https://github.com/0xJacky/nginx-ui/commit/95cd21b70814e5d9a48a359aa238aeea1ac97429","https://github.com/0xJacky/nginx-ui/releases/tag/v2.5.0","https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-45gv-9wjv-xh7p"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-107808","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Analyzed","weaknesses":["CWE-287","CWE-305","CWE-308"],"productCriteria":["cpe:2.3:a:nginxui:nginx_ui:*:*:*:*:*:*:*:*"],"sourceDates":{"published":"2026-10-09T16:17:25.237","lastModified":"2026-10-20T15:24:07.787"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-107808","identifiers":["CVE-2026-107808"]},"retained":false,"historicalAffected":[]},{"revision":"df8f5be69d1043a49f46c76c056015d3","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GHSA-45gv-9wjv-xh7p","aliases":["CVE-2026-107808","GO-2026-6704"],"title":"Nginx UI: Authentication bypass: password login does not enforce a passkey-only second factor (2FA bypass)","description":"## Summary\n\nnginx-ui supports two second-factor methods — TOTP (OTP) and WebAuthn passkeys —\nand reports an account as 2FA-enabled when **either** is configured. However, the\npassword login endpoint (`POST /api/login`) only enforces a second factor when a\n**TOTP secret** is present. An account that has registered a **passkey but no\nTOTP** is logged in after password verification alone — the passkey is never\nrequested. This silently downgrades a passkey-protected account to single-factor\n(password-only) authentication.\n\n## Details\n\nThe account's 2FA policy treats passkeys as a valid factor (`model/user.go`):\n\n```go\nfunc (u *User) EnabledOTP() bool     { return len(u.OTPSecret) != 0 }\nfunc (u *User) EnabledPasskey() bool { /* true if a passkey row exists */ }\nfunc (u *User) Enabled2FA() bool     { return u.EnabledOTP() || u.EnabledPasskey() }\n\nfunc (u *User) AfterFind(_ *gorm.DB) error {\n    u.EnabledTwoFA = u.Enabled2FA() // exposed to the UI as `enabled_2fa`\n    return nil\n}\n```\n\nBut the login handler only checks `EnabledOTP()` (`api/user/auth.go`, `Login`):\n\n```go\nu, err := user.Login(json.Name, json.Password)\n...\nif u.EnabledOTP() {                       // <-- only TOTP is enforced\n    if json.OTP == \"\" && json.RecoveryCode == \"\" {\n        c.JSON(http.StatusOK, LoginResponse{Message: \"The user has enabled 2FA\", Code: Enabled2FA}) // 199\n        user.BanIP(clientIP)\n        return\n    }\n    if _, err = user.VerifyOTP(u, json.OTP, json.RecoveryCode); err != nil { /* ... */ }\n    secureSessionID = user.SetSecureSessionID(u.ID)\n}\n\n// Passkey-only accounts fall through to here and receive a full session token:\naccessToken, err := user.GenerateJWT(u)\n```\n\nNo branch requires a WebAuthn assertion during password login when\n`EnabledPasskey()` is true. The root cause is the mismatch between the\n**policy definition** (`Enabled2FA()` = OTP **or** passkey) and the\n**enforcement check** (`EnabledOTP()` only).\n\nThe same `EnabledOTP()`-only gating in `RequireSecureSession()`\n(`internal/middleware/secure_session.go`) means passkey-only users are also\nexempted from step-up on sensitive actions.\n\n## Proof of Concept\n\nTested against nginx-ui built from source (`go build -tags unembed`) on\n`127.0.0.1:9000`, with WebAuthn configured and a victim account that has a\nregistered passkey and **no** TOTP. The vulnerable code is identical on the\nproduction `main` branch (`api/user/auth.go` `Login` gates on `u.EnabledOTP()`\nonly; verified at commit `6c86e5a`, 2026-05-17).\n\nAccount state advertised by `GET /api/2fa_status`:\n\n```json\n{\"enabled\":true,\"otp_status\":false,\"passkey_status\":true, ...}\n```\n\nAttacker logs in with **password only** (`POST /api/login`, encrypted params as\nthe client normally sends):\n\n```\nHTTP 200\n{\"message\":\"ok\",\"code\":200,\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....\"}\n```\n\n`code: 200` + a valid JWT = an authenticated session, with the passkey never\nused. For comparison, the identical account with a TOTP secret instead correctly\nreturns the second-factor challenge:\n\n```\nHTTP 200\n{\"message\":\"The user has enabled 2FA\",\"code\":199}\n```\n\n| Account state | `POST /api/login` (password only) |\n|---|---|\n| Passkey registered, no TOTP | `code 200` + JWT — **2FA NOT enforced** |\n| TOTP registered | `code 199` — 2FA challenge enforced |\n\nThis isolates the defect: the login path enforces OTP but ignores passkeys.\n\n## Impact\n\n- Any account protected **only** by a passkey is reduced to password-only\n  authentication. An attacker who obtains the password (phishing, reuse, leak)\n  gains full access despite the registered security key.\n- In nginx-ui all authenticated users are effectively administrators and the\n  terminal feature grants a host shell, so account takeover leads to full\n  control of the managed nginx instance / host.\n- Users are given a false sense of security: the UI shows the account as\n  2FA-enabled while the second factor is not enforced at login.\n\n## Remediation\n\n- Gate the second-factor decision on `u.Enabled2FA()` (not `u.EnabledOTP()`) in\n  `Login`, in both SSO callbacks, and in `RequireSecureSession()`.\n- For a passkey-only user logging in with a password, return a\n  \"passkey assertion required\" challenge and complete login only after a\n  successful WebAuthn assertion (the `begin_passkey_login` / `finish_passkey_login`\n  flow already exists and should be required as the second step).\n- Centralize session-token issuance so no login entry point can skip the 2FA\n  decision.\n\n## Affected components\n\n- `api/user/auth.go` — `Login`\n- `model/user.go` — `EnabledOTP`, `EnabledPasskey`, `Enabled2FA`, `AfterFind`\n- `api/user/2fa.go` — `get2FAStatus`\n- `internal/middleware/secure_session.go` — `RequireSecureSession`","state":0,"publishedAt":"2026-10-09T17:08:15+00:00","modifiedAt":"2026-10-19T21:41:12.3327935+00:00","affected":[{"ecosystem":"Go","name":"github.com/0xJacky/Nginx-UI","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"1.9.10-0.20250517140552-daee3ac7ade1"},{"kind":"fixed","version":"1.9.10-0.20260728074558-95cd21b70814"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-45gv-9wjv-xh7p","https://github.com/0xJacky/nginx-ui/commit/95cd21b70814e5d9a48a359aa238aeea1ac97429","https://github.com/0xJacky/nginx-ui","https://github.com/0xJacky/nginx-ui/releases/tag/v2.5.0"],"sourceUrl":"https://github.com/advisories/GHSA-45gv-9wjv-xh7p","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-45gv-9wjv-xh7p","identifiers":["GHSA-45gv-9wjv-xh7p","CVE-2026-107808","GO-2026-6704"]},"retained":false,"historicalAffected":[]},{"revision":"40baf64785a1454d959d952668852041","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GO-2026-6704","aliases":["CVE-2026-107808","GHSA-45gv-9wjv-xh7p"],"title":"Nginx UI: Authentication bypass: password login does not enforce a passkey-only second factor (2FA bypass) in github.com/0xJacky/Nginx-UI","description":"Nginx UI: Authentication bypass: password login does not enforce a passkey-only second factor (2FA bypass) in github.com/0xJacky/Nginx-UI","state":0,"publishedAt":"2026-10-19T21:08:26+00:00","modifiedAt":"2026-10-19T21:31:49.0177413+00:00","affected":[{"ecosystem":"Go","name":"github.com/0xJacky/Nginx-UI","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"1.9.10-0.20250517140552-daee3ac7ade1"},{"kind":"fixed","version":"1.9.10-0.20260728074558-95cd21b70814"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-45gv-9wjv-xh7p","https://github.com/0xJacky/nginx-ui/commit/95cd21b70814e5d9a48a359aa238aeea1ac97429","https://github.com/0xJacky/nginx-ui/releases/tag/v2.5.0"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-6704","license":"OSV · upstream source license applies","key":"osv:GO-2026-6704","identifiers":["GO-2026-6704","CVE-2026-107808","GHSA-45gv-9wjv-xh7p"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-08T13:04:23.5160344+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-08T13:04:23.5160344+00:00","lastSuccessAt":"2026-11-08T13:04:23.701507+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.7015079+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-eaa64dac63caa79c8d4905619258c41c51b39e461b996124e1a21b5e85e7e738-5e9a1b25b88c72db8d3c46be95e52009e07ae4af884484ac8e34e4d7f79f07eb","complete":true,"records":421098,"lastAttemptAt":"2026-11-08T13:04:23.0585244+00:00","lastSuccessAt":"2026-11-08T13:04:23.0353669+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.0585244+00:00","snapshotPublishedAt":"2026-11-08T12:20:14+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-383c643166b94ac4b6a615647191978e","complete":true,"records":13654,"lastAttemptAt":"2026-11-08T13:04:50.0771652+00:00","lastSuccessAt":"2026-11-08T13:04:50.073+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:50.073+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13706,"expectedIdentifiers":13706,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-08T13:04:23.5160344+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-08T13:04:23.5160344+00:00","lastSuccessAt":"2026-11-08T13:04:23.7022986+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.7022987+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-d0adb98f65b04a798dd6ab08fb972092","complete":false,"records":13666,"lastAttemptAt":"2026-11-08T13:04:49.0861553+00:00","lastSuccessAt":"2026-11-08T13:04:49.083+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:49.083+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13185,"expectedIdentifiers":13706,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-980593e6977a6d1b4fb1eed5a1ffe88a468736f89f0a06f710924ad59132c023","complete":true,"records":9914,"lastAttemptAt":"2026-11-08T13:04:23.3678903+00:00","lastSuccessAt":"2026-11-08T11:07:07.068788+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.3678903+00:00","snapshotPublishedAt":"2026-11-08T11:02:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-08T13:04:23.1502223+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.1502223+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-964c14035e6f0f6478b2870fad6a7ef9f6045876d4a12af2c2e514521e54b6af","complete":true,"records":3055,"lastAttemptAt":"2026-11-08T13:04:23.4767311+00:00","lastSuccessAt":"2026-11-08T11:07:12.1927025+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.4767311+00:00","snapshotPublishedAt":"2026-11-08T11:02:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-f8647d7ba64b1ba0dfdc7130944766d5b36d5b77d18fff0c79e651a4cff3c2b9","complete":true,"records":231293,"lastAttemptAt":"2026-11-08T13:04:23.2659134+00:00","lastSuccessAt":"2026-11-08T11:06:56.2918717+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:23.2659134+00:00","snapshotPublishedAt":"2026-11-08T11:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}