GHSA-p8v3-89rh-jxc7 CVE-2026-107810 GO-2026-6707
Nginx UI: Backup restore follows crafted symlinks into the live Nginx configuration path before restore flags are applied
- Severity
- 8.1 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.45%
- Chance of exploitation in the next 30 days. Higher than 38.68% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-59 Improper Link Resolution Before File Access ('Link Following')
- CWE-61 UNIX Symbolic Link (Symlink) Following
- Fix
1.9.10-0.20260728074146-a467ed652591- Upgrade github.com/0xJacky/Nginx-UI to this version or later
Description
Summary
An authenticated user who can create and restore a backup can craft a valid backup archive that causes the restore staging process to write attacker-controlled files into the live Nginx configuration path even when both restore_nginx and restore_nginx_ui are set to false.
Details
The restore flow always extracts the outer archive, verifies the manifest, decrypts nginx-ui.zip and nginx.zip, and extracts both inner archives before it decides whether RestoreNginx or RestoreNginxUI should be applied. The zip extractor explicitly allows absolute symlinks when the link target is under nginx.GetConfPath() or nginx.GetModulesPath(). Later regular-file entries are then created with os.OpenFile() on the symlinked path, which follows the symlink and writes into the live path.
Relevant code paths:
- internal/backup/restore.go
- internal/backup/restore.go
- internal/backup/restore.go
- internal/backup/restore.go
- api/backup/restore.go
- api/backup/backup.go
This means the restore trust boundary is broken during extraction. A restore request that explicitly opted out of restoring either Nginx or Nginx UI can still modify the live Nginx configuration tree during staging.
PoC
I verified this locally in an isolated environment with a temporary package-level harness that exercised the real Backup() and Restore() implementations.
What the executed test did:
- Created a temporary
app.ini, database file, and a temporary live Nginx config directory. - Called the real
Backup()implementation to obtain a valid backup archive plus AES key/IV. - Extracted the outer backup, decrypted
nginx.zip, replaced it with a crafted zip containing:- a symlink entry
link -> <live nginx conf dir> - a later regular file entry
link/poc.conf
- a symlink entry
- Recomputed
manifest.jsonsize/hash values for the modified encryptednginx.zipand re-signedmanifest.sigwith the expected signing key derived from the AES key. - Repacked the outer archive and called the real
Restore()implementation with:RestoreNginx: falseRestoreNginxUI: false
- Verified that
<live nginx conf dir>/poc.confwas created anyway.
Observed result from the actual local verification:
- The crafted restore completed successfully with both restore flags set to
false. - The asserted sink was the existence and content of the live-path file written during restore staging.
Impact
Any deployment that allows an authenticated user to create and restore backups is affected. A crafted restore archive can modify the live Nginx configuration path before either restore toggle is honored. This can lead to persistent configuration injection, denial of service on a later reload, or other follow-on impact depending on what files the deployment later consumes from the modified path.
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/0xJacky/Nginx-UI | >= 1.9.10-0.20250517140552-daee3ac7ade1, < 1.9.10-0.20260728074146-a467ed652591 |
1.9.10-0.20260728074146-a467ed652591 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
github.com/0xJacky/Nginx-UI Go → 1.9.10-0.20260728074146-a467ed652591
- go
go get github.com/0xJacky/Nginx-UI@v1.9.10-0.20260728074146-a467ed652591 && go mod tidy
Severity breakdown
CVSS 3.1 8.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- None
- Integrity
- High
- Availability
- High
Weakness
CWE-59: Improper Link Resolution Before File Access ('Link Following')
The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
How to prevent it
- Architecture and Design: Follow the principle of least privilege when assigning access rights to entities in a software system. Denying access to a file can prevent an attacker from replacing that file with a link to a sensitive file. Ensure good compartmentalization in the system to provide protected areas that can be trusted.
CWE-61: UNIX Symbolic Link (Symlink) Following
The product, when opening a file or directory, does not sufficiently account for when the file is a symbolic link that resolves to a target outside of the intended control sphere. This could allow an attacker to cause the product to operate on unauthorized files.
How to prevent it
- Implementation: Symbolic link attacks often occur when a program creates a tmp directory that stores files/links. Access to the directory should be restricted to the program as to prevent attackers from manipulating the files.
- Architecture and Design: Follow the principle of least privilege when assigning access rights to entities in a software system. Denying access to a file can prevent an attacker from replacing that file with a link to a sensitive file. Ensure good compartmentalization in the system to provide protected areas that can be trusted.
References
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GO-2026-6773 Nginx UI: Casdoor SSO callback uses a constant `state` parameter (login CSRF) | High | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-vc32-4gwf-77xp Nginx UI: OTP secure-session step-up can be bypassed via MCP configuration tools | High | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-wq4w-vwvq-q3x9 Nginx UI: Standalone short tokens survive administrative revocation in cache and authenticate again after account re-enable | Medium | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-jrq7-xrjx-pcfw Nginx UI: Auto backup name allows path traversal outside validated storage path | Medium | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-5r62-gj48-897x Nginx UI: MCP configuration tools bypass restricted directive validation and allow secondary authenticated users to execute NJS in Nginx worker context | High | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-9jww-cxg5-rj9m Nginx UI: MCP `nginx_config_add` Bypasses Directive Validation — Authenticated RCE | High | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-76pm-mq2q-9gcr Nginx UI: MCP nginx_config_add skips config directive validation → authenticated nginx directive injection (RCE on the official image) | High | Also affects github.com/0xJacky/Nginx-UI |
| GHSA-cf23-7qxj-xmhr Nginx UI: nginx config directive denylist (ValidateConfigFile) is bypassable via same-line ";" separation → authenticated directive injection (RCE on the official image) | High | Also affects github.com/0xJacky/Nginx-UI |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor