diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 28feaa537..cb3c8f029 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -9,8 +9,9 @@ class ScanStatus(Enum): # Technique was attempted and succeeded USED = 2 + # Dict that describes what BITS job was used for -BITS_UPLOAD_STRING = {"usage": "BITS job was used to upload monkey to a remote system."} +BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system." def format_time(time): diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 3fae277e8..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_type, data): + def send_telemetry(telem_category, data): if not WormConfiguration.current_server: return try: - telemetry = {'monkey_guid': GUID, 'telem_type': telem_type, '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 a1bcb2e6f..f4cc4b290 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -25,7 +25,7 @@ class HostExploiter(object): 'finished': '', 'vulnerable_urls': [], 'vulnerable_ports': [], - 'executed_cmds': {}} + 'executed_cmds': []} self._exploit_attempts = [] self.host = host @@ -49,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): @@ -59,17 +71,13 @@ class HostExploiter(object): def add_vuln_port(self, port): self._exploit_info['vulnerable_ports'].append(port) - def add_example_cmd(self, cmd): - self._exploit_info['executed_cmds']['example'] = cmd - - def add_powershell_cmd(self, cmd): + def add_executed_cmd(self, cmd): """ - Determines if command uses powershell and if so adds that command to exploiter info - :param cmd: Command used - :return: None + Appends command to exploiter's info. + :param cmd: String of executed command. e.g. 'echo Example' """ - if "powershell" in cmd.lower(): - self._exploit_info['executed_cmds']['powershell'] = cmd + 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 diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 3a129ebc0..24a902eea 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -11,7 +11,7 @@ from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\ DOWNLOAD_TIMEOUT from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING import re @@ -64,7 +64,7 @@ class ElasticGroovyExploiter(WebRCE): def upload_monkey(self, url, commands=None): result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands) if 'windows' in self.host.os['type'] and result: - VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send() + T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() return result def get_results(self, response): diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 54d0ef6da..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,8 +49,7 @@ class HadoopExploiter(WebRCE): return False http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.stop() - self.add_powershell_cmd(command) - self.add_example_cmd(command) + 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 9e1692fb9..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_powershell_cmd(' '.join(commands)) + 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_example_cmd(commands[-1]) + 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 1e66040fc..0db63e86d 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -15,9 +15,9 @@ from infection_monkey.exploit.tools import get_target_monkey from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline +from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.utils import utf_to_ascii from common.utils.exploit_enum import ExploitType -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'hoffer' @@ -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) @@ -316,7 +316,7 @@ class RdpExploiter(HostExploiter): if client_factory.success: if not self._config.rdp_use_vbs_download: - VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send() + T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() self.add_vuln_port(RDP_PORT) exploited = True self.report_login_attempt(True, user, password) @@ -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_example_cmd(command) + 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 9db770905..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,7 +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_example_cmd(cmdline) + 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 226ad8943..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,7 +178,7 @@ class SSHExploiter(HostExploiter): self._config.dropper_target_path_linux, self.host, cmdline) ssh.close() - self.add_example_cmd(cmdline) + 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 f3f13c073..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,7 +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_example_cmd(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 81e380bc5..fe45c65ce 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -7,7 +7,7 @@ from infection_monkey.exploit import HostExploiter from infection_monkey.model import * from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'VakarisZ' @@ -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 @@ -308,7 +308,7 @@ class WebRCE(HostExploiter): if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: LOG.info("Powershell not found in host. Using bitsadmin to download.") backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path} - VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING).send() + T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() resp = self.exploit(url, backup_command) return resp @@ -338,7 +338,7 @@ class WebRCE(HostExploiter): command = self.get_command(paths['dest_path'], http_path, commands) resp = self.exploit(url, command) - self.add_powershell_cmd(command) + self.add_executed_cmd(command) resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path) http_thread.join(DOWNLOAD_TIMEOUT) @@ -408,7 +408,7 @@ class WebRCE(HostExploiter): # If exploiter returns True / False if type(resp) is bool: LOG.info("Execution attempt successfully finished") - self.add_example_cmd(command) + 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: @@ -422,7 +422,7 @@ class WebRCE(HostExploiter): return False LOG.info("Execution attempt finished") - self.add_example_cmd(command) + 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 f6df5f0fa..4c99f82b9 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -20,8 +20,8 @@ __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 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 diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 72fca6d1d..e1d2e5b85 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -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 b9decf433..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_example_cmd(cmdline) + self.add_executed_cmd(cmdline) return success return False diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index b152e58be..06e0f267b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -15,10 +15,10 @@ from infection_monkey.network.firewall import app as firewall from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton +from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem from infection_monkey.exploit.tools import get_interface_to_target __author__ = 'itamar' @@ -186,9 +186,11 @@ class InfectionMonkey(object): for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True + VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) + VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send() if not self._keep_running: break @@ -283,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/telemetry/__init__.py b/monkey/infection_monkey/telemetry/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/telemetry/attack/__init__.py b/monkey/infection_monkey/telemetry/attack/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py new file mode 100644 index 000000000..893f4492a --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py @@ -0,0 +1,24 @@ +from infection_monkey.telemetry.base_telem import BaseTelem + +__author__ = "VakarisZ" + + +class AttackTelem(BaseTelem): + + def __init__(self, technique, status): + """ + Default ATT&CK telemetry constructor + :param technique: Technique ID. E.g. T111 + :param status: ScanStatus of technique + """ + super(AttackTelem, self).__init__() + self.technique = technique + self.status = status + + telem_category = 'attack' + + def get_data(self): + return { + 'status': self.status.value, + 'technique': self.technique + } diff --git a/monkey/infection_monkey/telemetry/attack/t1197_telem.py b/monkey/infection_monkey/telemetry/attack/t1197_telem.py new file mode 100644 index 000000000..387c3aa13 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1197_telem.py @@ -0,0 +1,22 @@ +from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem + +__author__ = "itay.mizeretz" + + +class T1197Telem(VictimHostTelem): + def __init__(self, status, machine, usage): + """ + T1197 telemetry. + :param status: ScanStatus of technique + :param machine: VictimHost obj from model/host.py + :param usage: Usage string + """ + super(T1197Telem, self).__init__('T1197', status, machine) + self.usage = usage + + def get_data(self): + data = super(T1197Telem, self).get_data() + data.update({ + 'usage': self.usage + }) + return data diff --git a/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py new file mode 100644 index 000000000..2ccab7483 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py @@ -0,0 +1,29 @@ +from unittest import TestCase + +from common.utils.attack_utils import ScanStatus +from infection_monkey.model import VictimHost +from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem + + +class TestVictimHostTelem(TestCase): + def test_get_data(self): + machine = VictimHost('127.0.0.1') + status = ScanStatus.USED + technique = 'T1210' + + telem = VictimHostTelem(technique, status, machine) + + self.assertEqual(telem.telem_category, 'attack') + + expected_data = { + 'machine': { + 'domain_name': machine.domain_name, + 'ip_addr': machine.ip_addr + }, + 'status': status.value, + 'technique': technique + } + + actual_data = telem.get_data() + + self.assertEqual(actual_data, expected_data) diff --git a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py new file mode 100644 index 000000000..9e277926c --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py @@ -0,0 +1,24 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + +__author__ = "VakarisZ" + + +class VictimHostTelem(AttackTelem): + + def __init__(self, technique, status, machine): + """ + ATT&CK telemetry. + When `send` is called, it will parse and send the VictimHost's (remote machine's) data. + :param technique: Technique ID. E.g. T111 + :param status: ScanStatus of technique + :param machine: VictimHost obj from model/host.py + """ + super(VictimHostTelem, self).__init__(technique, status) + self.machine = {'domain_name': machine.domain_name, 'ip_addr': machine.ip_addr} + + def get_data(self): + data = super(VictimHostTelem, self).get_data() + data.update({ + 'machine': self.machine + }) + return data diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py new file mode 100644 index 000000000..c232ab975 --- /dev/null +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -0,0 +1,36 @@ +import abc + +from infection_monkey.control import ControlClient + +__author__ = 'itay.mizeretz' + + +class BaseTelem(object): + """ + Abstract base class for telemetry. + """ + + __metaclass__ = abc.ABCMeta + + def __init__(self): + pass + + def send(self): + """ + Sends telemetry to island + """ + ControlClient.send_telemetry(self.telem_category, self.get_data()) + + @abc.abstractproperty + def telem_category(self): + """ + :return: Telemetry type + """ + pass + + @abc.abstractmethod + def get_data(self): + """ + :return: Data of telemetry (should be dict) + """ + pass diff --git a/monkey/infection_monkey/transport/attack_telems/__init__.py b/monkey/infection_monkey/transport/attack_telems/__init__.py deleted file mode 100644 index 98867ed4d..000000000 --- a/monkey/infection_monkey/transport/attack_telems/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py deleted file mode 100644 index 93d5bbbf7..000000000 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ /dev/null @@ -1,41 +0,0 @@ -from infection_monkey.config import WormConfiguration, GUID -import requests -import json -from infection_monkey.control import ControlClient -import logging - -__author__ = "VakarisZ" - -LOG = logging.getLogger(__name__) - - -class AttackTelem(object): - - def __init__(self, technique, status, data=None): - """ - Default ATT&CK telemetry constructor - :param technique: Technique ID. E.g. T111 - :param status: int from ScanStatus Enum - :param data: Dictionary of other relevant info. E.g. {'brute_force_blocked': True} - """ - self.technique = technique - self.result = status - self.data = {'status': status, 'id': GUID} - if data: - self.data.update(data) - - def send(self): - """ - Sends telemetry to island - """ - if not WormConfiguration.current_server: - return - try: - requests.post("https://%s/api/attack/%s" % (WormConfiguration.current_server, self.technique), - data=json.dumps(self.data), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies) - except Exception as exc: - LOG.warn("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py deleted file mode 100644 index fc0da7fbf..000000000 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ /dev/null @@ -1,18 +0,0 @@ -from infection_monkey.transport.attack_telems.base_telem import AttackTelem - -__author__ = "VakarisZ" - - -class VictimHostTelem(AttackTelem): - - def __init__(self, technique, status, machine, data=None): - """ - ATT&CK telemetry that parses and sends VictimHost's (remote machine's) data - :param technique: Technique ID. E.g. T111 - :param status: int from ScanStatus Enum - :param machine: VictimHost obj from model/host.py - :param data: Other data relevant to the attack technique - """ - super(VictimHostTelem, self).__init__(technique, status, data) - victim_host = {'domain_name': machine.domain_name, 'ip_addr': machine.ip_addr} - self.data.update({'machine': victim_host}) 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/app.py b/monkey/monkey_island/cc/app.py index 82392a7b4..2e04ef0be 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -33,7 +33,6 @@ from monkey_island.cc.services.database import Database from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.resources.pba_file_upload import FileUpload -from monkey_island.cc.resources.attack.attack_telem import AttackTelem from monkey_island.cc.resources.attack.attack_config import AttackConfiguration from monkey_island.cc.resources.attack.attack_report import AttackReport @@ -133,7 +132,6 @@ def init_api_resources(api): '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') api.add_resource(AttackConfiguration, '/api/attack') - api.add_resource(AttackTelem, '/api/attack/') api.add_resource(AttackReport, '/api/attack/report') api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') diff --git a/monkey/monkey_island/cc/resources/attack/attack_telem.py b/monkey/monkey_island/cc/resources/attack/attack_telem.py deleted file mode 100644 index 8c30bb13c..000000000 --- a/monkey/monkey_island/cc/resources/attack/attack_telem.py +++ /dev/null @@ -1,24 +0,0 @@ -import flask_restful -from flask import request -import json -from monkey_island.cc.services.attack.attack_telem import AttackTelemService -import logging - -__author__ = 'VakarisZ' - -LOG = logging.getLogger(__name__) - - -class AttackTelem(flask_restful.Resource): - """ - ATT&CK endpoint used to retrieve matrix related info from monkey - """ - - def post(self, technique): - """ - Gets ATT&CK telemetry data and stores it in the database - :param technique: Technique ID, e.g. T1111 - """ - data = json.loads(request.data) - AttackTelemService.set_results(technique, data) - return {} diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 2f464f068..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_type': {'$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_type': {'$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 5d63a96ca..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_type = request.args.get('telem_type') + 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_type: - find_filter["telem_type"] = {'$eq': telem_type} + 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_type = telemetry_json.get('telem_type') - if telem_type in TELEM_PROCESS_DICT: - TELEM_PROCESS_DICT[telem_type](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_type) + 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_type'] == '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(',', '.') @@ -265,6 +265,12 @@ class Telemetry(flask_restful.Resource): {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) + @staticmethod + def process_attack_telemetry(telemetry_json): + # No processing required + pass + + TELEM_PROCESS_DICT = \ { 'tunnel': Telemetry.process_tunnel_telemetry, @@ -273,5 +279,6 @@ TELEM_PROCESS_DICT = \ 'scan': Telemetry.process_scan_telemetry, 'system_info_collection': Telemetry.process_system_info_telemetry, 'trace': Telemetry.process_trace_telemetry, - 'post_breach': Telemetry.process_post_breach_telemetry + 'post_breach': Telemetry.process_post_breach_telemetry, + 'attack': Telemetry.process_attack_telemetry } diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 01fdcc51c..b98d650c8 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_type']](telem) + 'brief': TELEM_PROCESS_DICT[telem['telem_category']](telem) } @staticmethod @@ -86,6 +86,10 @@ class TelemetryFeed(flask_restful.Resource): telem['data']['hostname'], telem['data']['ip']) + @staticmethod + def get_attack_telem_brief(telem): + return 'Monkey collected MITRE ATT&CK info.' + TELEM_PROCESS_DICT = \ { @@ -95,5 +99,6 @@ TELEM_PROCESS_DICT = \ 'scan': TelemetryFeed.get_scan_telem_brief, 'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief, 'trace': TelemetryFeed.get_trace_telem_brief, - 'post_breach': TelemetryFeed.get_post_breach_telem_brief + 'post_breach': TelemetryFeed.get_post_breach_telem_brief, + 'attack': TelemetryFeed.get_attack_telem_brief } diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 9a40e1728..02d840e61 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,5 +1,5 @@ import logging -from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 +from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086 from monkey_island.cc.services.attack.attack_telem import AttackTelemService from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -31,7 +31,7 @@ class AttackReportService: Generates new report based on telemetries, replaces old report in db with new one. :return: Report object """ - report = {'techniques': {}, 'meta': AttackTelemService.get_latest_telem(), 'name': REPORT_NAME} + report = {'techniques': {}, 'latest_telem_time': AttackReportService.get_latest_attack_telem_time(), 'name': REPORT_NAME} for tech_id, value in AttackConfig.get_technique_values().items(): if value: try: @@ -42,6 +42,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(): """ @@ -49,9 +57,9 @@ class AttackReportService: :return: report dict. """ if AttackReportService.is_report_generated(): - telem_time = AttackTelemService.get_latest_telem() + telem_time = AttackReportService.get_latest_attack_telem_time() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) - if telem_time and latest_report['meta'] and telem_time['time'] == latest_report['meta']['time']: + if telem_time and latest_report['latest_telem_time'] and telem_time == latest_report['latest_telem_time']: return latest_report return AttackReportService.generate_new_report() diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py deleted file mode 100644 index b402bda56..000000000 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -File that contains ATT&CK telemetry storing/retrieving logic -""" -import logging -from monkey_island.cc.database import mongo - -__author__ = "VakarisZ" - -logger = logging.getLogger(__name__) - - -class AttackTelemService(object): - def __init__(self): - pass - - @staticmethod - def set_results(technique, data): - """ - Adds ATT&CK technique results(telemetry) to the database - :param technique: technique ID string e.g. T1110 - :param data: Data, relevant to the technique - """ - data.update({'technique': technique}) - mongo.db.attack_results.insert(data) - mongo.db.attack_results.update({'name': 'latest'}, {'name': 'latest', 'time': data['time']}, upsert=True) - - - @staticmethod - def get_latest_telem(): - return mongo.db.attack_results.find_one({'name': 'latest'}) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index 002e2eeb7..a92758cbc 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -9,17 +9,19 @@ 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 = "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_type': 'system_info_collection', '$and': [{'data.credentials': {'$exists': True}}, - {'data.credentials': {'$gt': {}}}]} + 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(T1003.tech_id)} + data = {'title': T1003.technique_title()} if mongo.db.telemetry.count_documents(T1003.query): - data.update({'message': T1003.used_msg, 'status': ScanStatus.USED.name}) + status = ScanStatus.USED else: - data.update({'message': T1003.unscanned_msg, 'status': ScanStatus.UNSCANNED.name}) + 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 index e85e27415..328c11112 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -12,19 +12,23 @@ class T1059(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully ran commands on exploited machines in the network." - query = [{'$match': {'telem_type': 'exploit', - 'data.info.executed_cmds.example': {'$exists': True}}}, + 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'}}}] + {'$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(T1059.tech_id), 'cmds': cmd_data} + data = {'title': T1059.technique_title(), 'cmds': cmd_data} if cmd_data: - data.update({'message': T1059.used_msg, 'status': ScanStatus.USED.name}) + status = ScanStatus.USED else: - data.update({'message': T1059.unscanned_msg, 'status': ScanStatus.UNSCANNED.name}) + 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 index 2ac3d5420..fa65a66c2 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -16,7 +16,7 @@ class T1075(AttackTechnique): {'lm_hash': {'$ne': ''}}]}}} # Gets data about successful PTH logins - query = [{'$match': {'telem_type': 'exploit', + query = [{'$match': {'telem_category': 'exploit', 'data.attempts': {'$not': {'$size': 0}, '$elemMatch': {'$and': [{'$or': [{'ntlm_hash': {'$ne': ''}}, {'lm_hash': {'$ne': ''}}]}, @@ -31,13 +31,14 @@ class T1075(AttackTechnique): @staticmethod def get_report_data(): - data = {'title': T1075.technique_title(T1075.tech_id)} + data = {'title': T1075.technique_title()} successful_logins = list(mongo.db.telemetry.aggregate(T1075.query)) data.update({'successful_logins': successful_logins}) if successful_logins: - data.update({'message': T1075.used_msg, 'status': ScanStatus.USED.name}) + status = ScanStatus.USED elif mongo.db.telemetry.count_documents(T1075.login_attempt_query): - data.update({'message': T1075.scanned_msg, 'status': ScanStatus.SCANNED.name}) + status = ScanStatus.SCANNED else: - data.update({'message': T1075.unscanned_msg, 'status': ScanStatus.UNSCANNED.name}) + 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 index 1dc2e9a67..4114047c5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -12,19 +12,25 @@ class T1086(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully ran powershell commands on exploited machines in the network." - query = [{'$match': {'telem_type': 'exploit', - 'data.info.executed_cmds.powershell': {'$exists': True}}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', + 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(T1086.tech_id), 'cmds': cmd_data} + data = {'title': T1086.technique_title(), 'cmds': cmd_data} if cmd_data: - data.update({'message': T1086.used_msg, 'status': ScanStatus.USED.name}) + status = ScanStatus.USED else: - data.update({'message': T1086.unscanned_msg, 'status': ScanStatus.UNSCANNED.name}) + 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 040b05be2..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,12 +35,16 @@ class T1110(AttackTechnique): result['successful_creds'].append(T1110.parse_creds(attempt)) if succeeded: - data = {'message': T1110.used_msg, 'status': ScanStatus.USED.name} + status = ScanStatus.USED elif attempts: - data = {'message': T1110.scanned_msg, 'status': ScanStatus.SCANNED.name} + status = ScanStatus.SCANNED else: - data = {'message': T1110.unscanned_msg, 'status': ScanStatus.UNSCANNED.name} - data.update({'services': attempts, 'title': T1110.technique_title(T1110.tech_id)}) + 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()}) return data @staticmethod @@ -51,21 +55,39 @@ class T1110(AttackTechnique): :return: string with username and used password/hash """ username = attempt['user'] - if attempt['lm_hash']: - return '%s ; LM hash %s ...' % (username, encryptor.dec(attempt['lm_hash'])[0:5]) - if attempt['ntlm_hash']: - return '%s ; NTLM hash %s ...' % (username, encryptor.dec(attempt['ntlm_hash'])[0:20]) - if attempt['ssh_key']: - return '%s ; SSH key %s ...' % (username, encryptor.dec(attempt['ssh_key'])[0:15]) - if attempt['password']: - return '%s : %s' % (username, T1110.obfuscate_password(encryptor.dec(attempt['password']))) + creds = {'lm_hash': {'type': 'LM hash', 'output': T1110.censor_hash(attempt['lm_hash'])}, + 'ntlm_hash': {'type': 'NTLM hash', 'output': T1110.censor_hash(attempt['ntlm_hash'], 20)}, + 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']}, + 'password': {'type': 'Plaintext password', 'output': T1110.censor_password(attempt['password'])}} + for key, cred in creds.items(): + if attempt[key]: + return '%s ; %s : %s' % (username, + cred['type'], + cred['output']) @staticmethod - def obfuscate_password(password, plain_chars=3): + def censor_password(password, plain_chars=3, secret_chars=5): """ - Obfuscates password by changing characters to * + Decrypts and obfuscates password by changing characters to * :param password: Password or string to obfuscate :param plain_chars: How many plain-text characters should be kept at the start of the string + :param secret_chars: How many * symbols should be used to hide the remainder of the password :return: Obfuscated string e.g. Pass**** """ - return password[0:plain_chars] + '*' * (len(password) - plain_chars) + if not password: + return "" + password = encryptor.dec(password) + return password[0:plain_chars] + '*' * secret_chars + + @staticmethod + def censor_hash(hash_, plain_chars=5): + """ + Decrypts and obfuscates hash by only showing a part of it + :param hash_: Hash to obfuscate + :param plain_chars: How many chars of hash should be shown + :return: Obfuscated string + """ + if not hash_: + return "" + hash_ = encryptor.dec(hash_) + return hash_[0: plain_chars] + ' ...' 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 0aaab082b..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.attack_results.aggregate([{'$match': {'technique': T1197.tech_id}}, - {'$group': {'_id': {'ip_addr': '$machine.ip_addr', 'usage': '$usage'}, - 'ip_addr': {'$first': '$machine.ip_addr'}, - 'domain_name': {'$first': '$machine.domain_name'}, - 'usage': {'$first': '$usage'}, - 'time': {'$first': '$time'}} - }]) + 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 b6335422f..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_type': '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_type': '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 0346a1857..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,43 +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.attack_results.find_one({'status': ScanStatus.USED.value, '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.attack_results.find_one({'status': ScanStatus.SCANNED.value, '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 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) - data.update({'status': status.name, 'title': title}) - if status == ScanStatus.UNSCANNED: - data.update({'message': technique.unscanned_msg}) - elif status == ScanStatus.SCANNED: - data.update({'message': technique.scanned_msg}) - else: - data.update({'message': technique.used_msg}) + status = cls.technique_status() + title = cls.technique_title() + data.update({'status': status.name, + 'title': title, + 'message': cls.get_message_by_status(status)}) return data diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 702a42cd1..2a1a58d2e 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -171,7 +171,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_type': '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'] @@ -199,7 +199,7 @@ class ReportService: """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_type': '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'] @@ -220,7 +220,7 @@ class ReportService: """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_type': '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'] @@ -373,7 +373,7 @@ class ReportService: @staticmethod def get_exploits(): exploits = [] - for exploit in mongo.db.telemetry.find({'telem_type': '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) @@ -382,7 +382,7 @@ class ReportService: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {'telem_type': '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: @@ -540,7 +540,7 @@ class ReportService: @staticmethod def get_cross_segment_issues(): - scans = mongo.db.telemetry.find({'telem_type': '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 index b15bba693..785e4a295 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -1,6 +1,6 @@ import React from "react"; -export function RenderMachine(val){ +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/T1059.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js index d1c809651..57d5bcb2c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { RenderMachine } from "./Helpers" +import { renderMachine } from "./Helpers" class T1059 extends React.Component { @@ -10,13 +10,13 @@ class T1059 extends React.Component { super(props); } - static getHashColumns() { + static getCommandColumns() { return ([{ Header: 'Example 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.example, style: { 'whiteSpace': 'unset' }}, + {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' }}, ] }])}; @@ -27,7 +27,7 @@ class T1059 extends React.Component {
{this.props.data.status === 'USED' ? { - if(login.attempts[0].ntlm_hash !== ""){ - login.attempts[0].hashType = 'NTLM'; - } else if(login.attempts[0].lm_hash !== ""){ - login.attempts[0].hashType = 'LM'; - } - }) + 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: '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' }}, 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 index d0b7c2928..d6d22c093 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1086.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { RenderMachine } from "./Helpers" +import { renderMachine } from "./Helpers" class T1086 extends React.Component { @@ -14,9 +14,9 @@ class T1086 extends React.Component { return ([{ Header: 'Example Powershell commands used', columns: [ - {Header: 'Machine', id: 'machine', accessor: x => RenderMachine(x.data[0].machine), style: { 'whiteSpace': 'unset'}, width: 160 }, + {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.powershell, style: { 'whiteSpace': 'unset' }}, + {Header: 'Command', id: 'command', accessor: x => x.data[0].info.executed_cmds[0].cmd, style: { 'whiteSpace': 'unset' }}, ] }])}; 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 468a9e79a..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,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { RenderMachine } from "./Helpers" +import { renderMachine } from "./Helpers" class T1110 extends React.Component { @@ -13,7 +13,7 @@ class T1110 extends React.Component { static getServiceColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => 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' }}, 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 e4c7d841d..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,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { RenderMachine } from "./Helpers" +import { renderMachine } from "./Helpers" class T1210 extends React.Component { @@ -9,7 +9,7 @@ class T1210 extends React.Component { constructor(props) { super(props); this.columns = [ {Header: 'Machine', - id: 'machine', accessor: x => RenderMachine(x), + id: 'machine', accessor: x => renderMachine(x), style: { 'whiteSpace': 'unset' }, width: 200}, {Header: 'Time', 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 76bc5d196..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,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { RenderMachine } from "./Helpers" +import { renderMachine } from "./Helpers" class T1210 extends React.Component { @@ -13,7 +13,7 @@ class T1210 extends React.Component { static getScanColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => 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' }}, @@ -24,7 +24,7 @@ class T1210 extends React.Component { static getExploitColumns() { return ([{ columns: [ - {Header: 'Machine', id: 'machine', accessor: x => 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' }}, diff --git a/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js index a23dd1d36..120344eea 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js @@ -11,7 +11,7 @@ const renderTime = (val) => val.split('.')[0]; const columns = [ { title: 'Time', prop: 'timestamp', render: renderTime}, { title: 'Monkey', prop: 'monkey'}, - { title: 'Type', prop: 'telem_type'}, + { title: 'Type', prop: 'telem_catagory'}, { title: 'Details', prop: 'data', render: renderJson, width: '40%' } ];