Merge branch '1606-run-credential-collectors' into agent-refactor

PR #1719
This commit is contained in:
Mike Salvatore 2022-02-17 09:30:01 -05:00
commit 095572f919
26 changed files with 219 additions and 201 deletions

View File

@ -10,6 +10,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- credentials.json file for storing Monkey Island user login information. #1206 - credentials.json file for storing Monkey Island user login information. #1206
- "GET /api/propagation-credentials/<string:guid>" endpoint for agents to - "GET /api/propagation-credentials/<string:guid>" endpoint for agents to
retrieve updated credentials from the Island. #1538 retrieve updated credentials from the Island. #1538
- SSHCollector as a configurable System info Collector. #1606
### Changed ### Changed
- "Communicate as Backdoor User" PBA's HTTP requests to request headers only and - "Communicate as Backdoor User" PBA's HTTP requests to request headers only and

View File

@ -1 +1,2 @@
MIMIKATZ_COLLECTOR = "MimikatzCollector" MIMIKATZ_COLLECTOR = "MimikatzCollector"
SSH_COLLECTOR = "SSHCollector"

View File

@ -1,11 +1,12 @@
class TelemCategoryEnum: class TelemCategoryEnum:
ATTACK = "attack"
AWS_INFO = "aws_info"
CREDENTIALS = "credentials"
EXPLOIT = "exploit" EXPLOIT = "exploit"
FILE_ENCRYPTION = "file_encryption"
POST_BREACH = "post_breach" POST_BREACH = "post_breach"
SCAN = "scan" SCAN = "scan"
STATE = "state" STATE = "state"
SYSTEM_INFO = "system_info" SYSTEM_INFO = "system_info"
TRACE = "trace" TRACE = "trace"
TUNNEL = "tunnel" TUNNEL = "tunnel"
ATTACK = "attack"
FILE_ENCRYPTION = "file_encryption"
AWS_INFO = "aws_info"

View File

@ -4,3 +4,4 @@ from .credential_components.password import Password
from .credential_components.username import Username from .credential_components.username import Username
from .credential_components.ssh_keypair import SSHKeypair from .credential_components.ssh_keypair import SSHKeypair
from .mimikatz_collector import MimikatzCredentialCollector from .mimikatz_collector import MimikatzCredentialCollector
from .ssh_collector import SSHCredentialCollector

View File

@ -1,4 +1,4 @@
from typing import Iterable from typing import Sequence
from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username
from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialCollector from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialCollector
@ -8,12 +8,12 @@ from .windows_credentials import WindowsCredentials
class MimikatzCredentialCollector(ICredentialCollector): class MimikatzCredentialCollector(ICredentialCollector):
def collect_credentials(self, options=None) -> Iterable[Credentials]: def collect_credentials(self, options=None) -> Sequence[Credentials]:
creds = pypykatz_handler.get_windows_creds() creds = pypykatz_handler.get_windows_creds()
return MimikatzCredentialCollector._to_credentials(creds) return MimikatzCredentialCollector._to_credentials(creds)
@staticmethod @staticmethod
def _to_credentials(win_creds: Iterable[WindowsCredentials]) -> [Credentials]: def _to_credentials(win_creds: Sequence[WindowsCredentials]) -> [Credentials]:
all_creds = [] all_creds = []
for win_cred in win_creds: for win_cred in win_creds:
identities = [] identities = []

View File

@ -1,10 +1,15 @@
import binascii import binascii
import logging
from typing import Any, Dict, List, NewType from typing import Any, Dict, List, NewType
from pypykatz.pypykatz import pypykatz from pypykatz.pypykatz import pypykatz
from infection_monkey.utils.environment import is_windows_os
from .windows_credentials import WindowsCredentials from .windows_credentials import WindowsCredentials
logger = logging.getLogger(__name__)
CREDENTIAL_TYPES = [ CREDENTIAL_TYPES = [
"msv_creds", "msv_creds",
"wdigest_creds", "wdigest_creds",
@ -19,6 +24,11 @@ PypykatzCredential = NewType("PypykatzCredential", Dict)
def get_windows_creds() -> List[WindowsCredentials]: def get_windows_creds() -> List[WindowsCredentials]:
# TODO: Remove this check when this is turned into a plugin.
if not is_windows_os():
logger.debug("Skipping pypykatz because the operating system is not Windows")
return []
pypy_handle = pypykatz.go_live() pypy_handle = pypykatz.go_live()
logon_data = pypy_handle.to_dict() logon_data = pypy_handle.to_dict()
windows_creds = _parse_pypykatz_results(logon_data) windows_creds = _parse_pypykatz_results(logon_data)

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Dict, Iterable, List from typing import Dict, Iterable, Sequence
from infection_monkey.credential_collectors import SSHKeypair, Username from infection_monkey.credential_collectors import SSHKeypair, Username
from infection_monkey.credential_collectors.ssh_collector import ssh_handler from infection_monkey.credential_collectors.ssh_collector import ssh_handler
@ -17,7 +17,7 @@ class SSHCredentialCollector(ICredentialCollector):
def __init__(self, telemetry_messenger: ITelemetryMessenger): def __init__(self, telemetry_messenger: ITelemetryMessenger):
self._telemetry_messenger = telemetry_messenger self._telemetry_messenger = telemetry_messenger
def collect_credentials(self, _options=None) -> List[Credentials]: def collect_credentials(self, _options=None) -> Sequence[Credentials]:
logger.info("Started scanning for SSH credentials") logger.info("Started scanning for SSH credentials")
ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger) ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger)
logger.info("Finished scanning for SSH credentials") logger.info("Finished scanning for SSH credentials")
@ -25,7 +25,7 @@ class SSHCredentialCollector(ICredentialCollector):
return SSHCredentialCollector._to_credentials(ssh_info) return SSHCredentialCollector._to_credentials(ssh_info)
@staticmethod @staticmethod
def _to_credentials(ssh_info: Iterable[Dict]) -> List[Credentials]: def _to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
ssh_credentials = [] ssh_credentials = []
for info in ssh_info: for info in ssh_info:

View File

@ -8,6 +8,7 @@ from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
from infection_monkey.telemetry.attack.t1145_telem import T1145Telem from infection_monkey.telemetry.attack.t1145_telem import T1145Telem
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.utils.environment import is_windows_os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,6 +16,13 @@ DEFAULT_DIRS = ["/.ssh/", "/"]
def get_ssh_info(telemetry_messenger: ITelemetryMessenger) -> Iterable[Dict]: def get_ssh_info(telemetry_messenger: ITelemetryMessenger) -> Iterable[Dict]:
# TODO: Remove this check when this is turned into a plugin.
if is_windows_os():
logger.debug(
"Skipping SSH credentials collection because the operating system is not Linux"
)
return []
home_dirs = _get_home_dirs() home_dirs = _get_home_dirs()
ssh_info = _get_ssh_files(home_dirs, telemetry_messenger) ssh_info = _get_ssh_files(home_dirs, telemetry_messenger)

View File

@ -1,4 +1,10 @@
from .plugin_type import PluginType from .plugin_type import PluginType
from .credential_collection import (
Credentials,
CredentialType,
ICredentialCollector,
ICredentialComponent,
)
from .i_puppet import ( from .i_puppet import (
IPuppet, IPuppet,
ExploiterResultData, ExploiterResultData,
@ -10,9 +16,3 @@ from .i_puppet import (
UnknownPluginError, UnknownPluginError,
) )
from .i_fingerprinter import IFingerprinter from .i_fingerprinter import IFingerprinter
from .credential_collection import (
Credentials,
CredentialType,
ICredentialCollector,
ICredentialComponent,
)

View File

@ -1,10 +1,10 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Iterable, Mapping, Optional from typing import Mapping, Optional, Sequence
from .credentials import Credentials from .credentials import Credentials
class ICredentialCollector(ABC): class ICredentialCollector(ABC):
@abstractmethod @abstractmethod
def collect_credentials(self, options: Optional[Mapping]) -> Iterable[Credentials]: def collect_credentials(self, options: Optional[Mapping]) -> Sequence[Credentials]:
pass pass

View File

@ -2,9 +2,9 @@ import abc
import threading import threading
from collections import namedtuple from collections import namedtuple
from enum import Enum from enum import Enum
from typing import Dict, List from typing import Dict, List, Sequence
from . import PluginType from . import Credentials, PluginType
class PortStatus(Enum): class PortStatus(Enum):
@ -36,12 +36,14 @@ class IPuppet(metaclass=abc.ABCMeta):
""" """
@abc.abstractmethod @abc.abstractmethod
def run_sys_info_collector(self, name: str) -> Dict: def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]:
""" """
Runs a system info collector Runs a credential collector
:param str name: The name of the system info collector to run :param str name: The name of the credential collector to run
:return: A dictionary containing the information collected from the system :param Dict options: A dictionary containing options that modify the behavior of the
:rtype: Dict Credential collector
:return: A sequence of Credentials that have been collected from the system
:rtype: Sequence[Credentials]
""" """
@abc.abstractmethod @abc.abstractmethod

