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:
parent
02d81771a9
commit
40820a5ba5
|
@ -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
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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 ""
|
|
@ -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}/>
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue