Agent: Skip empty password attempts in PowerShell if HTTP disabled

This commit is contained in:
Mike Salvatore 2022-03-23 13:49:51 -04:00
parent 06899be264
commit 45658b5559
2 changed files with 46 additions and 6 deletions

View File

@ -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)

View File

@ -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