View File

@ -2,8 +2,8 @@ from enum import Enum
class PluginType(Enum): class PluginType(Enum):
CREDENTIAL_COLLECTOR = "CredentialCollector"
EXPLOITER = "Exploiter" EXPLOITER = "Exploiter"
FINGERPRINTER = "Fingerprinter" FINGERPRINTER = "Fingerprinter"
PAYLOAD = "Payload" PAYLOAD = "Payload"
POST_BREACH_ACTION = "PBA" POST_BREACH_ACTION = "PBA"
SYSTEM_INFO_COLLECTOR = "SystemInfoCollector"

View File

@ -8,9 +8,9 @@ from infection_monkey.i_master import IMaster
from infection_monkey.i_puppet import IPuppet from infection_monkey.i_puppet import IPuppet
from infection_monkey.model import VictimHostFactory from infection_monkey.model import VictimHostFactory
from infection_monkey.network import NetworkInterface from infection_monkey.network import NetworkInterface
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
from infection_monkey.utils.threading import create_daemon_thread, interruptable_iter from infection_monkey.utils.threading import create_daemon_thread, interruptable_iter
from infection_monkey.utils.timer import Timer from infection_monkey.utils.timer import Timer
@ -134,12 +134,12 @@ class AutomatedMaster(IMaster):
logger.error(f"An error occurred while fetching configuration: {e}") logger.error(f"An error occurred while fetching configuration: {e}")
return return
system_info_collector_thread = create_daemon_thread( credential_collector_thread = create_daemon_thread(
target=self._run_plugins, target=self._run_plugins,
args=( args=(
config["system_info_collector_classes"], config["system_info_collector_classes"],
"system info collector", "credential collector",
self._collect_system_info, self._collect_credentials,
), ),
) )
pba_thread = create_daemon_thread( pba_thread = create_daemon_thread(
@ -147,14 +147,14 @@ class AutomatedMaster(IMaster):
args=(config["post_breach_actions"].items(), "post-breach action", self._run_pba), args=(config["post_breach_actions"].items(), "post-breach action", self._run_pba),
) )
system_info_collector_thread.start() credential_collector_thread.start()
pba_thread.start() pba_thread.start()
# Future stages of the simulation require the output of the system info collectors. Nothing # Future stages of the simulation require the output of the system info collectors. Nothing
# requires the output of PBAs, so we don't need to join on that thread here. We will join on # requires the output of PBAs, so we don't need to join on that thread here. We will join on
# the PBA thread later in this function to prevent the simulation from ending while PBAs are # the PBA thread later in this function to prevent the simulation from ending while PBAs are
# still running. # still running.
system_info_collector_thread.join() credential_collector_thread.join()
if self._can_propagate(): if self._can_propagate():
self._propagator.propagate(config["propagation"], self._stop) self._propagator.propagate(config["propagation"], self._stop)
@ -168,12 +168,13 @@ class AutomatedMaster(IMaster):
pba_thread.join() pba_thread.join()
def _collect_system_info(self, collector: str): def _collect_credentials(self, collector: str):
system_info_telemetry = {} credentials = self._puppet.run_credential_collector(collector, {})
system_info_telemetry[collector] = self._puppet.run_sys_info_collector(collector)
self._telemetry_messenger.send_telemetry( if credentials:
SystemInfoTelem({"collectors": system_info_telemetry}) self._telemetry_messenger.send_telemetry(CredentialsTelem(credentials))
) else:
logger.debug(f"No credentials were collected by {collector}")
def _run_pba(self, pba: Tuple[str, Dict]): def _run_pba(self, pba: Tuple[str, Dict]):
# TODO: This is the class's name right now. We need `display_name` (see the # TODO: This is the class's name right now. We need `display_name` (see the

