forked from p15670423/monkey
Merge pull request #1817 from guardicore/1801-credentials-store
1801 credentials store
This commit is contained in:
commit
2992d91f16
|
@ -0,0 +1,2 @@
|
||||||
|
from .i_credentials_store import ICredentialsStore
|
||||||
|
from .aggregating_credentials_store import AggregatingCredentialsStore
|
|
@ -0,0 +1,91 @@
|
||||||
|
import logging
|
||||||
|
from typing import Any, Iterable, Mapping
|
||||||
|
|
||||||
|
from common.common_consts.credential_component_type import CredentialComponentType
|
||||||
|
from infection_monkey.i_control_channel import IControlChannel
|
||||||
|
from infection_monkey.i_puppet import Credentials
|
||||||
|
from infection_monkey.typing import PropagationCredentials
|
||||||
|
from infection_monkey.utils.decorators import request_cache
|
||||||
|
|
||||||
|
from .i_credentials_store import ICredentialsStore
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CREDENTIALS_POLL_PERIOD_SEC = 30
|
||||||
|
|
||||||
|
|
||||||
|
class AggregatingCredentialsStore(ICredentialsStore):
|
||||||
|
def __init__(self, control_channel: IControlChannel):
|
||||||
|
self._stored_credentials = {
|
||||||
|
"exploit_user_list": set(),
|
||||||
|
"exploit_password_list": set(),
|
||||||
|
"exploit_lm_hash_list": set(),
|
||||||
|
"exploit_ntlm_hash_list": set(),
|
||||||
|
"exploit_ssh_keys": [],
|
||||||
|
}
|
||||||
|
self._control_channel = control_channel
|
||||||
|
|
||||||
|
def add_credentials(self, credentials_to_add: Iterable[Credentials]):
|
||||||
|
for credentials in credentials_to_add:
|
||||||
|
usernames = {
|
||||||
|
identity.username
|
||||||
|
for identity in credentials.identities
|
||||||
|
if identity.credential_type is CredentialComponentType.USERNAME
|
||||||
|
}
|
||||||
|
self._stored_credentials.setdefault("exploit_user_list", set()).update(usernames)
|
||||||
|
|
||||||
|
for secret in credentials.secrets:
|
||||||
|
if secret.credential_type is CredentialComponentType.PASSWORD:
|
||||||
|
self._stored_credentials.setdefault("exploit_password_list", set()).add(
|
||||||
|
secret.password
|
||||||
|
)
|
||||||
|
elif secret.credential_type is CredentialComponentType.LM_HASH:
|
||||||
|
self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add(
|
||||||
|
secret.lm_hash
|
||||||
|
)
|
||||||
|
elif secret.credential_type is CredentialComponentType.NT_HASH:
|
||||||
|
self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add(
|
||||||
|
secret.nt_hash
|
||||||
|
)
|
||||||
|
elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR:
|
||||||
|
self._set_attribute(
|
||||||
|
"exploit_ssh_keys",
|
||||||
|
[{"public_key": secret.public_key, "private_key": secret.private_key}],
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_credentials(self) -> PropagationCredentials:
|
||||||
|
try:
|
||||||
|
propagation_credentials = self._get_credentials_from_control_channel()
|
||||||
|
|
||||||
|
# Needs to be reworked when exploiters accepts sequence of Credentials
|
||||||
|
self._aggregate_credentials(propagation_credentials)
|
||||||
|
|
||||||
|
return self._stored_credentials
|
||||||
|
except Exception as ex:
|
||||||
|
self._stored_credentials = {}
|
||||||
|
logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}")
|
||||||
|
|
||||||
|
@request_cache(CREDENTIALS_POLL_PERIOD_SEC)
|
||||||
|
def _get_credentials_from_control_channel(self) -> PropagationCredentials:
|
||||||
|
return self._control_channel.get_credentials_for_propagation()
|
||||||
|
|
||||||
|
def _aggregate_credentials(self, credentials_to_aggr: Mapping):
|
||||||
|
for cred_attr, credentials_values in credentials_to_aggr.items():
|
||||||
|
self._set_attribute(cred_attr, credentials_values)
|
||||||
|
|
||||||
|
def _set_attribute(self, attribute_to_be_set: str, credentials_values: Iterable[Any]):
|
||||||
|
if not credentials_values:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(credentials_values[0], dict):
|
||||||
|
self._stored_credentials.setdefault(attribute_to_be_set, []).extend(credentials_values)
|
||||||
|
self._stored_credentials[attribute_to_be_set] = [
|
||||||
|
dict(s_c)
|
||||||
|
for s_c in set(
|
||||||
|
frozenset(d_c.items()) for d_c in self._stored_credentials[attribute_to_be_set]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self._stored_credentials.setdefault(attribute_to_be_set, set()).update(
|
||||||
|
credentials_values
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
import abc
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from infection_monkey.i_puppet import Credentials
|
||||||
|
from infection_monkey.typing import PropagationCredentials
|
||||||
|
|
||||||
|
|
||||||
|
class ICredentialsStore(metaclass=abc.ABCMeta):
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_credentials(self, credentials_to_add: Iterable[Credentials]):
|
||||||
|
"""
|
||||||
|
Adds credentials to the CredentialStore
|
||||||
|
:param Iterable[Credentials] credentials: The credentials that will be added
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_credentials(self) -> PropagationCredentials:
|
||||||
|
"""
|
||||||
|
Retrieves credentials from the store
|
||||||
|
:return: Credentials that can be used for propagation
|
||||||
|
:type: PropagationCredentials
|
||||||
|
"""
|
|
@ -60,7 +60,7 @@ class SSHExploiter(HostExploiter):
|
||||||
for user, ssh_key_pair in ssh_key_pairs_iterator:
|
for user, ssh_key_pair in ssh_key_pairs_iterator:
|
||||||
# Creating file-like private key for paramiko
|
# Creating file-like private key for paramiko
|
||||||
pkey = io.StringIO(ssh_key_pair["private_key"])
|
pkey = io.StringIO(ssh_key_pair["private_key"])
|
||||||
ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"])
|
ssh_string = "%s@%s" % (user, self.host.ip_addr)
|
||||||
|
|
||||||
ssh = paramiko.SSHClient()
|
ssh = paramiko.SSHClient()
|
||||||
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||||
|
|
|
@ -91,8 +91,9 @@ class ZerologonExploiter(HostExploiter):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def connect_to_dc(dc_ip) -> object:
|
def connect_to_dc(dc_ip) -> object:
|
||||||
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
|
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
|
||||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
rpc_transport = transport.DCERPCTransportFactory(binding)
|
||||||
rpc_con.set_connect_timeout(LONG_REQUEST_TIMEOUT)
|
rpc_transport.set_connect_timeout(LONG_REQUEST_TIMEOUT)
|
||||||
|
rpc_con = rpc_transport.get_dce_rpc()
|
||||||
rpc_con.connect()
|
rpc_con.connect()
|
||||||
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
||||||
return rpc_con
|
return rpc_con
|
||||||
|
|
|
@ -3,6 +3,7 @@ import threading
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
|
from infection_monkey.credential_store import ICredentialsStore
|
||||||
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
||||||
from infection_monkey.i_master import IMaster
|
from infection_monkey.i_master import IMaster
|
||||||
from infection_monkey.i_puppet import IPuppet
|
from infection_monkey.i_puppet import IPuppet
|
||||||
|
@ -36,6 +37,7 @@ class AutomatedMaster(IMaster):
|
||||||
victim_host_factory: VictimHostFactory,
|
victim_host_factory: VictimHostFactory,
|
||||||
control_channel: IControlChannel,
|
control_channel: IControlChannel,
|
||||||
local_network_interfaces: List[NetworkInterface],
|
local_network_interfaces: List[NetworkInterface],
|
||||||
|
credentials_store: ICredentialsStore,
|
||||||
):
|
):
|
||||||
self._current_depth = current_depth
|
self._current_depth = current_depth
|
||||||
self._puppet = puppet
|
self._puppet = puppet
|
||||||
|
@ -43,9 +45,8 @@ class AutomatedMaster(IMaster):
|
||||||
self._control_channel = control_channel
|
self._control_channel = control_channel
|
||||||
|
|
||||||
ip_scanner = IPScanner(self._puppet, NUM_SCAN_THREADS)
|
ip_scanner = IPScanner(self._puppet, NUM_SCAN_THREADS)
|
||||||
exploiter = Exploiter(
|
|
||||||
self._puppet, NUM_EXPLOIT_THREADS, self._control_channel.get_credentials_for_propagation
|
exploiter = Exploiter(self._puppet, NUM_EXPLOIT_THREADS, credentials_store.get_credentials)
|
||||||
)
|
|
||||||
self._propagator = Propagator(
|
self._propagator = Propagator(
|
||||||
self._telemetry_messenger,
|
self._telemetry_messenger,
|
||||||
ip_scanner,
|
ip_scanner,
|
||||||
|
|
|
@ -7,14 +7,12 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
|
||||||
from infection_monkey.config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
from infection_monkey.control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
||||||
from infection_monkey.utils.decorators import request_cache
|
from infection_monkey.typing import PropagationCredentials
|
||||||
|
|
||||||
requests.packages.urllib3.disable_warnings()
|
requests.packages.urllib3.disable_warnings()
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
CREDENTIALS_POLL_PERIOD_SEC = 30
|
|
||||||
|
|
||||||
|
|
||||||
class ControlChannel(IControlChannel):
|
class ControlChannel(IControlChannel):
|
||||||
def __init__(self, server: str, agent_id: str):
|
def __init__(self, server: str, agent_id: str):
|
||||||
|
@ -69,8 +67,7 @@ class ControlChannel(IControlChannel):
|
||||||
) as e:
|
) as e:
|
||||||
raise IslandCommunicationError(e)
|
raise IslandCommunicationError(e)
|
||||||
|
|
||||||
@request_cache(CREDENTIALS_POLL_PERIOD_SEC)
|
def get_credentials_for_propagation(self) -> PropagationCredentials:
|
||||||
def get_credentials_for_propagation(self) -> dict:
|
|
||||||
propagation_credentials_url = (
|
propagation_credentials_url = (
|
||||||
f"https://{self._control_channel_server}/api/propagation-credentials/{self._agent_id}"
|
f"https://{self._control_channel_server}/api/propagation-credentials/{self._agent_id}"
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from typing import Callable, Dict, List, Mapping
|
||||||
|
|
||||||
from infection_monkey.i_puppet import ExploiterResultData, IPuppet
|
from infection_monkey.i_puppet import ExploiterResultData, IPuppet
|
||||||
from infection_monkey.model import VictimHost
|
from infection_monkey.model import VictimHost
|
||||||
|
from infection_monkey.typing import PropagationCredentials
|
||||||
from infection_monkey.utils.threading import interruptible_iter, run_worker_threads
|
from infection_monkey.utils.threading import interruptible_iter, run_worker_threads
|
||||||
|
|
||||||
QUEUE_TIMEOUT = 2
|
QUEUE_TIMEOUT = 2
|
||||||
|
@ -24,7 +25,7 @@ class Exploiter:
|
||||||
self,
|
self,
|
||||||
puppet: IPuppet,
|
puppet: IPuppet,
|
||||||
num_workers: int,
|
num_workers: int,
|
||||||
get_updated_credentials_for_propagation: Callable[[], Mapping],
|
get_updated_credentials_for_propagation: Callable[[], PropagationCredentials],
|
||||||
):
|
):
|
||||||
self._puppet = puppet
|
self._puppet = puppet
|
||||||
self._num_workers = num_workers
|
self._num_workers = num_workers
|
||||||
|
@ -160,7 +161,7 @@ class Exploiter:
|
||||||
exploitation_success=False, propagation_success=False, error_message=msg
|
exploitation_success=False, propagation_success=False, error_message=msg
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_credentials_for_propagation(self) -> Mapping:
|
def _get_credentials_for_propagation(self) -> PropagationCredentials:
|
||||||
try:
|
try:
|
||||||
return self._get_updated_credentials_for_propagation()
|
return self._get_updated_credentials_for_propagation()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
|
|
@ -15,6 +15,7 @@ from infection_monkey.credential_collectors import (
|
||||||
MimikatzCredentialCollector,
|
MimikatzCredentialCollector,
|
||||||
SSHCredentialCollector,
|
SSHCredentialCollector,
|
||||||
)
|
)
|
||||||
|
from infection_monkey.credential_store import AggregatingCredentialsStore, ICredentialsStore
|
||||||
from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper
|
from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper
|
||||||
from infection_monkey.exploit.hadoop import HadoopExploiter
|
from infection_monkey.exploit.hadoop import HadoopExploiter
|
||||||
from infection_monkey.exploit.log4shell import Log4ShellExploiter
|
from infection_monkey.exploit.log4shell import Log4ShellExploiter
|
||||||
|
@ -54,6 +55,9 @@ from infection_monkey.puppet.puppet import Puppet
|
||||||
from infection_monkey.system_singleton import SystemSingleton
|
from infection_monkey.system_singleton import SystemSingleton
|
||||||
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
||||||
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
|
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
|
||||||
|
from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import (
|
||||||
|
CredentialsInterceptingTelemetryMessenger,
|
||||||
|
)
|
||||||
from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import (
|
from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import (
|
||||||
ExploitInterceptingTelemetryMessenger,
|
ExploitInterceptingTelemetryMessenger,
|
||||||
)
|
)
|
||||||
|
@ -84,7 +88,7 @@ class InfectionMonkey:
|
||||||
self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.server)
|
self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.server)
|
||||||
self._default_server = self._opts.server
|
self._default_server = self._opts.server
|
||||||
self._monkey_inbound_tunnel = None
|
self._monkey_inbound_tunnel = None
|
||||||
self.telemetry_messenger = LegacyTelemetryMessengerAdapter()
|
self._telemetry_messenger = LegacyTelemetryMessengerAdapter()
|
||||||
self._current_depth = self._opts.depth
|
self._current_depth = self._opts.depth
|
||||||
self._master = None
|
self._master = None
|
||||||
|
|
||||||
|
@ -119,7 +123,7 @@ class InfectionMonkey:
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
|
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
|
||||||
|
|
||||||
run_aws_environment_check(self.telemetry_messenger)
|
run_aws_environment_check(self._telemetry_messenger)
|
||||||
|
|
||||||
should_stop = ControlChannel(WormConfiguration.current_server, GUID).should_agent_stop()
|
should_stop = ControlChannel(WormConfiguration.current_server, GUID).should_agent_stop()
|
||||||
if should_stop:
|
if should_stop:
|
||||||
|
@ -175,12 +179,23 @@ class InfectionMonkey:
|
||||||
|
|
||||||
def _build_master(self):
|
def _build_master(self):
|
||||||
local_network_interfaces = InfectionMonkey._get_local_network_interfaces()
|
local_network_interfaces = InfectionMonkey._get_local_network_interfaces()
|
||||||
puppet = self._build_puppet()
|
|
||||||
|
control_channel = ControlChannel(self._default_server, GUID)
|
||||||
|
credentials_store = AggregatingCredentialsStore(control_channel)
|
||||||
|
|
||||||
|
puppet = self._build_puppet(credentials_store)
|
||||||
|
|
||||||
victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
|
victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
|
||||||
|
|
||||||
telemetry_messenger = ExploitInterceptingTelemetryMessenger(
|
telemetry_messenger = ExploitInterceptingTelemetryMessenger(
|
||||||
self.telemetry_messenger, self._monkey_inbound_tunnel
|
self._telemetry_messenger, self._monkey_inbound_tunnel
|
||||||
|
)
|
||||||
|
|
||||||
|
telemetry_messenger = CredentialsInterceptingTelemetryMessenger(
|
||||||
|
ExploitInterceptingTelemetryMessenger(
|
||||||
|
self._telemetry_messenger, self._monkey_inbound_tunnel
|
||||||
|
),
|
||||||
|
credentials_store,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._master = AutomatedMaster(
|
self._master = AutomatedMaster(
|
||||||
|
@ -188,8 +203,9 @@ class InfectionMonkey:
|
||||||
puppet,
|
puppet,
|
||||||
telemetry_messenger,
|
telemetry_messenger,
|
||||||
victim_host_factory,
|
victim_host_factory,
|
||||||
ControlChannel(self._default_server, GUID),
|
control_channel,
|
||||||
local_network_interfaces,
|
local_network_interfaces,
|
||||||
|
credentials_store,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -200,7 +216,7 @@ class InfectionMonkey:
|
||||||
|
|
||||||
return local_network_interfaces
|
return local_network_interfaces
|
||||||
|
|
||||||
def _build_puppet(self) -> IPuppet:
|
def _build_puppet(self, credentials_store: ICredentialsStore) -> IPuppet:
|
||||||
puppet = Puppet()
|
puppet = Puppet()
|
||||||
|
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
|
@ -210,7 +226,7 @@ class InfectionMonkey:
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"SSHCollector",
|
"SSHCollector",
|
||||||
SSHCredentialCollector(self.telemetry_messenger),
|
SSHCredentialCollector(self._telemetry_messenger),
|
||||||
PluginType.CREDENTIAL_COLLECTOR,
|
PluginType.CREDENTIAL_COLLECTOR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -223,7 +239,7 @@ class InfectionMonkey:
|
||||||
agent_repository = CachingAgentRepository(
|
agent_repository = CachingAgentRepository(
|
||||||
f"https://{self._default_server}", ControlClient.proxies
|
f"https://{self._default_server}", ControlClient.proxies
|
||||||
)
|
)
|
||||||
exploit_wrapper = ExploiterWrapper(self.telemetry_messenger, agent_repository)
|
exploit_wrapper = ExploiterWrapper(self._telemetry_messenger, agent_repository)
|
||||||
|
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"HadoopExploiter", exploit_wrapper.wrap(HadoopExploiter), PluginType.EXPLOITER
|
"HadoopExploiter", exploit_wrapper.wrap(HadoopExploiter), PluginType.EXPLOITER
|
||||||
|
@ -240,62 +256,67 @@ class InfectionMonkey:
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"MSSQLExploiter", exploit_wrapper.wrap(MSSQLExploiter), PluginType.EXPLOITER
|
"MSSQLExploiter", exploit_wrapper.wrap(MSSQLExploiter), PluginType.EXPLOITER
|
||||||
)
|
)
|
||||||
|
|
||||||
|
zerologon_telemetry_messenger = CredentialsInterceptingTelemetryMessenger(
|
||||||
|
self._telemetry_messenger, credentials_store
|
||||||
|
)
|
||||||
|
zerologon_wrapper = ExploiterWrapper(zerologon_telemetry_messenger, agent_repository)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"ZerologonExploiter",
|
"ZerologonExploiter",
|
||||||
exploit_wrapper.wrap(ZerologonExploiter),
|
zerologon_wrapper.wrap(ZerologonExploiter),
|
||||||
PluginType.EXPLOITER,
|
PluginType.EXPLOITER,
|
||||||
)
|
)
|
||||||
|
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"CommunicateAsBackdoorUser",
|
"CommunicateAsBackdoorUser",
|
||||||
CommunicateAsBackdoorUser(self.telemetry_messenger),
|
CommunicateAsBackdoorUser(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"ModifyShellStartupFiles",
|
"ModifyShellStartupFiles",
|
||||||
ModifyShellStartupFiles(self.telemetry_messenger),
|
ModifyShellStartupFiles(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"HiddenFiles", HiddenFiles(self.telemetry_messenger), PluginType.POST_BREACH_ACTION
|
"HiddenFiles", HiddenFiles(self._telemetry_messenger), PluginType.POST_BREACH_ACTION
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"TrapCommand",
|
"TrapCommand",
|
||||||
CommunicateAsBackdoorUser(self.telemetry_messenger),
|
CommunicateAsBackdoorUser(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"ChangeSetuidSetgid",
|
"ChangeSetuidSetgid",
|
||||||
ChangeSetuidSetgid(self.telemetry_messenger),
|
ChangeSetuidSetgid(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"ScheduleJobs", ScheduleJobs(self.telemetry_messenger), PluginType.POST_BREACH_ACTION
|
"ScheduleJobs", ScheduleJobs(self._telemetry_messenger), PluginType.POST_BREACH_ACTION
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"Timestomping", Timestomping(self.telemetry_messenger), PluginType.POST_BREACH_ACTION
|
"Timestomping", Timestomping(self._telemetry_messenger), PluginType.POST_BREACH_ACTION
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"AccountDiscovery",
|
"AccountDiscovery",
|
||||||
AccountDiscovery(self.telemetry_messenger),
|
AccountDiscovery(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"ProcessListCollection",
|
"ProcessListCollection",
|
||||||
ProcessListCollection(self.telemetry_messenger),
|
ProcessListCollection(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"TrapCommand", TrapCommand(self.telemetry_messenger), PluginType.POST_BREACH_ACTION
|
"TrapCommand", TrapCommand(self._telemetry_messenger), PluginType.POST_BREACH_ACTION
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"SignedScriptProxyExecution",
|
"SignedScriptProxyExecution",
|
||||||
SignedScriptProxyExecution(self.telemetry_messenger),
|
SignedScriptProxyExecution(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"ClearCommandHistory",
|
"ClearCommandHistory",
|
||||||
ClearCommandHistory(self.telemetry_messenger),
|
ClearCommandHistory(self._telemetry_messenger),
|
||||||
PluginType.POST_BREACH_ACTION,
|
PluginType.POST_BREACH_ACTION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,10 @@ class CredentialsTelem(BaseTelem):
|
||||||
"""
|
"""
|
||||||
self._credentials = credentials
|
self._credentials = credentials
|
||||||
|
|
||||||
|
@property
|
||||||
|
def credentials(self) -> Iterable[Credentials]:
|
||||||
|
return iter(self._credentials)
|
||||||
|
|
||||||
def send(self, log_data=True):
|
def send(self, log_data=True):
|
||||||
super().send(log_data=False)
|
super().send(log_data=False)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
from functools import singledispatch
|
||||||
|
|
||||||
|
from infection_monkey.credential_store import ICredentialsStore
|
||||||
|
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
|
||||||
|
from infection_monkey.telemetry.i_telem import ITelem
|
||||||
|
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialsInterceptingTelemetryMessenger(ITelemetryMessenger):
|
||||||
|
def __init__(
|
||||||
|
self, telemetry_messenger: ITelemetryMessenger, credentials_store: ICredentialsStore
|
||||||
|
):
|
||||||
|
self._telemetry_messenger = telemetry_messenger
|
||||||
|
self._credentials_store = credentials_store
|
||||||
|
|
||||||
|
def send_telemetry(self, telemetry: ITelem):
|
||||||
|
_send_telemetry(telemetry, self._telemetry_messenger, self._credentials_store)
|
||||||
|
|
||||||
|
|
||||||
|
# Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or
|
||||||
|
# later.
|
||||||
|
@singledispatch
|
||||||
|
def _send_telemetry(
|
||||||
|
telemetry: ITelem,
|
||||||
|
telemetry_messenger: ITelemetryMessenger,
|
||||||
|
credentials_store: ICredentialsStore,
|
||||||
|
):
|
||||||
|
telemetry_messenger.send_telemetry(telemetry)
|
||||||
|
|
||||||
|
|
||||||
|
@_send_telemetry.register
|
||||||
|
def _(
|
||||||
|
telemetry: CredentialsTelem,
|
||||||
|
telemetry_messenger: ITelemetryMessenger,
|
||||||
|
credentials_store: ICredentialsStore,
|
||||||
|
):
|
||||||
|
credentials_store.add_credentials(telemetry.credentials)
|
||||||
|
telemetry_messenger.send_telemetry(telemetry)
|
|
@ -0,0 +1,3 @@
|
||||||
|
from typing import Iterable, Mapping
|
||||||
|
|
||||||
|
PropagationCredentials = Mapping[str, Iterable[str]]
|
|
@ -0,0 +1,95 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from infection_monkey.credential_collectors import Password, SSHKeypair, Username
|
||||||
|
from infection_monkey.credential_store import AggregatingCredentialsStore
|
||||||
|
from infection_monkey.i_puppet import Credentials
|
||||||
|
|
||||||
|
CONTROL_CHANNEL_CREDENTIALS = {
|
||||||
|
"exploit_user_list": ["Administrator", "root", "user1"],
|
||||||
|
"exploit_password_list": ["123456", "123456789", "password", "root"],
|
||||||
|
"exploit_lm_hash_list": ["aasdf23asd1fdaasadasdfas"],
|
||||||
|
"exploit_ntlm_hash_list": ["asdfadvxvsdftw3e3421234123412", "qw4trklxklvznksbhasd1231"],
|
||||||
|
"exploit_ssh_keys": [
|
||||||
|
{"public_key": "some_public_key", "private_key": "some_private_key"},
|
||||||
|
{
|
||||||
|
"public_key": "ssh-ed25519 AAAAC3NzEIFaJ7xH+Yoxd\n",
|
||||||
|
"private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BdHIAAAAGYXjl0j66VAKruPEKjS3A=\n"
|
||||||
|
"-----END OPENSSH PRIVATE KEY-----\n",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CREDENTIALS = [
|
||||||
|
Credentials(
|
||||||
|
[Username("user1"), Username("user3")],
|
||||||
|
[
|
||||||
|
Password("abcdefg"),
|
||||||
|
Password("root"),
|
||||||
|
SSHKeypair(public_key="some_public_key_1", private_key="some_private_key_1"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
SSH_KEYS_CREDENTIALS = [
|
||||||
|
Credentials(
|
||||||
|
[Username("root")],
|
||||||
|
[
|
||||||
|
SSHKeypair(public_key="some_public_key", private_key="some_private_key"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def aggregating_credentials_store() -> AggregatingCredentialsStore:
|
||||||
|
control_channel = MagicMock()
|
||||||
|
control_channel.get_credentials_for_propagation.return_value = CONTROL_CHANNEL_CREDENTIALS
|
||||||
|
return AggregatingCredentialsStore(control_channel)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_credentials_from_store(aggregating_credentials_store):
|
||||||
|
actual_stored_credentials = aggregating_credentials_store.get_credentials()
|
||||||
|
|
||||||
|
print(actual_stored_credentials)
|
||||||
|
|
||||||
|
assert actual_stored_credentials["exploit_user_list"] == set(
|
||||||
|
CONTROL_CHANNEL_CREDENTIALS["exploit_user_list"]
|
||||||
|
)
|
||||||
|
assert actual_stored_credentials["exploit_password_list"] == set(
|
||||||
|
CONTROL_CHANNEL_CREDENTIALS["exploit_password_list"]
|
||||||
|
)
|
||||||
|
assert actual_stored_credentials["exploit_ntlm_hash_list"] == set(
|
||||||
|
CONTROL_CHANNEL_CREDENTIALS["exploit_ntlm_hash_list"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]:
|
||||||
|
assert ssh_keypair in CONTROL_CHANNEL_CREDENTIALS["exploit_ssh_keys"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_credentials_to_store(aggregating_credentials_store):
|
||||||
|
aggregating_credentials_store.add_credentials(TEST_CREDENTIALS)
|
||||||
|
aggregating_credentials_store.add_credentials(SSH_KEYS_CREDENTIALS)
|
||||||
|
|
||||||
|
actual_stored_credentials = aggregating_credentials_store.get_credentials()
|
||||||
|
|
||||||
|
assert actual_stored_credentials["exploit_user_list"] == set(
|
||||||
|
[
|
||||||
|
"Administrator",
|
||||||
|
"root",
|
||||||
|
"user1",
|
||||||
|
"user3",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert actual_stored_credentials["exploit_password_list"] == set(
|
||||||
|
[
|
||||||
|
"123456",
|
||||||
|
"123456789",
|
||||||
|
"abcdefg",
|
||||||
|
"password",
|
||||||
|
"root",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(actual_stored_credentials["exploit_ssh_keys"]) == 3
|
|
@ -14,7 +14,7 @@ INTERVAL = 0.001
|
||||||
|
|
||||||
|
|
||||||
def test_terminate_without_start():
|
def test_terminate_without_start():
|
||||||
m = AutomatedMaster(None, None, None, None, MagicMock(), [])
|
m = AutomatedMaster(None, None, None, None, MagicMock(), [], MagicMock())
|
||||||
|
|
||||||
# Test that call to terminate does not raise exception
|
# Test that call to terminate does not raise exception
|
||||||
m.terminate()
|
m.terminate()
|
||||||
|
@ -34,7 +34,7 @@ def test_stop_if_cant_get_config_from_island(monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL
|
"infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL
|
||||||
)
|
)
|
||||||
m = AutomatedMaster(None, None, None, None, cc, [])
|
m = AutomatedMaster(None, None, None, None, cc, [], MagicMock())
|
||||||
m.start()
|
m.start()
|
||||||
|
|
||||||
assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT
|
assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT
|
||||||
|
@ -73,7 +73,7 @@ def test_stop_if_cant_get_stop_signal_from_island(monkeypatch, sleep_and_return_
|
||||||
"infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL
|
"infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL
|
||||||
)
|
)
|
||||||
|
|
||||||
m = AutomatedMaster(None, None, None, None, cc, [])
|
m = AutomatedMaster(None, None, None, None, cc, [], MagicMock())
|
||||||
m.start()
|
m.start()
|
||||||
|
|
||||||
assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT
|
assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from infection_monkey.telemetry.base_telem import BaseTelem
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="package")
|
||||||
|
def TestTelem():
|
||||||
|
class InnerTestTelem(BaseTelem):
|
||||||
|
telem_category = None
|
||||||
|
__test__ = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return InnerTestTelem
|
|
@ -0,0 +1,56 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from infection_monkey.credential_collectors import Password, SSHKeypair, Username
|
||||||
|
from infection_monkey.i_puppet import Credentials
|
||||||
|
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
|
||||||
|
from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import (
|
||||||
|
CredentialsInterceptingTelemetryMessenger,
|
||||||
|
)
|
||||||
|
|
||||||
|
TELEM_CREDENTIALS = [
|
||||||
|
Credentials(
|
||||||
|
[Username("user1"), Username("user3")],
|
||||||
|
[
|
||||||
|
Password("abcdefg"),
|
||||||
|
Password("root"),
|
||||||
|
SSHKeypair(public_key="some_public_key", private_key="some_private_key"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MockCredentialsTelem(CredentialsTelem):
|
||||||
|
def __init(self, credentials):
|
||||||
|
super().__init__(credentials)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_credentials_generic_telemetry(TestTelem):
|
||||||
|
mock_telemetry_messenger = MagicMock()
|
||||||
|
mock_credentials_store = MagicMock()
|
||||||
|
|
||||||
|
telemetry_messenger = CredentialsInterceptingTelemetryMessenger(
|
||||||
|
mock_telemetry_messenger, mock_credentials_store
|
||||||
|
)
|
||||||
|
|
||||||
|
telemetry_messenger.send_telemetry(TestTelem())
|
||||||
|
|
||||||
|
assert mock_telemetry_messenger.send_telemetry.called
|
||||||
|
assert not mock_credentials_store.add_credentials.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_successful_intercepting_credentials_telemetry():
|
||||||
|
mock_telemetry_messenger = MagicMock()
|
||||||
|
mock_credentials_store = MagicMock()
|
||||||
|
mock_empty_credentials_telem = MockCredentialsTelem(TELEM_CREDENTIALS)
|
||||||
|
|
||||||
|
telemetry_messenger = CredentialsInterceptingTelemetryMessenger(
|
||||||
|
mock_telemetry_messenger, mock_credentials_store
|
||||||
|
)
|
||||||
|
|
||||||
|
telemetry_messenger.send_telemetry(mock_empty_credentials_telem)
|
||||||
|
|
||||||
|
assert mock_telemetry_messenger.send_telemetry.called
|
||||||
|
assert mock_credentials_store.add_credentials.called
|
|
@ -2,23 +2,12 @@ from unittest.mock import MagicMock
|
||||||
|
|
||||||
from infection_monkey.i_puppet.i_puppet import ExploiterResultData
|
from infection_monkey.i_puppet.i_puppet import ExploiterResultData
|
||||||
from infection_monkey.model.host import VictimHost
|
from infection_monkey.model.host import VictimHost
|
||||||
from infection_monkey.telemetry.base_telem import BaseTelem
|
|
||||||
from infection_monkey.telemetry.exploit_telem import ExploitTelem
|
from infection_monkey.telemetry.exploit_telem import ExploitTelem
|
||||||
from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import (
|
from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import (
|
||||||
ExploitInterceptingTelemetryMessenger,
|
ExploitInterceptingTelemetryMessenger,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestTelem(BaseTelem):
|
|
||||||
telem_category = None
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class MockExpliotTelem(ExploitTelem):
|
class MockExpliotTelem(ExploitTelem):
|
||||||
def __init__(self, propagation_success):
|
def __init__(self, propagation_success):
|
||||||
erd = ExploiterResultData()
|
erd = ExploiterResultData()
|
||||||
|
@ -29,7 +18,7 @@ class MockExpliotTelem(ExploitTelem):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def test_generic_telemetry():
|
def test_generic_telemetry(TestTelem):
|
||||||
mock_telemetry_messenger = MagicMock()
|
mock_telemetry_messenger = MagicMock()
|
||||||
mock_tunnel = MagicMock()
|
mock_tunnel = MagicMock()
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,51 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from infection_monkey.credential_collectors import Password, SSHKeypair, Username
|
from infection_monkey.credential_collectors import Password, SSHKeypair, Username
|
||||||
from infection_monkey.i_puppet import Credentials
|
from infection_monkey.i_puppet import Credentials
|
||||||
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
|
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
|
||||||
|
|
||||||
|
USERNAME = "m0nkey"
|
||||||
|
PASSWORD = "mmm"
|
||||||
|
PUBLIC_KEY = "pub_key"
|
||||||
|
PRIVATE_KEY = "priv_key"
|
||||||
|
|
||||||
def test_credential_telem_send(spy_send_telemetry):
|
|
||||||
username = "m0nkey"
|
@pytest.fixture
|
||||||
password = "mmm"
|
def credentials_for_test():
|
||||||
public_key = "pub_key"
|
|
||||||
private_key = "priv_key"
|
return Credentials(
|
||||||
|
[Username(USERNAME)], [Password(PASSWORD), SSHKeypair(PRIVATE_KEY, PUBLIC_KEY)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_credential_telem_send(spy_send_telemetry, credentials_for_test):
|
||||||
|
|
||||||
expected_data = [
|
expected_data = [
|
||||||
{
|
{
|
||||||
"identities": [{"username": username, "credential_type": "USERNAME"}],
|
"identities": [{"username": USERNAME, "credential_type": "USERNAME"}],
|
||||||
"secrets": [
|
"secrets": [
|
||||||
{"password": password, "credential_type": "PASSWORD"},
|
{"password": PASSWORD, "credential_type": "PASSWORD"},
|
||||||
{
|
{
|
||||||
"private_key": "pub_key",
|
"private_key": PRIVATE_KEY,
|
||||||
"public_key": "priv_key",
|
"public_key": PUBLIC_KEY,
|
||||||
"credential_type": "SSH_KEYPAIR",
|
"credential_type": "SSH_KEYPAIR",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
credentials = Credentials(
|
telem = CredentialsTelem([credentials_for_test])
|
||||||
[Username(username)], [Password(password), SSHKeypair(public_key, private_key)]
|
|
||||||
)
|
|
||||||
|
|
||||||
telem = CredentialsTelem([credentials])
|
|
||||||
telem.send()
|
telem.send()
|
||||||
|
|
||||||
expected_data = json.dumps(expected_data, cls=telem.json_encoder)
|
expected_data = json.dumps(expected_data, cls=telem.json_encoder)
|
||||||
assert spy_send_telemetry.data == expected_data
|
assert spy_send_telemetry.data == expected_data
|
||||||
assert spy_send_telemetry.telem_category == "credentials"
|
assert spy_send_telemetry.telem_category == "credentials"
|
||||||
|
|
||||||
|
|
||||||
|
def test_credentials_property(credentials_for_test):
|
||||||
|
telem = CredentialsTelem([credentials_for_test])
|
||||||
|
|
||||||
|
assert len(list(telem.credentials)) == 1
|
||||||
|
assert list(telem.credentials)[0] == credentials_for_test
|
||||||
|
|
Loading…
Reference in New Issue