Advisories for Golang/Github.com/0xJacky/Nginx-UI package

2026

Nginx UI: TOTP / passkey enrolment requires no re-authentication, enabling persistent account takeover

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.

Nginx UI: nginx config directive denylist (ValidateConfigFile) is bypassable via same-line ";" separation → authenticated directive injection (RCE on the official image)

config.ValidateConfigFile is the security control that blocks dangerous nginx directives (js_, perl_, load_module of njs/perl modules, user root). Its directive scanner is bypassable: it splits the config by newlines and inspects only the FIRST token of each line, but nginx separates directives with ;, not newlines. Putting a benign directive first and the blocked directive after a ; on the SAME line defeats every check. The include directive is also …

Nginx UI: MCP nginx_config_add skips config directive validation → authenticated nginx directive injection (RCE on the official image)

internal/config/validation.go (ValidateConfigFile) is a security control that blocks dangerous nginx directives — js_, perl_, load_module of the njs/perl modules, and user root. It is enforced on three of the four config-write paths: api/config/add.go, api/config/modify.go, and mcp/config/config_modify.go. The fourth path — the MCP tool nginx_config_add (mcp/config/config_add.go) — does NOT call it: it writes the caller-supplied content straight to disk and reloads nginx. An authenticated MCP caller (or an MCP agent induced …

Nginx UI: MCP configuration tools bypass restricted directive validation and allow secondary authenticated users to execute NJS in Nginx worker context

The regular HTTP configuration API rejects restricted Nginx directives such as load_module, js_import, and js_content. However, the MCP configuration interface allows a secondary authenticated user to write and apply equivalent restricted configuration content. In the lab, this allowed the user to load ngx_http_js_module.so, import attacker-controlled JavaScript, reload Nginx, and execute NJS in the Nginx worker context. This is authenticated and is not OS command execution by itself.

Nginx UI: MCP `nginx_config_add` Bypasses Directive Validation — Authenticated RCE

The MCP tool nginx_config_add writes nginx configuration files to disk and triggers an nginx reload without calling config.ValidateConfigFile. The identical flow in the REST API (POST /api/configs) does call that function and correctly rejects dangerous directives. An authenticated attacker can exploit this gap to write config files containing any blocked directive (perl, js_import, load_module, user root, etc.), bypassing nginx-ui's entire security layer. On a server with the nginx Perl, njs, …

Nginx UI: Casdoor SSO callback uses a constant `state` parameter (login CSRF)

In the Casdoor SSO flow, the OAuth state parameter is hard-coded to settings.CasdoorSettings.Application — a value identical for every user and every login. The callback handler accepts whatever state the client sends without comparing it to anything stored in the user agent, enabling a login-CSRF: an attacker can force the victim's browser to authenticate as the attacker.

Nginx UI: Auto backup name allows path traversal outside validated storage path

Nginx UI validates backup_path and storage_path against the administrator-defined backup.GrantedAccessPath boundary, but the final auto-backup output filename is built from the user-controlled auto-backup name without stripping path separators or .. components. An authenticated user who can create or modify an auto-backup task can set a name such as task/../../../escaped. The configured storage_path still passes validation because it is inside GrantedAccessPath, but the generated backup path escapes that storage directory when …

Nginx UI: Account Takeover via Missing `RequireSecureSession` on User Management Endpoint

POST /api/users/:id allows any authenticated user to change any other user's password without RequireSecureSession (2FA verification). In contrast, the self password change endpoint POST /api/user/password correctly requires RequireSecureSession. This inconsistency enables any authenticated user to take over the initial admin account (user ID 1) by simply changing its password.

Nginx UI OTP secure-session step-up is missing on sensitive mutation routes

Nginx UI has a short-lived OTP/passkey "secure session" step-up boundary for users with 2FA enabled. RequireSecureSession() rejects an OTP-enabled user's request unless the request includes a valid X-Secure-Session-ID issued after a fresh OTP/passkey verification. The project already applies this step-up middleware to sensitive operations such as: Nginx config create/edit/rename/delete; site save/delete/enable/disable/rename/duplicate; stream save/delete/enable/disable/rename/duplicate; settings save and protected secret reads; backup restore. However, several similarly sensitive authenticated mutation routes remain outside …

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

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 …

Nginx UI: Unauthenticated signed-request body staging can exhaust temporary storage

In Nginx UI versions 2.5.0 through 2.5.x, the node-signature authentication path staged an attacker-controlled request body in a temporary file and synchronized it to disk before validating the body digest and cryptographic signature. An unauthenticated remote client that can reach the API can provide syntactically valid signature metadata and cause storage and I/O consumption before the request is rejected.

Nginx UI: Self-upgrade runs an unsigned binary verified only by a same-origin digest → RCE via a compromised mirror or MITM

The self-upgrade downloads the release binary and its checksum (*.tar.gz and *.tar.gz.digest) through the SAME endpoint (version.GetUrl(), which is github_proxy or, by default, the project's cloud.nginxui.com mirror), and verifies the binary ONLY by comparing it to that digest: digestFileContent == DigestSHA512(tarName). Both the binary and the digest come from the same origin, and there is NO cryptographic signature / public-key verification. The downloaded binary then replaces the running executable (selfupdate.CommitBinary) …

Nginx UI: Node Secret Credential Exposure via URL Query Parameter

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 …

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

Incomplete fix of GHSA-5v7c-xpfp-p65m: the secure-session (OTP step-up) requirement added to the nginx, cert, dns, backup, site, and stream mutation routers was not applied to the parallel api/cluster router, so cluster node management and cluster-wide nginx reload/restart run with only a JWT and no step-up. An authenticated user whose JWT is stolen or persisted, but who does not hold a fresh secure-session, can perform cluster node CRUD (including reading and …

Nginx UI: Authentication bypass: password login does not enforce a passkey-only second factor (2FA bypass)

nginx-ui supports two second-factor methods — TOTP (OTP) and WebAuthn passkeys — and reports an account as 2FA-enabled when either is configured. However, the password login endpoint (POST /api/login) only enforces a second factor when a TOTP secret is present. An account that has registered a passkey but no TOTP is logged in after password verification alone — the passkey is never requested. This silently downgrades a passkey-protected account to …

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

Summary 0xJacky/nginx-ui contains a high-severity trust-boundary failure between ordinary users and cluster node credentials. The source code shows that cluster node list and detail endpoints are reachable by ordinary authenticated users and return node objects containing the token field. That same token is also used as the X-Node-Secret shared credential for node-to-node authentication. When the middleware receives a correct node secret, it upgrades the request to initUser and bypasses the …

Nginx-UI: Unauthenticated First-Run Installer Allows Remote Initial Admin Claim

An unauthenticated network attacker can claim the initial administrator account on a fresh nginx-ui instance during the first-run setup window. The public /api/install endpoint is reachable without authentication, and the request-encryption flow only protects payload confidentiality in transit; it does not authenticate who is allowed to perform installation. A remote attacker who reaches the service before the legitimate operator can set the admin email, username, and password, causing permanent initial-instance …

Nginx-UI: Unauthenticated first-boot instance claim via POST /api/install allows remote bootstrap takeover

An unauthenticated bootstrap takeover exists in nginx-ui during the initial installation window exposed by POST /api/install. When the instance is still uninitialized, POST /api/install is reachable without authentication and accepts attacker-controlled bootstrap data. The handler sets the application's JWT secret, the node secret, the certificate email, and the initial administrator username and password. This allows an attacker who can reach a fresh instance during the initial 10-minute setup window to …

Nginx-UI Settings API Exposes Protected Secrets

The GetSettings API handler (api/settings/settings.go:24-65) serializes all settings structs to JSON and returns them to authenticated users. Many sensitive fields are tagged with protected:"true" - however, this tag is only enforced during writes (via ProtectedFill in SaveSettings) and is completely ignored during reads. This exposes 40+ protected fields including JwtSecret (enabling auth token forgery), NodeSecret (enabling cluster node impersonation), OIDC ClientSecret (enabling OAuth account takeover), and the IP whitelist configuration.

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

nginx-ui exposes a backup restore endpoint (POST /api/restore) that is completely unauthenticated during the first 10 minutes after process startup on any fresh installation. An unauthenticated remote attacker can upload a crafted backup archive that overwrites the application's configuration file (app.ini) and SQLite database. Because the attacker controls the restored app.ini, they can inject an arbitrary OS command into the TestConfigCmd setting. After the application automatically restarts to apply the …

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

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 …

Nginx-UI has Server-Side Request Forgery (SSRF) via Cluster Proxy Middleware that Allows Access to Internal Services

An authenticated user can perform Server-Side Request Forgery (SSRF) by creating a cluster node pointing to an arbitrary internal URL and then sending API requests with the X-Node-ID header. The Proxy middleware forwards these requests to the attacker-specified internal address, bypassing network segmentation and enabling access to services bound to localhost or internal networks.

Nginx-UI has Server-Side Request Forgery (SSRF) via Cluster Proxy Middleware that Allows Access to Internal Services

An authenticated user can perform Server-Side Request Forgery (SSRF) by creating a cluster node pointing to an arbitrary internal URL and then sending API requests with the X-Node-ID header. The Proxy middleware forwards these requests to the attacker-specified internal address, bypassing network segmentation and enabling access to services bound to localhost or internal networks.

Nginx-UI: Disabled users retain full API access through previously issued bearer tokens

A user who was disabled by an administrator can use previously issued API tokens for up to the token lifetime. In practice, disabling a compromised account does not actually terminate that user’s access, so an attacker who already stole a JWT can continue reading and modifying protected resources after the account is marked disabled. Since tokens can be used to create new accounts, it is possible the disabled user to …

Nginx-UI: Cross-Site WebSocket Hijacking (CSWSH) via missing origin validation on all WebSocket endpoints

All WebSocket endpoints in nginx-ui use a gorilla/websocket Upgrader with CheckOrigin unconditionally returning true, allowing Cross-Site WebSocket Hijacking (CSWSH). Combined with the fact that authentication tokens are stored in browser cookies (set via JavaScript without HttpOnly or explicit SameSite attributes), a malicious webpage can establish authenticated WebSocket connections to the nginx-ui instance when a logged-in administrator visits the attacker-controlled page.

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

The nginx-ui MCP (Model Context Protocol) integration exposes two HTTP endpoints: /mcp and /mcp_message. While /mcp requires both IP whitelisting and authentication (AuthRequired() middleware), the /mcp_message endpoint only applies IP whitelisting - and the default IP whitelist is empty, which the middleware treats as "allow all". This means any network attacker can invoke all MCP tools without authentication, including restarting nginx, creating/modifying/deleting nginx configuration files, and triggering automatic config reloads …

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

Nginx-UI contains an Insecure Direct Object Reference (IDOR) vulnerability that allows any authenticated user to access, modify, and delete resources belonging to other users. The application's base Model struct lacks a user_id field, and all resource endpoints perform queries by ID without verifying user ownership, enabling complete authorization bypass in multi-user environments. | File | Line | Current | Fix | |——|——|———|—–| | model/dns_credential.go | 7 | serializer:json | serializer:json[aes] …

nginx-ui has Race Condition that Leads to Persistent Data Corruption and Service Collapse

The nginx-ui application is vulnerable to a Race Condition. Due to the complete absence of synchronization mechanisms (Mutex) and non-atomic file writes, concurrent requests lead to the severe corruption of the primary configuration file (app.ini). This vulnerability results in a persistent Denial of Service (DoS) and introduces a non-deterministic path for Remote Code Execution (RCE) through configuration cross-contamination.

Nginx Configuration Directory Vulnerable to Recursive Deletion via Improper Path Validation

The nginx-ui configuration improperly handles URL-encoded traversal sequences. When specially crafted paths are supplied, the backend resolves them to the base Nginx configuration directory and executes the operation on the base directory (/etc/nginx). In particular, this allows an authenticated user to remove the entire /etc/nginx directory, resulting in a partial Denial of Service.

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

The /api/backup endpoint is accessible without authentication and discloses the encryption keys required to decrypt the backup in the X-Backup-Security response header. This allows an unauthenticated attacker to download a full system backup containing sensitive data (user credentials, session tokens, SSL private keys, Nginx configurations) and decrypt it immediately.

2024

Nginx-UI vulnerable to arbitrary file write through the Import Certificate feature

The Import Certificate feature allows arbitrary write into the system. The feature does not check if the provided user input is a certification/key and allows to write into arbitrary paths in the system. https://github.com/0xJacky/nginx-ui/blob/f20d97a9fdc2a83809498b35b6abc0239ec7fdda/api/certificate/certificate.go#L72 func AddCert(c *gin.Context) { var json struct { Name string json:"name" SSLCertificatePath string json:"ssl_certificate_path" binding:"required" SSLCertificateKeyPath string json:"ssl_certificate_key_path" binding:"required" SSLCertificate string json:"ssl_certificate" SSLCertificateKey string json:"ssl_certificate_key" ChallengeMethod string json:"challenge_method" DnsCredentialID int json:"dns_credential_id" } if !api.BindAndValid(c, &json) { …

Authenticated (user role) SQL injection in `OrderAndPaginate` (GHSL-2023-270)

The OrderAndPaginate function is used to order and paginate data. It is defined as follows: func OrderAndPaginate(c *gin.Context) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { sort := c.DefaultQuery("order", "desc") order := fmt.Sprintf("%s %s", DefaultQuery(c, "sort_by", "id"), sort) db = db.Order(order) … } } By using DefaultQuery, the "desc" and "id" values are used as default values if the query parameters are not set. Thus, the order and sort_by …

Authenticated (user role) remote command execution by modifying `nginx` settings (GHSL-2023-269)

The Home > Preference page exposes a small list of nginx settings such as Nginx Access Log Path and Nginx Error Log Path. However, the API also exposes test_config_cmd, reload_cmd and restart_cmd. While the UI doesn't allow users to modify any of these settings, it is possible to do so by sending a request to the API. func InitPrivateRouter(r *gin.RouterGroup) { r.GET("settings", GetSettings) r.POST("settings", SaveSettings) … } The SaveSettings function …