View File

@ -3,11 +3,11 @@ import logging
from infection_monkey.i_master import IMaster from infection_monkey.i_master import IMaster
from infection_monkey.i_puppet import IPuppet, PortStatus from infection_monkey.i_puppet import IPuppet, PortStatus
from infection_monkey.model.host import VictimHost from infection_monkey.model.host import VictimHost
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.exploit_telem import ExploitTelem
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.scan_telem import ScanTelem
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
logger = logging.getLogger() logger = logging.getLogger()
@ -31,18 +31,18 @@ class MockMaster(IMaster):
self._exploit() self._exploit()
self._run_payload() self._run_payload()
def _run_sys_info_collectors(self): def _run_credential_collectors(self):
logger.info("Running system info collectors") logger.info("Running credential collectors")
system_info_telemetry = {}
system_info_telemetry["ProcessListCollector"] = self._puppet.run_sys_info_collector( windows_credentials = self._puppet.run_credential_collector("MimikatzCollector")
"ProcessListCollector" if windows_credentials:
) self._telemetry_messenger.send_telemetry(CredentialsTelem(windows_credentials))
self._telemetry_messenger.send_telemetry(
SystemInfoTelem({"collectors": system_info_telemetry}) ssh_credentials = self._puppet.run_sys_info_collector("SSHCollector")
) if ssh_credentials:
system_info = self._puppet.run_sys_info_collector("LinuxInfoCollector") self._telemetry_messenger.send_telemetry(CredentialsTelem(ssh_credentials))
self._telemetry_messenger.send_telemetry(SystemInfoTelem(system_info))
logger.info("Finished running system info collectors") logger.info("Finished running credential collectors")
def _run_pbas(self): def _run_pbas(self):

View File

