GitHub Security Advisory: GHSA-g9w5-qffc-6762
Package: github.com/0xJacky/Nginx-UI (GO)
Severity: CRITICAL (CVSS 9.8)
CWE: CWE-306: Missing Authentication for Critical Function, CWE-311: Missing Encryption of Sensitive Data
Vulnerable versions: < 2.3.3
Patched in: 2.3.3

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

Details:
## Summary

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:

```go
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:

```go
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:

```go
// 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

```python
#!/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 --decr