CVE-2026-107809: Nginx-UI AuthRequired token cookie fallback enables CSRF against management APIs
Summary Nginx-UI v2.4.3 stores the API JWT in a browser cookie named token and the AuthRequired middleware accepts that cookie as an authentication source. Because management endpoints are protected by AuthRequired and do not enforce a universal CSRF token or Origin/Referer check, a remote attacker can induce a logged-in administrator to submit cross-site state-changing requests. The attacker does not need to read the cross-origin response; the browser-sent cookie is enough to authenticate the request.
Details The root cause is a trust-boundary failure between browser-managed cookies and API bearer-token authentication. In app/src/pinia/moudule/user.ts:25-35, the front end writes the JWT to a token cookie when the token changes. In internal/middleware/middleware.go:16-36, getToken first checks the Authorization header and token query parameter, then falls back to c.Cookie("token"). That fallback causes browser-initiated cross-site requests to carry a valid API credential automatically. The route group in router/routers.go:87-110 applies middleware.AuthRequired() to many management modules, including config.InitRouter, nginx.InitRouter, settings.InitRouter, and backup-related handlers. For accounts without OTP/Passkey, RequireSecureSession does not provide a universal second factor for all state-changing operations. A representative sink is api/config/add.go:68-78, where an authenticated request writes Nginx configuration with os.WriteFile and then calls nginx.Control(nginx.Reload). CORS does not mitigate this issue because preventing cross-origin response reads does not prevent cross-site form or no-cors requests from being sent with cookies.
Core vulnerable code path:
typescript // app/src/pinia/moudule/user.ts:25-35 watch(token, v => { if (v) { cookies.set('token', v, getCookieOptions(86400)) if (!shortToken.value) { void fetchShortToken() } } else { cookies.remove('token', { path: '/' }) shortToken.value = '' } })
The front end stores the authentication token in a token cookie. The cookie is created client-side and is not shown here as HttpOnly; it becomes the credential that the back end later accepts through cookie fallback.
go // internal/middleware/middleware.go:16-36 // getToken from header, cookie or query func getToken(c gin.Context) (token string) { if token = c.GetHeader("Authorization"); token != "" { return }
if token = c.Query("token"); token != "" { if len(token) > 16 { // Long token (base64 encoded JWT) tokenBytes, := base64.StdEncoding.DecodeString(token) return string(tokenBytes) } // Short token (16 characters) return token }
if token, = c.Cookie("token"); token != "" { return token }
return "" }
getToken accepts the token cookie as an authentication credential after checking the header and query parameter. That behavior lets a browser-sent cookie authenticate state-changing API requests triggered from another origin.
go // router/routers.go:87-110 // Authorization required and not websocket request g := root.Group("/", middleware.AuthRequired(), middleware.Proxy()) { debug.InitRouter(g) user.InitUserRouter(g) analytic.InitRouter(g) user.InitManageUserRouter(g) nginx.InitRouter(g) sites.InitRouter(g) streams.InitRouter(g) config.InitRouter(g) template.InitRouter(g) certificate.InitCertificateRouter(g) certificate.InitDNSCredentialRouter(g) certificate.InitAcmeUserRouter(g) dnsapi.InitRouter(g) system.InitPrivateRouter(g) settings.InitRouter(g) llm.InitRouter(g) cluster.InitRouter(g) notification.InitRouter(g) externalnotify.InitRouter(g) backup.InitAutoBackupRouter(g) nginxLog.InitRouter(g) upstream.InitHTTPRouter(g)
Many management modules, including Nginx, configuration, settings, backup, and user management, are placed behind AuthRequired. The cookie fallback therefore protects high-impact state-changing routes, not only read-only endpoints.
go // api/config/add.go:68-78 err = os.WriteFile(path, []byte(content), 0644) if err != nil { cosy.ErrHandler(c, err) return }
res := nginx.Control(nginx.Reload) if res.IsError() { res.RespError(c) return }
This representative sink shows why the CSRF is high impact: once authenticated through the cookie fallback, a request can write Nginx configuration content and trigger a reload.
POC Prerequisites: an administrator is logged in to Nginx-UI and has a browser token cookie; the administrator account has not enabled OTP/Passkey, or the chosen target endpoint does not require secure-session proof; the attacker knows the Nginx-UI management URL and can induce the administrator to visit an attacker-controlled page. Reproduction: (1) Host an attacker page at GET /csrf.html that automatically submits a cross-site request to the victim instance. (2) The page submits POST /api/configs with configuration fields such as basedir, name, content, overwrite, and syncnodeids; the attacker does not need to read the response. (3) When the administrator visits the page, the browser attaches the victim site's token cookie. (4) Expected result: AuthRequired authenticates from the cookie, AddConfig writes the Nginx configuration, and the application attempts an Nginx reload. Testing should use a harmless configuration in an isolated environment.
Impact An attacker can trigger administrative operations while an administrator is logged in, including Nginx configuration creation or modification, reload/restart operations, settings changes, and backup-related actions depending on endpoint protections. Direct impacts include traffic redirection, reverse proxy tampering, denial of service, and sensitive configuration changes. In deployments with permissive Nginx modules or dangerous configuration capabilities, configuration tampering may lead to further server-side impact or internal network abuse.
Other sources
Nginx UI is a web user interface for the Nginx web server. From 2.0.0 until 2.5.0, AuthRequired accepts a browser-managed token cookie as an API credential after the front end stores the JWT in that cookie. Because management endpoints do not universally require a CSRF token or perform Origin or Referer validation, a remote attacker can induce a logged-in administrator's browser to submit authenticated cross-site state-changing requests, including POST /api/configs. The attack requires an administrator account without OTP/Passkey or a target endpoint that does not require secure-session proof. The attacker cannot read the cross-origin response but can modify Nginx configuration, trigger reloads, or invoke other management operations reachable with the victim's session. This issue is fixed in version 2.5.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/0xJacky/Nginx-UIto a version that resolves this vulnerability.Fixed in 1.9.10-0.20260728074433-a3999bd78a3b - Upgrade
Upgrade
Nginx-UIto a version that resolves this vulnerability.Fixed in 2.5.0
Event History
Frequently Asked Questions
Who is exposed to this attack?
Administrators using affected Nginx UI versions are exposed when they are logged in and an attacker can induce their browser to make a cross-site request. The attack specifically requires an administrator account without OTP/Passkey, unless the targeted management endpoint does not require secure-session proof.
What can an attacker do without reading the API response?
An attacker can cause authenticated state-changing management requests through the administrator's browser. Reported impacts include modifying Nginx configuration through endpoints such as POST /api/configs, triggering reloads, and invoking other management operations reachable with the victim's session.
Are all management API endpoints protected from this issue?
No. Management endpoints do not universally require a CSRF token or validate the Origin or Referer header. Endpoints lacking those protections may accept cross-site state-changing requests authenticated by the browser-managed token cookie.
How can this be remediated?
Upgrade Nginx UI to version 2.5.0, which fixes the issue. The affected range is from 2.0.0 until 2.5.0.