Island: refactor report generation to take credentials from model

Reporting used to fetch credentials from telemetries, but they are no longer stored. Instead, credentials are being fetched from stolen_credentials collection
This commit is contained in:
vakarisz 2022-02-25 17:28:07 +02:00
parent 02d81771a9
commit 40820a5ba5
5 changed files with 172 additions and 135 deletions

View File

@ -1,7 +1,7 @@
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo from monkey_island.cc.models import StolenCredentials
from monkey_island.cc.services.attack.technique_reports import AttackTechnique from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.reporting.report import ReportService from monkey_island.cc.services.reporting.stolen_credentials import get_stolen_creds
class T1003(AttackTechnique): class T1003(AttackTechnique):
@ -14,29 +14,10 @@ class T1003(AttackTechnique):
scanned_msg = "" scanned_msg = ""
used_msg = "Monkey successfully obtained some credentials from systems on the network." used_msg = "Monkey successfully obtained some credentials from systems on the network."
query = {
"$or": [
{
"telem_category": "system_info",
"$and": [
{"data.credentials": {"$exists": True}},
{"data.credentials": {"$gt": {}}},
],
}, # $gt: {} checks if field is not an empty object
{
"telem_category": "exploit",
"$and": [
{"data.info.credentials": {"$exists": True}},
{"data.info.credentials": {"$gt": {}}},
],
},
]
}
@staticmethod @staticmethod
def get_report_data(): def get_report_data():
def get_technique_status_and_data(): def get_technique_status_and_data():
if mongo.db.telemetry.count_documents(T1003.query): if list(StolenCredentials.objects()):
status = ScanStatus.USED.value status = ScanStatus.USED.value
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
@ -47,6 +28,5 @@ class T1003(AttackTechnique):
data.update(T1003.get_message_and_status(status)) data.update(T1003.get_message_and_status(status))
data.update(T1003.get_mitigation_by_status(status)) data.update(T1003.get_mitigation_by_status(status))
data["stolen_creds"] = ReportService.get_stolen_creds() data["stolen_creds"] = get_stolen_creds()
data["stolen_creds"].extend(ReportService.get_ssh_keys())
return data return data

View File

@ -16,7 +16,6 @@ from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.models.report import get_report, save_report from monkey_island.cc.models.report import get_report, save_report
from monkey_island.cc.models.telemetries import get_telemetry_by_query
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.configuration.utils import ( from monkey_island.cc.services.configuration.utils import (
get_config_network_segments_as_subnet_groups, get_config_network_segments_as_subnet_groups,
@ -26,22 +25,21 @@ from monkey_island.cc.services.reporting.exploitations.manual_exploitation impor
from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import (
get_monkey_exploited, get_monkey_exploited,
) )
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501
ExploiterDescriptorEnum,
)
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501
CredentialType,
)
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
ExploiterReportInfo,
)
from monkey_island.cc.services.reporting.pth_report import PTHReportService from monkey_island.cc.services.reporting.pth_report import PTHReportService
from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager
from monkey_island.cc.services.reporting.report_generation_synchronisation import ( from monkey_island.cc.services.reporting.report_generation_synchronisation import (
safe_generate_regular_report, safe_generate_regular_report,
) )
from monkey_island.cc.services.reporting.stolen_credentials import (
extract_ssh_keys,
get_stolen_creds,
)
from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses
from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum
from .issue_processing.exploit_processing.processors.cred_exploit import CredentialType
from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -133,104 +131,6 @@ class ReportService:
nodes = nodes_without_monkeys + nodes_with_monkeys nodes = nodes_without_monkeys + nodes_with_monkeys
return nodes return nodes
@staticmethod
def get_stolen_creds():
creds = []
stolen_system_info_creds = ReportService._get_credentials_from_system_info_telems()
creds.extend(stolen_system_info_creds)
stolen_exploit_creds = ReportService._get_credentials_from_exploit_telems()
creds.extend(stolen_exploit_creds)
logger.info("Stolen creds generated for reporting")
return creds
@staticmethod
def _get_credentials_from_system_info_telems():
formatted_creds = []
for telem in get_telemetry_by_query(
{"telem_category": "system_info", "data.credentials": {"$exists": True}},
{"data.credentials": 1, "monkey_guid": 1},
):
creds = telem["data"]["credentials"]
origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"]
formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin))
return formatted_creds
@staticmethod
def _get_credentials_from_exploit_telems():
formatted_creds = []
for telem in mongo.db.telemetry.find(
{"telem_category": "exploit", "data.info.credentials": {"$exists": True}},
{"data.info.credentials": 1, "data.machine": 1, "monkey_guid": 1},
):
creds = telem["data"]["info"]["credentials"]
domain_name = telem["data"]["machine"]["domain_name"]
ip = telem["data"]["machine"]["ip_addr"]
origin = domain_name if domain_name else ip
formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin))
return formatted_creds
@staticmethod
def _format_creds_for_reporting(telem, monkey_creds, origin):
creds = []
CRED_TYPE_DICT = {
"password": "Clear Password",
"lm_hash": "LM hash",
"ntlm_hash": "NTLM hash",
}
if len(monkey_creds) == 0:
return []
for user in monkey_creds:
for cred_type in CRED_TYPE_DICT:
if cred_type not in monkey_creds[user] or not monkey_creds[user][cred_type]:
continue
username = (
monkey_creds[user]["username"] if "username" in monkey_creds[user] else user
)
cred_row = {
"username": username,
"type": CRED_TYPE_DICT[cred_type],
"origin": origin,
}
if cred_row not in creds:
creds.append(cred_row)
return creds
@staticmethod
def get_ssh_keys():
"""
Return private ssh keys found as credentials
:return: List of credentials
"""
creds = []
for telem in mongo.db.telemetry.find(
{"telem_category": "system_info", "data.ssh_info": {"$exists": True}},
{"data.ssh_info": 1, "monkey_guid": 1},
):
origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"]
if telem["data"]["ssh_info"]:
# Pick out all ssh keys not yet included in creds
ssh_keys = [
{
"username": key_pair["name"],
"type": "Clear SSH private key",
"origin": origin,
}
for key_pair in telem["data"]["ssh_info"]
if key_pair["private_key"]
and {
"username": key_pair["name"],
"type": "Clear SSH private key",
"origin": origin,
}
not in creds
]
creds.extend(ssh_keys)
return creds
@staticmethod @staticmethod
def process_exploit(exploit) -> ExploiterReportInfo: def process_exploit(exploit) -> ExploiterReportInfo:
exploiter_type = exploit["data"]["exploiter"] exploiter_type = exploit["data"]["exploiter"]
@ -564,6 +464,7 @@ class ReportService:
issue_set = ReportService.get_issue_set(issues, config_users, config_passwords) issue_set = ReportService.get_issue_set(issues, config_users, config_passwords)
cross_segment_issues = ReportService.get_cross_segment_issues() cross_segment_issues = ReportService.get_cross_segment_issues()
monkey_latest_modify_time = Monkey.get_latest_modifytime() monkey_latest_modify_time = Monkey.get_latest_modifytime()
stolen_creds = get_stolen_creds()
scanned_nodes = ReportService.get_scanned() scanned_nodes = ReportService.get_scanned()
exploited_cnt = len(get_monkey_exploited()) exploited_cnt = len(get_monkey_exploited())
@ -585,8 +486,8 @@ class ReportService:
"glance": { "glance": {
"scanned": scanned_nodes, "scanned": scanned_nodes,
"exploited_cnt": exploited_cnt, "exploited_cnt": exploited_cnt,
"stolen_creds": ReportService.get_stolen_creds(), "stolen_creds": stolen_creds,
"ssh_keys": ReportService.get_ssh_keys(), "ssh_keys": extract_ssh_keys(stolen_creds),
"strong_users": PTHReportService.get_strong_users_on_crit_details(), "strong_users": PTHReportService.get_strong_users_on_crit_details(),
}, },
"recommendations": {"issues": issues, "domain_issues": domain_issues}, "recommendations": {"issues": issues, "domain_issues": domain_issues},

View File

@ -0,0 +1,59 @@
import logging
from typing import Mapping, Sequence
from common.common_consts.credential_component_type import CredentialComponentType
from monkey_island.cc.models import StolenCredentials
logger = logging.getLogger(__name__)
def get_stolen_creds() -> Sequence[Mapping]:
stolen_creds = _fetch_from_db()
stolen_creds = _format_creds_for_reporting(stolen_creds)
logger.info("Stolen creds generated for reporting")
return stolen_creds
def extract_ssh_keys(credentials: Sequence[Mapping]) -> Sequence[Mapping]:
ssh_keys = []
for credential in credentials:
if credential["_type"] == CredentialComponentType.SSH_KEYPAIR.name:
ssh_keys.append(credential)
return ssh_keys
def _fetch_from_db() -> Sequence[StolenCredentials]:
return list(StolenCredentials.objects())
def _format_creds_for_reporting(credentials: Sequence[StolenCredentials]):
formatted_creds = []
cred_type_dict = {
CredentialComponentType.PASSWORD.name: "Clear Password",
CredentialComponentType.LM_HASH.name: "LM hash",
CredentialComponentType.NT_HASH.name: "NTLM hash",
CredentialComponentType.SSH_KEYPAIR.name: "Clear SSH private key",
}
for cred in credentials:
for secret_type in cred.secrets:
if secret_type not in cred_type_dict:
continue
username = _get_username(cred)
cred_row = {
"username": username,
"_type": secret_type,
"type": cred_type_dict[secret_type],
"origin": cred.monkey.hostname,
}
if cred_row not in formatted_creds:
formatted_creds.append(cred_row)
return formatted_creds
def _get_username(credentials: StolenCredentials) -> str:
if credentials.identities:
return credentials.identities[0]["username"]
else:
return ""

View File

@ -556,7 +556,7 @@ class ReportPageComponent extends AuthComponent {
</div> </div>
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/> <StolenPasswords data={this.state.report.glance.stolen_creds}/>
</div> </div>
<div> <div>
<StrongUsers data={this.state.report.glance.strong_users}/> <StrongUsers data={this.state.report.glance.strong_users}/>

View File

@ -0,0 +1,97 @@
import pytest
from common.common_consts.credential_component_type import CredentialComponentType
from monkey_island.cc.models import Monkey, StolenCredentials
from monkey_island.cc.services.reporting.stolen_credentials import (
extract_ssh_keys,
get_stolen_creds,
)
monkey_hostname = "fake_hostname"
fake_monkey_guid = "abc"
fake_username = "m0nk3y_user"
fake_nt_hash = "c1c58f96cdf212b50837bc11a00be47c"
fake_lm_hash = "299BD128C1101FD6"
fake_password = "trytostealthis"
fake_ssh_key = "RSA_fake_key"
fake_credentials = {
"identities": [{"username": fake_username, "credential_type": "USERNAME"}],
"secrets": [
CredentialComponentType.NT_HASH.name,
CredentialComponentType.LM_HASH.name,
CredentialComponentType.PASSWORD.name,
CredentialComponentType.SSH_KEYPAIR.name,
],
}
@pytest.fixture
def fake_monkey():
monkey = Monkey()
monkey.guid = fake_monkey_guid
monkey.hostname = monkey_hostname
monkey.save()
return monkey.id
@pytest.mark.usefixture("uses_database")
def test_get_credentials(fake_monkey):
StolenCredentials(
identities=fake_credentials["identities"],
secrets=fake_credentials["secrets"],
monkey=fake_monkey,
).save()
credentials = get_stolen_creds()
result1 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.NT_HASH.name,
"type": "NTLM hash",
"username": fake_username,
}
result2 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.LM_HASH.name,
"type": "LM hash",
"username": fake_username,
}
result3 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.PASSWORD.name,
"type": "Clear Password",
"username": fake_username,
}
result4 = {
"origin": monkey_hostname,
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key",
"username": fake_username,
}
assert result1 in credentials
assert result2 in credentials
assert result3 in credentials
assert result4 in credentials
@pytest.mark.usefixtures("uses_database")
def test_extract_ssh_keys(fake_monkey):
StolenCredentials(
identities=fake_credentials["identities"],
secrets=fake_credentials["secrets"],
monkey=fake_monkey,
).save()
credentials = get_stolen_creds()
keys = extract_ssh_keys(credentials)
assert len(keys) == 1
result = {
"origin": monkey_hostname,
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key",
"username": fake_username,
}
assert result in keys