forked from p15670423/monkey
Merge pull request #1731 from guardicore/1695-parsing-mimikatz
1695 parsing mimikatz
This commit is contained in:
commit
3fee7dec90
|
@ -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"
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class CredentialType(Enum):
|
||||
USERNAME = 1
|
||||
PASSWORD = 2
|
||||
NT_HASH = 3
|
||||
LM_HASH = 4
|
||||
SSH_KEYPAIR = 5
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,5 @@
|
|||
from monkey_island.cc.services.config import ConfigService
|
||||
|
||||
|
||||
def process_username(username: dict):
|
||||
ConfigService.creds_add_username(username["username"])
|
|
@ -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"])
|
|
@ -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"])
|
|
@ -0,0 +1,5 @@
|
|||
from monkey_island.cc.services.config import ConfigService
|
||||
|
||||
|
||||
def process_password(password: dict):
|
||||
ConfigService.creds_add_password(password["password"])
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue