GitHub Advisoryadvisory · Go

GHSA-7jrr-xw9c-mj39 Nginx-UI: Authenticated settings disclosure exposes node.secret and enables trusted-node authentication abuse, backup exfiltration, and restore-based nginx-ui state rollback

MEDIUM Published 2026-05-05 Modified 2026-07-21 Official page ↗
Aliases
CVE-2026-42220
Ecosystem
Go
Packages
github.com/0xJacky/Nginx-UI
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weaknesses
CWE-200, CWE-863
Reviewed
2026-05-05

Affected packages

• Go github.com/0xJacky/Nginx-UI: <= 1.9.9 (no fix yet)

Description

Summary An authenticated user can call `GET /api/settings` and retrieve sensitive configuration values, including `node.secret`. The same `node.secret` is accepted by `AuthRequired()` through the `X-Node-Secret` header (or `node_secret` query parameter), causing the request to be treated as authenticated via the trusted-node path and associated with the init user. In my local reproduction on `v2.3.6`, `GET /api/settings` also returned `app.jwt_secret`. After extracting `node.secret`, I was able to access `GET /api/backup` using only `X-Node-Secret`, download a full backup archive, and obtain the `X-Backup-Security` response header containing the backup decryption material (`AESKey:AESIv`). I also confirmed that the disclosed `node.secret` is sufficient to reach the restore workflow on an installed instance. Using only `X-Node-Secret`, a valid backup archive, and its matching `X-Backup-Security` token, I successfully invoked `POST /api/restore`. In a follow-up rollback test, I changed `node.name` to `rollback-poc-B`, then restored a previously captured backup and observed the value revert to its original state. This extends the issue beyond secret disclosure and backup exfiltration into confirmed integrity impact through restore-based rollback of nginx-ui state/configuration. This breaks the trust boundary between ordinary user-authenticated API access and the internal node-authentication mechanism, and results in sensitive configuration disclosure, alternate-authentication abuse, backup exfiltration with decryption material, and confirmed restore-based rollback of nginx-ui state. Details Vulnerable code / related files and functions 1) Route exposure and insufficient protection on the read path File: `api/settings/router.go` Relevant function: `InitRouter` The settings router exposes the following endpoints: ```http GET /api/settings/server/name → GetServerName GET /api/settings → GetSettings POST /api/settings → RequireSecureSession(), SaveSettings ``` The key issue is that the read path (GET /api/settings) is only protected by the generic authentication middleware, while the write path (POST /api/settings) has an additional RequireSecureSession() check. This makes the read path a much easier place to leak sensitive configuration data than the write path. ```go r.GET("settings/server/name", GetServerName) r.GET("settings", GetSettings) r.POST("settings", middleware.RequireSecureSession(), SaveSettings) ``` 2) Sensitive data is disclosed by GetSettings File: `api/settings/settings.go` Relevant functions: GetSettings, SaveSettings `GetSettings` returns multiple configuration objects directly in the JSON response, including app, server, database, auth, casdoor, oidc, cert, http, logrotate, nginx, node, openai, terminal, and webauthn. In other words, the handler does not use a redacted DTO for user-facing output; it serializes the live settings objects directly. ```go c.JSON(http.StatusOK, gin.H{ "app": cSettings.AppSettings, "server": cSettings.ServerSettings, "database": settings.DatabaseSettings, "auth": settings.AuthSettings, "casdoor": settings.CasdoorSettings, "oidc": settings.OIDCSettings, "cert": settings.CertSettings, "http": settings.HTTPSettings, "logrotate": settings.LogrotateSettings, "nginx": settings.NginxSettings, "node": settings.NodeSettings, "openai": settings.OpenAISettings, "terminal": settings.TerminalSettings, "webauthn": settings.WebAuthnSettings, }) ``` In my local reproduction on v2.3.6, this response exposed both: ``` node.secret app.jwt_secret ``` This makes GetSettings the direct disclosure source for the vulnerability. 3) The disclosed value is explicitly defined as protected/sensitive File: `settings/node.go` Relevant object: type Node The Node settings object defines the following field: ```go type Node struct { Name string `json:"name" binding:"omitempty,safety_text"` Secret string `json:"secret" protected:"true"` ... } ``` The `protected:"true"` tag shows that the codebase itself treats `node.secret` as a protected/sensitive value. Despite that, the field is still returned unredacted by `GetSettings`. This strongly indicates a real secret disclosure issue rather than a harmless configuration read. 4) The disclosed secret is reused as an authentication credential File: `internal/middleware/middleware.go` Relevant functions: getNodeSecret, AuthRequired, AuthRequiredWS The authentication middleware contains a separate node-secret authentication path: • `getNodeSecret(c)` reads the value from the `X-Node-Secret` header or the `node_secret` query parameter. • AuthRequired() checks whether the supplied value equals settings.NodeSettings.Secret. • If it matches, the middleware: loads initUser := user.GetInitUser(c) stores Secret in the context stores user in the context • allows the request to proceed without relying on the ordinary JWT path for that identity flow This is the sink of the vulnerability: the same secret disclosed by GET /api/settings is accepted as a valid authentication credential by the middleware. ```go if nodeSecret := getNodeSecret(c); nodeSecret != "" && nodeSecret == settings.NodeSettings.Secret { initUser := user.GetInitUser(c) c.Set("Secret", nodeSecret) c.Set("user", initUser) c.Next() return } ``` AuthRequiredWS() contains similar logic for the WebSocket path, meaning the same secret is also trusted by the WebSocket authentication flow. 5) The write path already treats these fields as protected, but the read path does not File: `api/settings/settings.go` Relevant function: SaveSettings `SaveSettings()` already uses ProtectedFill(...) for several settings objects, including: ``` AppSettings NodeSettings OpenAISettings NginxSettings OIDCSettings ``` This shows the project already recognizes that these objects contain protected fields on the write path. However, GetSettings() still returns the raw objects on the read path, creating a clear “write-protected but read-exposed” inconsistency. That inconsistency is the core authorization/secret-handling flaw here. ```go cSettings.ProtectedFill(cSettings.AppSettings, &json.App) cSettings.ProtectedFill(settings.NodeSettings, &json.Node) cSettings.ProtectedFill(settings.OpenAISettings, &json.Openai) cSettings.ProtectedFill(settings.NginxSettings, &json.Nginx) cSettings.ProtectedFill(settings.OIDCSettings, &json.Oidc) ``` 6) Backup endpoint reachable after alternate authentication File: `api/backup/router.go`, `api/backup/backup.go` Relevant functions: `InitRouter`, `CreateBackup` The backup route is exposed as: ```go r.GET("backup", CreateBackup) ``` This route is protected by the same AuthRequired() middleware chain as other authenticated API routes. In `CreateBackup()`, the server returns the backup archive to the caller and also sets the X-Backup-Security response header containing the decryption material: ```go c.Header("X-Backup-Security", fmt.Sprintf("%s:%s", backup.Security.AESKey, backup.Security.AESIv)) c.File(backupFilePath) ``` As a result, once node.secret is disclosed from /api/settings and reused through X-Node-Secret, the attacker can access /api/backup and obtain both the encrypted backup and the decryption token in the same response. This means the disclosed secret is not only usable for low-risk authenticated reads, but also for high-impact data exfiltration through the backup subsystem. 7) Restore endpoint is reachable and usable after alternate authentication File: `api/backup/router.go`, `api/backup/restore.go`, `internal/backup/restore.go` Relevant functions: `authIfInstalled`, `RestoreBackup`, internal restore helpers The restore route is exposed as: ```go r.POST("/restore", authIfInstalled, middleware.EncryptedForm(), RestoreBackup) ``` On installed instances, authIfInstalled calls AuthRequired(). Because AuthRequired() accepts X-Node…

Related

Alias · CVE (1)

  • CVE-2026-42220 Nginx UI is a web user interface for the Nginx web server. vulnerability

Weakness · CWE (2)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-863 Incorrect Authorization weakness

References