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 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)
|
@dataclass(frozen=True)
|
||||||
class LMHash(ICredentialComponent):
|
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
|
lm_hash: str
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from dataclasses import dataclass, field
|
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)
|
@dataclass(frozen=True)
|
||||||
class NTHash(ICredentialComponent):
|
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
|
nt_hash: str
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from dataclasses import dataclass, field
|
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)
|
@dataclass(frozen=True)
|
||||||
class Password(ICredentialComponent):
|
class Password(ICredentialComponent):
|
||||||
credential_type: CredentialType = field(default=CredentialType.PASSWORD, init=False)
|
credential_type: CredentialComponentType = field(
|
||||||
|
default=CredentialComponentType.PASSWORD.value, init=False
|
||||||
|
)
|
||||||
password: str
|
password: str
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
from dataclasses import dataclass, field
|
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)
|
@dataclass(frozen=True)
|
||||||
class SSHKeypair(ICredentialComponent):
|
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
|
private_key: str
|
||||||
public_key: str
|
public_key: str
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from dataclasses import dataclass, field
|
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)
|
@dataclass(frozen=True)
|
||||||
class Username(ICredentialComponent):
|
class Username(ICredentialComponent):
|
||||||
credential_type: CredentialType = field(default=CredentialType.USERNAME, init=False)
|
credential_type: CredentialComponentType = field(
|
||||||
|
default=CredentialComponentType.USERNAME.value, init=False
|
||||||
|
)
|
||||||
username: str
|
username: str
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username
|
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 . import pypykatz_handler
|
||||||
from .windows_credentials import WindowsCredentials
|
from .windows_credentials import WindowsCredentials
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MimikatzCredentialCollector(ICredentialCollector):
|
class MimikatzCredentialCollector(ICredentialCollector):
|
||||||
|
|
||||||
def collect_credentials(self, options=None) -> Sequence[Credentials]:
|
def collect_credentials(self, options=None) -> Sequence[Credentials]:
|
||||||
|
logger.info("Attempting to collect windows credentials with pypykatz.")
|
||||||
creds = pypykatz_handler.get_windows_creds()
|
creds = pypykatz_handler.get_windows_creds()
|
||||||
|
logger.info(f"Pypykatz gathered {len(creds)} credentials.")
|
||||||
return MimikatzCredentialCollector._to_credentials(creds)
|
return MimikatzCredentialCollector._to_credentials(creds)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -30,7 +30,6 @@ def get_ssh_info(telemetry_messenger: ITelemetryMessenger) -> Iterable[Dict]:
|
||||||
|
|
||||||
def _get_home_dirs() -> Iterable[Dict]:
|
def _get_home_dirs() -> Iterable[Dict]:
|
||||||
import pwd
|
import pwd
|
||||||
|
|
||||||
root_dir = _get_ssh_struct("root", "")
|
root_dir = _get_ssh_struct("root", "")
|
||||||
home_dirs = [
|
home_dirs = [
|
||||||
_get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() if x.pw_dir.startswith("/home")
|
_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 .plugin_type import PluginType
|
||||||
from .credential_collection import (
|
|
||||||
Credentials,
|
|
||||||
CredentialType,
|
|
||||||
ICredentialCollector,
|
|
||||||
ICredentialComponent,
|
|
||||||
)
|
|
||||||
from .i_puppet import (
|
from .i_puppet import (
|
||||||
IPuppet,
|
IPuppet,
|
||||||
ExploiterResultData,
|
ExploiterResultData,
|
||||||
|
@ -16,3 +10,8 @@ from .i_puppet import (
|
||||||
UnknownPluginError,
|
UnknownPluginError,
|
||||||
)
|
)
|
||||||
from .i_fingerprinter import IFingerprinter
|
from .i_fingerprinter import IFingerprinter
|
||||||
|
from .credential_collection import (
|
||||||
|
Credentials,
|
||||||
|
ICredentialCollector,
|
||||||
|
ICredentialComponent,
|
||||||
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from .i_credential_collector import ICredentialCollector
|
from .i_credential_collector import ICredentialCollector
|
||||||
from .credentials import Credentials
|
from .credentials import Credentials
|
||||||
from .i_credential_component import ICredentialComponent
|
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 abc import ABC, abstractmethod
|
||||||
|
|
||||||
from .credential_type import CredentialType
|
from common.common_consts.credential_component_type import CredentialComponentType
|
||||||
|
|
||||||
|
|
||||||
class ICredentialComponent(ABC):
|
class ICredentialComponent(ABC):
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def credential_type(self) -> CredentialType:
|
def credential_type(self) -> CredentialComponentType:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -4,7 +4,8 @@ from collections import namedtuple
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, Sequence
|
from typing import Dict, List, Sequence
|
||||||
|
|
||||||
from . import Credentials, PluginType
|
from . import PluginType
|
||||||
|
from .credential_collection import Credentials
|
||||||
|
|
||||||
|
|
||||||
class PortStatus(Enum):
|
class PortStatus(Enum):
|
||||||
|
|
|
@ -17,6 +17,9 @@ class CredentialsTelem(BaseTelem):
|
||||||
"""
|
"""
|
||||||
self._credentials = credentials
|
self._credentials = credentials
|
||||||
|
|
||||||
|
def send(self, log_data=True):
|
||||||
|
super().send(log_data=False)
|
||||||
|
|
||||||
def get_data(self) -> Dict:
|
def get_data(self) -> Dict:
|
||||||
# TODO: At a later time we can consider factoring this into a Serializer class or similar.
|
# 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))
|
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 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.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.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.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.scan import process_scan_telemetry
|
||||||
from monkey_island.cc.services.telemetry.processing.state import process_state_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
|
from monkey_island.cc.services.telemetry.processing.tunnel import process_tunnel_telemetry
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = {
|
TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = {
|
||||||
|
TelemCategoryEnum.CREDENTIALS: parse_credentials,
|
||||||
TelemCategoryEnum.TUNNEL: process_tunnel_telemetry,
|
TelemCategoryEnum.TUNNEL: process_tunnel_telemetry,
|
||||||
TelemCategoryEnum.STATE: process_state_telemetry,
|
TelemCategoryEnum.STATE: process_state_telemetry,
|
||||||
TelemCategoryEnum.EXPLOIT: process_exploit_telemetry,
|
TelemCategoryEnum.EXPLOIT: process_exploit_telemetry,
|
||||||
TelemCategoryEnum.SCAN: process_scan_telemetry,
|
TelemCategoryEnum.SCAN: process_scan_telemetry,
|
||||||
TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry,
|
|
||||||
TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry,
|
TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry,
|
||||||
TelemCategoryEnum.AWS_INFO: process_aws_telemetry,
|
TelemCategoryEnum.AWS_INFO: process_aws_telemetry,
|
||||||
# `lambda *args, **kwargs: None` is a no-op.
|
# `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