@ -12,6 +12,10 @@ from common.utils.attack_utils import ScanStatus, UsageEnum
from common.version import get_version from common.version import get_version
from infection_monkey.config import GUID, WormConfiguration from infection_monkey.config import GUID, WormConfiguration
from infection_monkey.control import ControlClient from infection_monkey.control import ControlClient
from infection_monkey.credential_collectors import (
MimikatzCredentialCollector,
SSHCredentialCollector,
)
from infection_monkey.i_puppet import IPuppet, PluginType from infection_monkey.i_puppet import IPuppet, PluginType
from infection_monkey.master import AutomatedMaster from infection_monkey.master import AutomatedMaster
from infection_monkey.master.control_channel import ControlChannel from infection_monkey.master.control_channel import ControlChannel
@ -169,7 +173,7 @@ 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 = InfectionMonkey._build_puppet() puppet = self._build_puppet()
victim_host_factory = self._build_victim_host_factory(local_network_interfaces) victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
@ -189,10 +193,20 @@ class InfectionMonkey:
return local_network_interfaces return local_network_interfaces
@staticmethod def _build_puppet(self) -> IPuppet:
def _build_puppet() -> IPuppet:
puppet = Puppet() puppet = Puppet()
puppet.load_plugin(
"MimikatzCollector",
MimikatzCredentialCollector(),
PluginType.CREDENTIAL_COLLECTOR,
)
puppet.load_plugin(
"SSHCollector",
SSHCredentialCollector(self.telemetry_messenger),
PluginType.CREDENTIAL_COLLECTOR,
)
puppet.load_plugin("elastic", ElasticSearchFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("elastic", ElasticSearchFingerprinter(), PluginType.FINGERPRINTER)
puppet.load_plugin("http", HTTPFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("http", HTTPFingerprinter(), PluginType.FINGERPRINTER)
puppet.load_plugin("mssql", MSSQLFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("mssql", MSSQLFingerprinter(), PluginType.FINGERPRINTER)

View File

@ -1,8 +1,10 @@
import logging import logging
import threading import threading
from typing import Dict, List from typing import Dict, List, Sequence
from infection_monkey.credential_collectors import LMHash, Password, SSHKeypair, Username
from infection_monkey.i_puppet import ( from infection_monkey.i_puppet import (
Credentials,
ExploiterResultData, ExploiterResultData,
FingerprintData, FingerprintData,
IPuppet, IPuppet,
@ -12,7 +14,6 @@ from infection_monkey.i_puppet import (
PortStatus, PortStatus,
PostBreachData, PostBreachData,
) )
from infection_monkey.post_breach.actions.collect_processes_list import ProcessListCollection
DOT_1 = "10.0.0.1" DOT_1 = "10.0.0.1"
DOT_2 = "10.0.0.2" DOT_2 = "10.0.0.2"
@ -26,133 +27,26 @@ class MockPuppet(IPuppet):
def load_plugin(self, plugin: object, plugin_type: PluginType) -> None: def load_plugin(self, plugin: object, plugin_type: PluginType) -> None:
logger.debug(f"load_plugin({plugin}, {plugin_type})") logger.debug(f"load_plugin({plugin}, {plugin_type})")
def run_sys_info_collector(self, name: str) -> Dict: def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]:
logger.debug(f"run_sys_info_collector({name})") logger.debug(f"run_credential_collector({name})")
# TODO: More collectors
if name == "LinuxInfoCollector":
return {
"credentials": {},
"network_info": {
"networks": [
{"addr": "10.0.0.7", "netmask": "255.255.255.0"},
{"addr": "10.45.31.103", "netmask": "255.255.255.0"},
{"addr": "192.168.33.241", "netmask": "255.255.0.0"},
]
},
"ssh_info": [
{
"name": "m0nk3y",
"home_dir": "/home/m0nk3y",
"public_key": "ssh-rsa "
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCqhqTJfcrAbTUPzQ+Ou9bhQjmP29jRBz00BAdvNu77Y1SwM/+wETxapv7QPG55oc04Y5qR1KaItcwz3Prh7Qe/ohP/I2mIhP5tDRNfYHxXaGtj58wQhFrkrUhERVvEvwyvb97RWPAtAJjWT8+S6ASjjvyUNHulFIjJ0Yptlj2fboeh1eETDQ4FKfofpgwmab110ct2500FOtY1MWqFgpRvV0EX8WgJoscQ5FnsJAn6Ueb3DnsrIDq1LtK1rmxGSiZwpgOCwvyC1FFfHeP+cfpPsS+G9pBSYm2VqR42QL1BJL1pm4wFPVrBDmzORVQRf35k6agL7loRlfmAt28epDi1 ubuntu@test\n", # noqa: E501
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAqoakyX3KwG01D80PjrvW4UI5j9vY0Qc9NAQHbzbu+2NUsDP/\n"
"sBE8Wqb+0DxueaHNOGOakdSmiLXMM9z64e0Hv6IT/yNpiIT+bQ0TX2B8V2hrY+fM\n"
"Ew0OBSn6H6YMJmm9ddHLdudNBTrWNTFqhYKUb1dBF/FoCaLHEORZ7CQJ+lHm9w57\n"
"KyA6tS7Sta5sRkomcKYDgsL8gtRRXx3j/nH6T7EvhvaQUmJtlakeNkC9QSS9aZuM\n"
"snegLvVSlHVmKe8SjD0YAF7g9HH/vm0R2jYTYSArslw4mUZMjTcAQ/XBeDHDkNZq\n"
"x9ECzXdeZhXCXlKcadC+kNp+yT4MwkHAjid6AyalSDJ+9k3QRaI6ItxofWJhnZdB\n"
"RxQtnkJNOZCMKqwxmxUweX7AyShT1KdBdkw0VzkY0O3VUgdR9IzQu73eME5Qr4LM\n"
"5x+rFy0EggHkzCXecviDDQ/SJZEDR4yE0SCxwY0GxVfDdvM6aoLK7wLfu0hG+hjO\n"
"ewXmOAECgYEA4yA14atxKYWf8tAJnmH+IJi1nuiyBoaKJh9nGulGTFVpugytkfdy\n"
"omGYsvlSJd6x4KPM2nXuSD9uvS0ZDeHDXbPJcFAPscghwwIekunQigECgYEAwDRl\n"
"QOhBx8PpicbRmoEe06zb+gRNTYTnvcHgkJN275pqTn1hIAdQSGnyuyWdCN6CU8cg\n"
"p7ecLbCujAstim4H8LG6xMv8jBgVeBKclKEEy9IpvMZ/DGOdUS4/RMWkdVbcFFHZ\n"
"57gycmFwgN7ZFXdMkuCCZi2KCa4jX54G1VNX0+k64cLV8lgQXvVyl9QdvBkt8NqB\n"
"Zoce2vfDrFkUHoxQmAl2jvn8925KkAdga4Zj+zvLgmcryxCFZnA6IvxaoHzrUSxO\n"
"HpuEdCFek/4gyhXPbYQO99ZtOjx0mXwZVqRaEA1kvhX3+PjoPRO2wgBLXVNyb+P5\n"
"5Bxfk6XI40UAUSYv6XQlfIQj0xz/YfSkWbOwTJOShgMbJtiZVFuZ2YcEjSYXzNtv\n"
"WBM0+05OGqjxdyI+qpjHqrZVWN9WvvkH0gJz+zvcorygINMnuSjpNCw4nipXHaud\n"
"LbiqWK42eTmVSiFH+pH+YwVaTatc0RfQ7OP218GD8dtkTgw2JFOzbA==\n"
"-----END RSA PRIVATE KEY-----\n",
"known_hosts": "|1|pERVcy3opIGJnp7HVTpeA0FmuEY=|L64j7430lwkSFrmcn49Nf8YEsLc= " # noqa: E501
"ssh-rsa "
"AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n" # noqa: E501
"|1|DXEyHSAtnxSSWb4z6XLaxHJL/aM=|zjIBopXOz1GB9hbdpVcYsHY+eSU= "
"ssh-rsa "
"AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n" # noqa: E501
"10.197.94.221 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n" # noqa: E501
"|1|kVjsp1IWhGMsWfrbQuhLUABrNMk=|xKCh+yr8mPEyCLZ2/E5bC8bjvw0= "
"ecdsa-sha2-nistp256 "
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n" # noqa: E501
"other_host,fd42:5289:fddc:ffdf:216:3eff:fe5b:9114 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n" # noqa: E501
"|1|S6K6SneX+l7xTM1gNLvDAAzj4gs=|cSOIX6qf5YuIe2aw/KmUrM2ye/c= "
"ecdsa-sha2-nistp256 "
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n", # noqa: E501
}
],
}
if name == "ProcessListCollector":
return {
"process_list": {
1: {
"cmdline": "/sbin/init",
"full_image_path": "/sbin/init",
"name": "systemd",
"pid": 1,
"ppid": 0,
},
65: {
"cmdline": "/lib/systemd/systemd-journald",
"full_image_path": "/lib/systemd/systemd-journald",
"name": "systemd-journald",
"pid": 65,
"ppid": 1,
},
84: {
"cmdline": "/lib/systemd/systemd-udevd",
"full_image_path": "/lib/systemd/systemd-udevd",
"name": "systemd-udevd",
"pid": 84,
"ppid": 1,
},
192: {
"cmdline": "/lib/systemd/systemd-networkd",
"full_image_path": "/lib/systemd/systemd-networkd",
"name": "systemd-networkd",
"pid": 192,
"ppid": 1,
},
17749: {
"cmdline": "-zsh",
"full_image_path": "/bin/zsh",
"name": "zsh",
"pid": 17749,
"ppid": 17748,
},
18392: {
"cmdline": "/home/ubuntu/venvs/monkey/bin/python " "monkey_island.py",
"full_image_path": "/usr/bin/python3.7",
"name": "python",
"pid": 18392,
"ppid": 17502,
},
18400: {
"cmdline": "/home/ubuntu/git/monkey/monkey/monkey_island/bin/mongodb/bin/mongod " # noqa: E501
"--dbpath /home/ubuntu/.monkey_island/db",
"full_image_path": "/home/ubuntu/git/monkey/monkey/monkey_island/bin/mongodb/bin/mongod", # noqa: E501
"name": "mongod",
"pid": 18400,
"ppid": 18392,
},
26535: {
"cmdline": "ACCESS DENIED",
"full_image_path": "null",
"name": "null",
"pid": 26535,
"ppid": 26469,
},
29291: {
"cmdline": "python infection_monkey.py m0nk3y -s " "localhost:5000",
"full_image_path": "/usr/bin/python3.7",
"name": "python",
"pid": 29291,
"ppid": 17749,
},
}
}
return {} if name == "SSHCollector":
# TODO: Replace Passwords with SSHKeypair after it is implemented
ssh_credentials = Credentials(
[Username("m0nk3y")],
[
SSHKeypair("Public_Key_0", "Private_Key_0"),
SSHKeypair("Public_Key_1", "Private_Key_1"),
],
)
return [ssh_credentials]
elif name == "MimikatzCollector":
windows_credentials = Credentials(
[Username("test_user")], [Password("1234"), LMHash("DEADBEEF")]
)
return [windows_credentials]
return []
def run_pba(self, name: str, options: Dict) -> PostBreachData: def run_pba(self, name: str, options: Dict) -> PostBreachData:
logger.debug(f"run_pba({name}, {options})") logger.debug(f"run_pba({name}, {options})")

View File

@ -1,5 +1,4 @@
import logging import logging
from typing import Optional
from infection_monkey.i_puppet import PluginType, UnknownPluginError from infection_monkey.i_puppet import PluginType, UnknownPluginError
@ -28,7 +27,7 @@ class PluginRegistry:
logger.debug(f"Plugin '{plugin_name}' loaded") logger.debug(f"Plugin '{plugin_name}' loaded")
def get_plugin(self, plugin_name: str, plugin_type: PluginType) -> Optional[object]: def get_plugin(self, plugin_name: str, plugin_type: PluginType) -> object:
try: try:
plugin = self._registry[plugin_type][plugin_name] plugin = self._registry[plugin_type][plugin_name]
except KeyError: except KeyError:

View File

@ -1,9 +1,10 @@
import logging import logging
import threading import threading
from typing import Dict, List from typing import Dict, List, Sequence
from infection_monkey import network from infection_monkey import network
from infection_monkey.i_puppet import ( from infection_monkey.i_puppet import (
Credentials,
ExploiterResultData, ExploiterResultData,
FingerprintData, FingerprintData,
IPuppet, IPuppet,
@ -27,8 +28,11 @@ class Puppet(IPuppet):
def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None:
self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type)
def run_sys_info_collector(self, name: str) -> Dict: def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]:
return self._mock_puppet.run_sys_info_collector(name) credential_collector = self._plugin_registry.get_plugin(
name, PluginType.CREDENTIAL_COLLECTOR
)
return credential_collector.collect_credentials(options)
def run_pba(self, name: str, options: Dict) -> PostBreachData: def run_pba(self, name: str, options: Dict) -> PostBreachData:
return self._mock_puppet.run_pba(name, options) return self._mock_puppet.run_pba(name, options)

View File

@ -0,0 +1,40 @@
import enum
import json
from typing import Dict, Iterable
from common.common_consts.telem_categories import TelemCategoryEnum
from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialComponent
from infection_monkey.telemetry.base_telem import BaseTelem
class CredentialsTelem(BaseTelem):
telem_category = TelemCategoryEnum.CREDENTIALS
def __init__(self, credentials: Iterable[Credentials]):
"""
Used to send information about stolen or discovered credentials to the Island.
:param credentials: An iterable containing credentials to be sent to the Island.
"""
self._credentials = credentials
def get_data(self) -> Dict:
# TODO: At a later time we can consider factoring this into a Serializer class or similar.
return json.loads(json.dumps(self._credentials, default=_serialize))
def _serialize(obj):
if isinstance(obj, enum.Enum):
return obj.name
if isinstance(obj, ICredentialComponent):
# This is a workaround for ICredentialComponents that are implemented as dataclasses. If the
# credential_type attribute is populated with `field(init=False, ...)`, then credential_type
# is not added to the object's __dict__ attribute. The biggest risk of this workaround is
# that we might change the name of the credential_type field in ICredentialComponents, but
# automated refactoring tools would not detect that this string needs to change. This is
# mittigated by the call to getattr() below, which will raise an AttributeException if the
# attribute name changes and a unit test will fail under these conditions.
credential_type = getattr(obj, "credential_type")
return dict(obj.__dict__, **{"credential_type": credential_type})
return getattr(obj, "__dict__", str(obj))

View File

@ -1,6 +1,4 @@
from common.common_consts.system_info_collectors_names import ( from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR, SSH_COLLECTOR
MIMIKATZ_COLLECTOR,
)
SYSTEM_INFO_COLLECTOR_CLASSES = { SYSTEM_INFO_COLLECTOR_CLASSES = {
"title": "System Information Collectors", "title": "System Information Collectors",
@ -10,10 +8,18 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
{ {
"type": "string", "type": "string",
"enum": [MIMIKATZ_COLLECTOR], "enum": [MIMIKATZ_COLLECTOR],
"title": "Mimikatz Collector", "title": "Mimikatz Credentials Collector",
"safe": True, "safe": True,
"info": "Collects credentials from Windows credential manager.", "info": "Collects credentials from Windows credential manager.",
"attack_techniques": ["T1003", "T1005"], "attack_techniques": ["T1003", "T1005"],
}, },
{
"type": "string",
"enum": [SSH_COLLECTOR],
"title": "SSH Credentials Collector",
"safe": True,
"info": "Searches users' home directories and collects SSH keypairs.",
"attack_techniques": ["T1005", "T1145"],
},
], ],
} }

View File

@ -1,6 +1,4 @@
from common.common_consts.system_info_collectors_names import ( from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR, SSH_COLLECTOR
MIMIKATZ_COLLECTOR,
)
MONKEY = { MONKEY = {
"title": "Monkey", "title": "Monkey",
@ -86,6 +84,7 @@ MONKEY = {
"items": {"$ref": "#/definitions/system_info_collector_classes"}, "items": {"$ref": "#/definitions/system_info_collector_classes"},
"default": [ "default": [
MIMIKATZ_COLLECTOR, MIMIKATZ_COLLECTOR,
SSH_COLLECTOR,
], ],
}, },
}, },

