diff --git a/monkey/common/tags/__init__.py b/monkey/common/tags/__init__.py index ea08aa9f5..fb30c71df 100644 --- a/monkey/common/tags/__init__.py +++ b/monkey/common/tags/__init__.py @@ -2,6 +2,7 @@ from .attack import ( T1003_ATTACK_TECHNIQUE_TAG, T1005_ATTACK_TECHNIQUE_TAG, T1021_ATTACK_TECHNIQUE_TAG, + T1059_ATTACK_TECHNIQUE_TAG, T1098_ATTACK_TECHNIQUE_TAG, T1105_ATTACK_TECHNIQUE_TAG, T1110_ATTACK_TECHNIQUE_TAG, diff --git a/monkey/common/tags/attack.py b/monkey/common/tags/attack.py index e8881dfa7..5c3a8d117 100644 --- a/monkey/common/tags/attack.py +++ b/monkey/common/tags/attack.py @@ -1,6 +1,7 @@ T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003" T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005" T1021_ATTACK_TECHNIQUE_TAG = "attack-t1021" +T1059_ATTACK_TECHNIQUE_TAG = "attack-t1059" T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098" T1105_ATTACK_TECHNIQUE_TAG = "attack-t1105" T1110_ATTACK_TECHNIQUE_TAG = "attack-t1110" diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index ff0f0db4e..b8efce04b 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -1,8 +1,14 @@ import logging from pathlib import Path, PurePath +from time import time from typing import List, Optional from common import OperatingSystem +from common.tags import ( + T1059_ATTACK_TECHNIQUE_TAG, + T1105_ATTACK_TECHNIQUE_TAG, + T1110_ATTACK_TECHNIQUE_TAG, +) from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options from infection_monkey.exploit.powershell_utils.credentials import ( @@ -21,6 +27,7 @@ from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) +POWERSHELL_EXPLOITER_TAG = "powershell-exploiter" class RemoteAgentCopyError(Exception): @@ -34,6 +41,17 @@ class RemoteAgentExecutionError(Exception): class PowerShellExploiter(HostExploiter): _EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)" + _EXPLOITER_TAGS = ( + POWERSHELL_EXPLOITER_TAG, + T1059_ATTACK_TECHNIQUE_TAG, + T1110_ATTACK_TECHNIQUE_TAG, + ) + _PROPAGATION_TAGS = ( + POWERSHELL_EXPLOITER_TAG, + T1059_ATTACK_TECHNIQUE_TAG, + T1105_ATTACK_TECHNIQUE_TAG, + ) + def __init__(self): super().__init__() self._client = None @@ -68,12 +86,21 @@ class PowerShellExploiter(HostExploiter): ) return self.exploit_result + execute_agent_timestamp = time() try: self._execute_monkey_agent_on_victim() - self.exploit_result.propagation_success = True - except Exception as ex: - logger.error(f"Failed to propagate to the remote host: {ex}") - self.exploit_result.error_message = str(ex) + except Exception as err: + self.exploit_result.error_message = f"Failed to propagate to the remote host: {err}" + self._publish_propagation_event( + time=execute_agent_timestamp, + success=False, + error_message=self.exploit_result.error_message, + ) + logger.error(self.exploit_result.error_message) + return self.exploit_result + + self.exploit_result.propagation_success = True + self._publish_propagation_event(time=execute_agent_timestamp, success=True) return self.exploit_result @@ -94,21 +121,27 @@ class PowerShellExploiter(HostExploiter): try: client = PowerShellClient(self.host.ip_addr, creds, opts) + connect_timestamp = time() client.connect() logger.info( f"Successfully logged into {self.host.ip_addr} using Powershell. User: " f"{creds.username}, Secret Type: {creds.secret_type.name}" ) + self._publish_exploitation_event(time=connect_timestamp, success=True) self.exploit_result.exploitation_success = True self._report_login_attempt(True, creds) return client except Exception as ex: - logger.debug( + error_message = ( f"Error logging into {self.host.ip_addr} using Powershell. User: " f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}" ) + logger.debug(error_message) + self._publish_exploitation_event( + time=connect_timestamp, success=False, error_message=error_message + ) self._report_login_attempt(False, creds) return None diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py index 97096830d..5fb98ea93 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -16,6 +16,7 @@ LM_HASH_LIST = ["bogo_lm_1"] NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"] bogus_servers = ["1.1.1.1:5000", "2.2.2.2:5007"] +VICTIM_IP = "10.10.10.1" mock_agent_binary_repository = MagicMock() @@ -23,7 +24,25 @@ mock_agent_binary_repository.get_agent_binary.return_value = BytesIO(b"BINARY_EX @pytest.fixture -def powershell_arguments(http_and_https_both_enabled_host): +def host_with_ip_address(http_and_https_both_enabled_host): + http_and_https_both_enabled_host.ip_addr = VICTIM_IP + return http_and_https_both_enabled_host + + +@pytest.fixture +def http_host_with_ip_address(http_only_host): + http_only_host.ip_addr = VICTIM_IP + return http_only_host + + +@pytest.fixture +def https_host_with_ip_address(https_only_host): + https_only_host.ip_addr = VICTIM_IP + return https_only_host + + +@pytest.fixture +def powershell_arguments(host_with_ip_address): options = { "credentials": { "exploit_user_list": USER_LIST, @@ -33,7 +52,7 @@ def powershell_arguments(http_and_https_both_enabled_host): }, } arguments = { - "host": http_and_https_both_enabled_host, + "host": host_with_ip_address, "servers": bogus_servers, "options": options, "current_depth": 2, @@ -63,8 +82,10 @@ def test_powershell_disabled(powershell_exploiter, powershell_arguments, powersh assert "disabled" in exploit_result.error_message -def test_powershell_http(monkeypatch, powershell_exploiter, powershell_arguments, http_only_host): - powershell_arguments["host"] = http_only_host +def test_powershell_http( + monkeypatch, powershell_exploiter, powershell_arguments, http_host_with_ip_address +): + powershell_arguments["host"] = http_host_with_ip_address mock_powershell_client = MagicMock() monkeypatch.setattr( @@ -77,7 +98,7 @@ def test_powershell_http(monkeypatch, powershell_exploiter, powershell_arguments assert not call_args[0][2].ssl -def test_powershell_https(monkeypatch, powershell_exploiter, powershell_arguments, https_only_host): +def test_powershell_https(monkeypatch, powershell_exploiter, powershell_arguments): mock_powershell_client = MagicMock() mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client) @@ -191,11 +212,11 @@ def test_build_monkey_execution_command(): def test_skip_http_only_logins( - monkeypatch, powershell_exploiter, powershell_arguments, https_only_host + monkeypatch, powershell_exploiter, powershell_arguments, https_host_with_ip_address ): # Only HTTPS is enabled on the destination, so we should never try to connect with "" empty # password, since connection with empty password requires SSL == False. - powershell_arguments["host"] = https_only_host + powershell_arguments["host"] = https_host_with_ip_address mock_powershell_client = MagicMock() mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login"))