From 45658b5559b3c0b3dafbe4c6ff88a5f973fc83f7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Mar 2022 13:49:51 -0400 Subject: [PATCH] Agent: Skip empty password attempts in PowerShell if HTTP disabled --- monkey/infection_monkey/exploit/powershell.py | 27 ++++++++++++++++--- .../exploit/test_powershell.py | 25 +++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index 3d5a41131..457eccd11 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -39,7 +39,7 @@ class PowerShellExploiter(HostExploiter): self._client = None def _exploit_host(self): - if not self._is_any_default_port_open(): + if not self._any_powershell_port_is_open(): message = "PowerShell Remoting appears to be disabled on the remote host" self.exploit_result.error_message = message logger.debug(message) @@ -77,13 +77,21 @@ class PowerShellExploiter(HostExploiter): return self.exploit_result - def _is_any_default_port_open(self) -> bool: - return "tcp-5985" in self.host.services or "tcp-5986" in self.host.services + def _any_powershell_port_is_open(self) -> bool: + return self._http_powershell_port_is_open() or self._https_powershell_port_is_open() + + def _http_powershell_port_is_open(self) -> bool: + return "tcp-5985" in self.host.services + + def _https_powershell_port_is_open(self) -> bool: + return "tcp-5986" in self.host.services def _authenticate_via_brute_force( self, credentials: List[Credentials], auth_options: List[AuthOptions] ) -> Optional[IPowerShellClient]: - for (creds, opts) in interruptible_iter(zip(credentials, auth_options), self.interrupt): + creds_opts_pairs = filter(self.check_ssl_setting_is_valid, zip(credentials, auth_options)) + for (creds, opts) in interruptible_iter(creds_opts_pairs, self.interrupt): + try: client = PowerShellClient(self.host.ip_addr, creds, opts) client.connect() @@ -105,6 +113,17 @@ class PowerShellExploiter(HostExploiter): return None + def check_ssl_setting_is_valid(self, creds_opts_pair): + opts = creds_opts_pair[1] + + if opts.ssl and not self._https_powershell_port_is_open(): + return False + + if not opts.ssl and not self._http_powershell_port_is_open(): + return False + + return True + 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) 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 698c7ac2d..78ba133af 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -76,8 +76,6 @@ def test_powershell_http(monkeypatch, powershell_exploiter, powershell_arguments def test_powershell_https(monkeypatch, powershell_exploiter, powershell_arguments, https_only_host): - powershell_arguments["host"] = https_only_host - mock_powershell_client = MagicMock() mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client) @@ -85,11 +83,15 @@ def test_powershell_https(monkeypatch, powershell_exploiter, powershell_argument powershell_exploiter.exploit_host(**powershell_arguments) + non_ssl_calls = 0 for call_args in mock_powershell_client_constructor.call_args_list: if call_args[0][1].secret != "": assert call_args[0][2].ssl else: assert not call_args[0][2].ssl + non_ssl_calls += 1 + + assert non_ssl_calls > 0 def test_no_valid_credentials(monkeypatch, powershell_exploiter, powershell_arguments): @@ -185,3 +187,22 @@ def test_build_monkey_execution_command(): assert f"-d {depth}" in cmd assert executable_path in cmd + + +def test_skip_http_only_logins( + monkeypatch, powershell_exploiter, powershell_arguments, https_only_host +): + # 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 + + mock_powershell_client = MagicMock() + mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) + mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client) + monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client_constructor) + + powershell_exploiter.exploit_host(**powershell_arguments) + + for call_args in mock_powershell_client_constructor.call_args_list: + assert call_args[0][1].secret != "" + assert call_args[0][2].ssl