diff --git a/monkey/common/common_consts/credential_component_type.py b/monkey/common/common_consts/credential_component_type.py new file mode 100644 index 000000000..76326e50e --- /dev/null +++ b/monkey/common/common_consts/credential_component_type.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CredentialComponentType(Enum): + USERNAME = "username" + PASSWORD = "password" + NT_HASH = "nt_hash" + LM_HASH = "lm_hash" + SSH_KEYPAIR = "ssh_keypair" diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py index 7706540a3..1fef78437 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -1,9 +1,12 @@ from dataclasses import dataclass, field -from infection_monkey.i_puppet import CredentialType, ICredentialComponent +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent @dataclass(frozen=True) class LMHash(ICredentialComponent): - credential_type: CredentialType = field(default=CredentialType.LM_HASH, init=False) + credential_type: CredentialComponentType = field( + default=CredentialComponentType.LM_HASH.value, init=False + ) lm_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py index e6932c4c5..07b9f859a 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -1,9 +1,12 @@ from dataclasses import dataclass, field -from infection_monkey.i_puppet import CredentialType, ICredentialComponent +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent @dataclass(frozen=True) class NTHash(ICredentialComponent): - credential_type: CredentialType = field(default=CredentialType.NT_HASH, init=False) + credential_type: CredentialComponentType = field( + default=CredentialComponentType.NT_HASH.value, init=False + ) nt_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index 701c9fcde..3a05e3599 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -1,9 +1,12 @@ from dataclasses import dataclass, field -from infection_monkey.i_puppet import CredentialType, ICredentialComponent +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent @dataclass(frozen=True) class Password(ICredentialComponent): - credential_type: CredentialType = field(default=CredentialType.PASSWORD, init=False) + credential_type: CredentialComponentType = field( + default=CredentialComponentType.PASSWORD.value, init=False + ) password: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py index c5f377c44..6abd314bb 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py @@ -1,10 +1,13 @@ from dataclasses import dataclass, field -from infection_monkey.i_puppet import CredentialType, ICredentialComponent +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent @dataclass(frozen=True) class SSHKeypair(ICredentialComponent): - credential_type: CredentialType = field(default=CredentialType.SSH_KEYPAIR, init=False) + credential_type: CredentialComponentType = field( + default=CredentialComponentType.SSH_KEYPAIR.value, init=False + ) private_key: str public_key: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 208849061..791dd6abe 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -1,9 +1,12 @@ from dataclasses import dataclass, field -from infection_monkey.i_puppet import CredentialType, ICredentialComponent +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent @dataclass(frozen=True) class Username(ICredentialComponent): - credential_type: CredentialType = field(default=CredentialType.USERNAME, init=False) + credential_type: CredentialComponentType = field( + default=CredentialComponentType.USERNAME.value, init=False + ) username: str 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 1cbef911e..0ef75ed1b 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,3 +1,4 @@ +import logging from typing import Sequence from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username @@ -6,10 +7,15 @@ from infection_monkey.i_puppet.credential_collection import Credentials, ICreden from . import pypykatz_handler from .windows_credentials import WindowsCredentials +logger = logging.getLogger(__name__) + class MimikatzCredentialCollector(ICredentialCollector): + def collect_credentials(self, options=None) -> Sequence[Credentials]: + logger.info("Attempting to collect windows credentials with pypykatz.") creds = pypykatz_handler.get_windows_creds() + logger.info(f"Pypykatz gathered {len(creds)} credentials.") return MimikatzCredentialCollector._to_credentials(creds) @staticmethod 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 98ca0df4a..5dba2bbf3 100644 --- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py @@ -30,7 +30,6 @@ def get_ssh_info(telemetry_messenger: ITelemetryMessenger) -> Iterable[Dict]: def _get_home_dirs() -> Iterable[Dict]: import pwd - root_dir = _get_ssh_struct("root", "") home_dirs = [ _get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() if x.pw_dir.startswith("/home") diff --git a/monkey/infection_monkey/i_puppet/__init__.py b/monkey/infection_monkey/i_puppet/__init__.py index 1c16f6df2..767826297 100644 --- a/monkey/infection_monkey/i_puppet/__init__.py +++ b/monkey/infection_monkey/i_puppet/__init__.py @@ -1,10 +1,4 @@ from .plugin_type import PluginType -from .credential_collection import ( - Credentials, - CredentialType, - ICredentialCollector, - ICredentialComponent, -) from .i_puppet import ( IPuppet, ExploiterResultData, @@ -16,3 +10,8 @@ from .i_puppet import ( UnknownPluginError, ) from .i_fingerprinter import IFingerprinter +from .credential_collection import ( + Credentials, + ICredentialCollector, + ICredentialComponent, +) diff --git a/monkey/infection_monkey/i_puppet/credential_collection/__init__.py b/monkey/infection_monkey/i_puppet/credential_collection/__init__.py index 8bfa68b38..a97d8373f 100644 --- a/monkey/infection_monkey/i_puppet/credential_collection/__init__.py +++ b/monkey/infection_monkey/i_puppet/credential_collection/__init__.py @@ -1,4 +1,3 @@ from .i_credential_collector import ICredentialCollector from .credentials import Credentials from .i_credential_component import ICredentialComponent -from .credential_type import CredentialType diff --git a/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py b/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py deleted file mode 100644 index ef00f3732..000000000 --- a/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py +++ /dev/null @@ -1,9 +0,0 @@ -from enum import Enum - - -class CredentialType(Enum): - USERNAME = 1 - PASSWORD = 2 - NT_HASH = 3 - LM_HASH = 4 - SSH_KEYPAIR = 5 diff --git a/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py index d1c005886..c4471ebfb 100644 --- a/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py +++ b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from .credential_type import CredentialType +from common.common_consts.credential_component_type import CredentialComponentType class ICredentialComponent(ABC): @property @abstractmethod - def credential_type(self) -> CredentialType: + def credential_type(self) -> CredentialComponentType: pass diff --git a/monkey/infection_monkey/i_puppet/i_puppet.py b/monkey/infection_monkey/i_puppet/i_puppet.py index 79bd3b4fe..82f6b8b94 100644 --- a/monkey/infection_monkey/i_puppet/i_puppet.py +++ b/monkey/infection_monkey/i_puppet/i_puppet.py @@ -4,7 +4,8 @@ from collections import namedtuple from enum import Enum from typing import Dict, List, Sequence -from . import Credentials, PluginType +from . import PluginType +from .credential_collection import Credentials class PortStatus(Enum): diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py index 5da7040d5..c0573d942 100644 --- a/monkey/infection_monkey/telemetry/credentials_telem.py +++ b/monkey/infection_monkey/telemetry/credentials_telem.py @@ -17,6 +17,9 @@ class CredentialsTelem(BaseTelem): """ self._credentials = credentials + def send(self, log_data=True): + super().send(log_data=False) + 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)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/__init__.py rename to monkey/monkey_island/cc/services/telemetry/processing/credentials/__init__.py diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py new file mode 100644 index 000000000..9c4661e1d --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py @@ -0,0 +1,29 @@ +import logging +from typing import Mapping + +from common.common_consts.credential_component_type import CredentialComponentType + +from .identities.username_processor import process_username +from .secrets.lm_hash_processor import process_lm_hash +from .secrets.nt_hash_processor import process_nt_hash +from .secrets.password_processor import process_password + +logger = logging.getLogger(__name__) + +SECRET_PROCESSORS = { + CredentialComponentType.PASSWORD.value: process_password, + CredentialComponentType.NT_HASH.value: process_nt_hash, + CredentialComponentType.LM_HASH.value: process_lm_hash, +} + +IDENTITY_PROCESSORS = { + CredentialComponentType.USERNAME.value: process_username, +} + + +def parse_credentials(credentials: Mapping): + for credential in credentials["data"]: + for identity in credential["identities"]: + IDENTITY_PROCESSORS[identity["credential_type"]](identity) + for secret in credential["secrets"]: + SECRET_PROCESSORS[secret["credential_type"]](secret) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/username_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/username_processor.py new file mode 100644 index 000000000..79b09901b --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/username_processor.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.config import ConfigService + + +def process_username(username: dict): + ConfigService.creds_add_username(username["username"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/lm_hash_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/lm_hash_processor.py new file mode 100644 index 000000000..7c5d5f3fa --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/lm_hash_processor.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.config import ConfigService + + +def process_lm_hash(lm_hash: dict): + ConfigService.creds_add_lm_hash(lm_hash["lm_hash"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/nt_hash_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/nt_hash_processor.py new file mode 100644 index 000000000..e29e2eef0 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/nt_hash_processor.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.config import ConfigService + + +def process_nt_hash(nt_hash: dict): + ConfigService.creds_add_ntlm_hash(nt_hash["nt_hash"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/password_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/password_processor.py new file mode 100644 index 000000000..6d3331db6 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/password_processor.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.config import ConfigService + + +def process_password(password: dict): + ConfigService.creds_add_password(password["password"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 44cd5c0cc..0dd93aab1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -2,21 +2,22 @@ import logging from common.common_consts.telem_categories import TelemCategoryEnum from monkey_island.cc.services.telemetry.processing.aws_info import process_aws_telemetry +from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import\ + parse_credentials from monkey_island.cc.services.telemetry.processing.exploit import process_exploit_telemetry from monkey_island.cc.services.telemetry.processing.post_breach import process_post_breach_telemetry from monkey_island.cc.services.telemetry.processing.scan import process_scan_telemetry from monkey_island.cc.services.telemetry.processing.state import process_state_telemetry -from monkey_island.cc.services.telemetry.processing.system_info import process_system_info_telemetry from monkey_island.cc.services.telemetry.processing.tunnel import process_tunnel_telemetry logger = logging.getLogger(__name__) TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = { + TelemCategoryEnum.CREDENTIALS: parse_credentials, TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, TelemCategoryEnum.STATE: process_state_telemetry, TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, TelemCategoryEnum.SCAN: process_scan_telemetry, - TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, TelemCategoryEnum.AWS_INFO: process_aws_telemetry, # `lambda *args, **kwargs: None` is a no-op. diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py deleted file mode 100644 index 7d7f404ce..000000000 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ /dev/null @@ -1,107 +0,0 @@ -import logging - -from monkey_island.cc.server_utils.encryption import get_datastore_encryptor -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 - SystemInfoTelemetryDispatcher, -) -from monkey_island.cc.services.wmi_handler import WMIHandler - -logger = logging.getLogger(__name__) - - -def process_system_info_telemetry(telemetry_json): - dispatcher = SystemInfoTelemetryDispatcher() - telemetry_processing_stages = [ - process_ssh_info, - process_credential_info, - process_wmi_info, - dispatcher.dispatch_collector_results_to_relevant_processors, - ] - - # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of - # failing the rest of - # them, as they are independent. - for stage in telemetry_processing_stages: - safe_process_telemetry(stage, telemetry_json) - - -def safe_process_telemetry(processing_function, telemetry_json): - # noinspection PyBroadException - try: - processing_function(telemetry_json) - except Exception as err: - logger.error( - "Error {} while in {} stage of processing telemetry.".format( - str(err), processing_function.__name__ - ), - exc_info=True, - ) - - -def process_ssh_info(telemetry_json): - if "ssh_info" in telemetry_json["data"]: - ssh_info = telemetry_json["data"]["ssh_info"] - encrypt_system_info_ssh_keys(ssh_info) - if telemetry_json["data"]["network_info"]["networks"]: - # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip - # from telemetry - add_ip_to_ssh_keys(telemetry_json["data"]["network_info"]["networks"][0], ssh_info) - add_system_info_ssh_keys_to_config(ssh_info) - - -def add_system_info_ssh_keys_to_config(ssh_info): - for user in ssh_info: - ConfigService.creds_add_username(user["name"]) - # Public key is useless without private key - if user["public_key"] and user["private_key"]: - ConfigService.ssh_add_keys( - user["public_key"], user["private_key"], user["name"], user["ip"] - ) - - -def add_ip_to_ssh_keys(ip, ssh_info): - for key in ssh_info: - key["ip"] = ip["addr"] - - -def encrypt_system_info_ssh_keys(ssh_info): - for idx, user in enumerate(ssh_info): - for field in ["public_key", "private_key", "known_hosts"]: - if ssh_info[idx][field]: - ssh_info[idx][field] = get_datastore_encryptor().encrypt(ssh_info[idx][field]) - - -def process_credential_info(telemetry_json): - if "credentials" in telemetry_json["data"]: - creds = telemetry_json["data"]["credentials"] - add_system_info_creds_to_config(creds) - replace_user_dot_with_comma(creds) - - -def replace_user_dot_with_comma(creds): - for user in creds: - if -1 != user.find("."): - new_user = user.replace(".", ",") - creds[new_user] = creds.pop(user) - - -def add_system_info_creds_to_config(creds): - for user in creds: - ConfigService.creds_add_username(creds[user]["username"]) - if "password" in creds[user] and creds[user]["password"]: - ConfigService.creds_add_password(creds[user]["password"]) - if "lm_hash" in creds[user] and creds[user]["lm_hash"]: - ConfigService.creds_add_lm_hash(creds[user]["lm_hash"]) - if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]: - ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"]) - - -def process_wmi_info(telemetry_json): - users_secrets = {} - - if "wmi" in telemetry_json["data"]: - monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]).get("_id") - wmi_handler = WMIHandler(monkey_id, telemetry_json["data"]["wmi"], users_secrets) - wmi_handler.process_and_handle_wmi_info() diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py deleted file mode 100644 index d2f3441f9..000000000 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ /dev/null @@ -1,181 +0,0 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.groups_and_users_consts import GROUPTYPE, USERTYPE - - -class WMIHandler(object): - ADMINISTRATORS_GROUP_KNOWN_SID = "1-5-32-544" - - def __init__(self, monkey_id, wmi_info, user_secrets): - - self.monkey_id = monkey_id - self.info_for_mongo = {} - self.users_secrets = user_secrets - if not wmi_info: - self.users_info = "" - self.groups_info = "" - self.groups_and_users = "" - self.services = "" - self.products = "" - else: - self.users_info = wmi_info["Win32_UserAccount"] - self.groups_info = wmi_info["Win32_Group"] - self.groups_and_users = wmi_info["Win32_GroupUser"] - self.services = wmi_info["Win32_Service"] - self.products = wmi_info["Win32_Product"] - - def process_and_handle_wmi_info(self): - - self.add_groups_to_collection() - self.add_users_to_collection() - self.create_group_user_connection() - self.insert_info_to_mongo() - if self.info_for_mongo: - self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) - self.update_admins_retrospective() - self.update_critical_services() - - def update_critical_services(self): - critical_names = ("W3svc", "MSExchangeServiceHost", "dns", "MSSQL$SQLEXPRES") - mongo.db.monkey.update({"_id": self.monkey_id}, {"$set": {"critical_services": []}}) - - services_names_list = [str(i["Name"])[2:-1] for i in self.services] - products_names_list = [str(i["Name"])[2:-2] for i in self.products] - - for name in critical_names: - if name in services_names_list or name in products_names_list: - mongo.db.monkey.update( - {"_id": self.monkey_id}, {"$addToSet": {"critical_services": name}} - ) - - def build_entity_document(self, entity_info, monkey_id=None): - general_properties_dict = { - "SID": str(entity_info["SID"])[4:-1], - "name": str(entity_info["Name"])[2:-1], - "machine_id": monkey_id, - "member_of": [], - "admin_on_machines": [], - } - - if monkey_id: - general_properties_dict["domain_name"] = None - else: - general_properties_dict["domain_name"] = str(entity_info["Domain"])[2:-1] - - return general_properties_dict - - def add_users_to_collection(self): - for user in self.users_info: - if not user.get("LocalAccount"): - base_entity = self.build_entity_document(user) - else: - base_entity = self.build_entity_document(user, self.monkey_id) - base_entity["NTLM_secret"] = self.users_secrets.get(base_entity["name"], {}).get( - "ntlm_hash" - ) - base_entity["SAM_secret"] = self.users_secrets.get(base_entity["name"], {}).get("sam") - base_entity["secret_location"] = [] - - base_entity["type"] = USERTYPE - self.info_for_mongo[base_entity.get("SID")] = base_entity - - def add_groups_to_collection(self): - for group in self.groups_info: - if not group.get("LocalAccount"): - base_entity = self.build_entity_document(group) - else: - base_entity = self.build_entity_document(group, self.monkey_id) - base_entity["entities_list"] = [] - base_entity["type"] = GROUPTYPE - self.info_for_mongo[base_entity.get("SID")] = base_entity - - def create_group_user_connection(self): - for group_user_couple in self.groups_and_users: - group_part = group_user_couple["GroupComponent"] - child_part = group_user_couple["PartComponent"] - group_sid = str(group_part["SID"])[4:-1] - groups_entities_list = self.info_for_mongo[group_sid]["entities_list"] - child_sid = "" - - if isinstance(child_part, str): - child_part = str(child_part) - name = None - domain_name = None - if "cimv2:Win32_UserAccount" in child_part: - # domain user - domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' - )[0] - name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' - )[1][:-2] - - if "cimv2:Win32_Group" in child_part: - # domain group - domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split( - '",Name="' - )[0] - name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][ - :-2 - ] - - for entity in self.info_for_mongo: - if ( - self.info_for_mongo[entity]["name"] == name - and self.info_for_mongo[entity]["domain"] == domain_name - ): - child_sid = self.info_for_mongo[entity]["SID"] - else: - child_sid = str(child_part["SID"])[4:-1] - - if child_sid and child_sid not in groups_entities_list: - groups_entities_list.append(child_sid) - - if child_sid: - if child_sid in self.info_for_mongo: - self.info_for_mongo[child_sid]["member_of"].append(group_sid) - - def insert_info_to_mongo(self): - for entity in list(self.info_for_mongo.values()): - if entity["machine_id"]: - # Handling for local entities. - mongo.db.groupsandusers.update( - {"SID": entity["SID"], "machine_id": entity["machine_id"]}, entity, upsert=True - ) - else: - # Handlings for domain entities. - if not mongo.db.groupsandusers.find_one({"SID": entity["SID"]}): - mongo.db.groupsandusers.insert_one(entity) - else: - # if entity is domain entity, add the monkey id of current machine to - # secrets_location. - # (found on this machine) - if entity.get("NTLM_secret"): - mongo.db.groupsandusers.update_one( - {"SID": entity["SID"], "type": USERTYPE}, - {"$addToSet": {"secret_location": self.monkey_id}}, - ) - - def update_admins_retrospective(self): - for profile in self.info_for_mongo: - groups_from_mongo = mongo.db.groupsandusers.find( - {"SID": {"$in": self.info_for_mongo[profile]["member_of"]}}, - {"admin_on_machines": 1}, - ) - - for group in groups_from_mongo: - if group["admin_on_machines"]: - mongo.db.groupsandusers.update_one( - {"SID": self.info_for_mongo[profile]["SID"]}, - {"$addToSet": {"admin_on_machines": {"$each": group["admin_on_machines"]}}}, - ) - - def add_admin(self, group, machine_id): - for sid in group["entities_list"]: - mongo.db.groupsandusers.update_one( - {"SID": sid}, {"$addToSet": {"admin_on_machines": machine_id}} - ) - entity_details = mongo.db.groupsandusers.find_one( - {"SID": sid}, {"type": USERTYPE, "entities_list": 1} - ) - if entity_details.get("type") == GROUPTYPE: - self.add_admin(entity_details, machine_id) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_credential_processing.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_credential_processing.py new file mode 100644 index 000000000..173b9662f --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_credential_processing.py @@ -0,0 +1,92 @@ +from copy import deepcopy +from datetime import datetime + +import dpath.util +import mongoengine +import pytest + +from common.config_value_paths import ( + LM_HASH_LIST_PATH, + NTLM_HASH_LIST_PATH, + PASSWORD_LIST_PATH, + USER_LIST_PATH, +) +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import ( + parse_credentials, +) + +CREDENTIAL_TELEM_TEMPLATE = { + "monkey_guid": "272405690278083", + "telem_category": "credentials", + "timestamp": datetime(2022, 2, 18, 11, 51, 15, 338953), + "command_control_channel": {"src": "10.2.2.251", "dst": "10.2.2.251:5000"}, + "data": None, +} + +fake_username = "m0nk3y_user" +cred_telem_usernames = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_telem_usernames["data"] = [ + {"identities": [{"username": fake_username, "credential_type": "username"}], "secrets": []} +] + +fake_nt_hash = "c1c58f96cdf212b50837bc11a00be47c" +fake_lm_hash = "299BD128C1101FD6" +fake_password = "trytostealthis" +cred_telem = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_telem["data"] = [ + { + "identities": [{"username": fake_username, "credential_type": "username"}], + "secrets": [ + {"nt_hash": fake_nt_hash, "credential_type": "nt_hash"}, + {"lm_hash": fake_lm_hash, "credential_type": "lm_hash"}, + {"password": fake_password, "credential_type": "password"}, + ], + } +] + +cred_empty_telem = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_empty_telem["data"] = [{"identities": [], "secrets": []}] + + +@pytest.fixture +def fake_mongo(monkeypatch): + mongo = mongoengine.connection.get_connection() + monkeypatch.setattr("monkey_island.cc.services.config.mongo", mongo) + config = ConfigService.get_default_config() + ConfigService.update_config(config, should_encrypt=True) + return mongo + + +@pytest.mark.usefixtures("uses_database") +def test_cred_username_parsing(fake_mongo): + parse_credentials(cred_telem_usernames) + config = ConfigService.get_config(should_decrypt=True) + assert fake_username in dpath.util.get(config, USER_LIST_PATH) + + +@pytest.mark.usefixtures("uses_database") +def test_cred_telemetry_parsing(fake_mongo): + parse_credentials(cred_telem) + config = ConfigService.get_config(should_decrypt=True) + assert fake_username in dpath.util.get(config, USER_LIST_PATH) + assert fake_nt_hash in dpath.util.get(config, NTLM_HASH_LIST_PATH) + assert fake_lm_hash in dpath.util.get(config, LM_HASH_LIST_PATH) + assert fake_password in dpath.util.get(config, PASSWORD_LIST_PATH) + + +@pytest.mark.usefixtures("uses_database") +def test_empty_cred_telemetry_parsing(fake_mongo): + default_config = deepcopy(ConfigService.get_config(should_decrypt=True)) + default_usernames = dpath.util.get(default_config, USER_LIST_PATH) + default_nt_hashes = dpath.util.get(default_config, NTLM_HASH_LIST_PATH) + default_lm_hashes = dpath.util.get(default_config, LM_HASH_LIST_PATH) + default_passwords = dpath.util.get(default_config, PASSWORD_LIST_PATH) + + parse_credentials(cred_empty_telem) + config = ConfigService.get_config(should_decrypt=True) + + assert default_usernames == dpath.util.get(config, USER_LIST_PATH) + assert default_nt_hashes == dpath.util.get(config, NTLM_HASH_LIST_PATH) + assert default_lm_hashes == dpath.util.get(config, LM_HASH_LIST_PATH) + assert default_passwords == dpath.util.get(config, PASSWORD_LIST_PATH)