View File

@ -104,7 +104,8 @@
} }
}, },
"system_info_collector_classes": [ "system_info_collector_classes": [
"MimikatzCollector" "MimikatzCollector",
"SSHCollector"
] ]
} }
} }

View File

@ -146,8 +146,8 @@
}, },
"system_info": { "system_info": {
"system_info_collector_classes": [ "system_info_collector_classes": [
"hostnamecollector", "MimikatzCollector",
"mimikatzcollector" "SSHCollector"
] ]
} }
} }

View File

@ -1,4 +1,4 @@
from typing import List from typing import Sequence
import pytest import pytest
@ -23,8 +23,8 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch):
) )
def collect_credentials() -> List[Credentials]: def collect_credentials() -> Sequence[Credentials]:
return list(MimikatzCredentialCollector().collect_credentials()) return MimikatzCredentialCollector().collect_credentials()
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -2,8 +2,7 @@ from unittest.mock import MagicMock
import pytest import pytest
from infection_monkey.credential_collectors import SSHKeypair, Username from infection_monkey.credential_collectors import SSHCredentialCollector, SSHKeypair, Username
from infection_monkey.credential_collectors.ssh_collector import SSHCredentialCollector
from infection_monkey.i_puppet.credential_collection import Credentials from infection_monkey.i_puppet.credential_collection import Credentials

View File

@ -0,0 +1,37 @@
import json
from infection_monkey.credential_collectors import Password, SSHKeypair, Username
from infection_monkey.i_puppet import Credentials
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
def test_credential_telem_send(spy_send_telemetry):
username = "m0nkey"
password = "mmm"
public_key = "pub_key"
private_key = "priv_key"
expected_data = [
{
"identities": [{"username": username, "credential_type": "USERNAME"}],
"secrets": [
{"password": password, "credential_type": "PASSWORD"},
{
"private_key": "pub_key",
"public_key": "priv_key",
"credential_type": "SSH_KEYPAIR",
},
],
}
]
credentials = Credentials(
[Username(username)], [Password(password), SSHKeypair(public_key, private_key)]
)
telem = CredentialsTelem([credentials])
telem.send()
expected_data = json.dumps(expected_data, cls=telem.json_encoder)
assert spy_send_telemetry.data == expected_data
assert spy_send_telemetry.telem_category == "credentials"