Island: Export AWS findings without report_exporter_manager.py

This commit is contained in:
vakarisz 2022-05-11 17:07:16 +03:00
parent f83832dc3c
commit 7ed7164a03
4 changed files with 323 additions and 348 deletions

View File

@ -3,7 +3,6 @@ import json
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from threading import Thread
import gevent.hub import gevent.hub
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
@ -29,7 +28,6 @@ from monkey_island.cc.server_utils.consts import ( # noqa: E402
) )
from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402 from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402
from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402
from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402
from monkey_island.cc.setup import island_config_options_validator # noqa: E402 from monkey_island.cc.setup import island_config_options_validator # noqa: E402
from monkey_island.cc.setup.data_dir import IncompatibleDataDirectory, setup_data_dir # noqa: E402 from monkey_island.cc.setup.data_dir import IncompatibleDataDirectory, setup_data_dir # noqa: E402
@ -132,8 +130,6 @@ def _configure_gevent_exception_handling(data_dir):
def _start_island_server( def _start_island_server(
should_setup_only: bool, config_options: IslandConfigOptions, container: DIContainer should_setup_only: bool, config_options: IslandConfigOptions, container: DIContainer
): ):
# AWS exporter takes a long time to load
Thread(target=populate_exporter_list, name="Report exporter list", daemon=True).start()
app = init_app(mongo_setup.MONGO_URL, container) app = init_app(mongo_setup.MONGO_URL, container)
if should_setup_only: if should_setup_only:

View File

@ -7,6 +7,7 @@ from monkey_island.cc.services.post_breach_files import PostBreachFilesService
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
from . import AuthenticationService, JsonFileUserDatastore from . import AuthenticationService, JsonFileUserDatastore
from .reporting.report import ReportService
def initialize_services(data_dir: Path) -> DIContainer: def initialize_services(data_dir: Path) -> DIContainer:
@ -22,5 +23,6 @@ def initialize_services(data_dir: Path) -> DIContainer:
PostBreachFilesService.initialize(container.resolve(IFileStorageService)) PostBreachFilesService.initialize(container.resolve(IFileStorageService))
LocalMonkeyRunService.initialize(data_dir) LocalMonkeyRunService.initialize(data_dir)
AuthenticationService.initialize(data_dir, JsonFileUserDatastore(data_dir)) AuthenticationService.initialize(data_dir, JsonFileUserDatastore(data_dir))
ReportService.initialize(container.resolve(AWSService))
return container return container

View File

@ -1,21 +1,15 @@
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import Mapping
import boto3 import boto3
from botocore.exceptions import UnknownServiceError from botocore.exceptions import UnknownServiceError
from monkey_island.cc.services import aws_service from common.aws import AWSInstance
from monkey_island.cc.services.reporting.exporter import Exporter
__authors__ = ["maor.rayzin", "shay.nehmad"]
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa:E501 (Long import) from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa:E501 (Long import)
ExploiterDescriptorEnum, ExploiterDescriptorEnum,
) )
# noqa:E501 (Long import)
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa:E501 (Long import) from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa:E501 (Long import)
CredentialType, CredentialType,
) )
@ -25,376 +19,351 @@ logger = logging.getLogger(__name__)
INFECTION_MONKEY_ARN = "324264561773:product/guardicore/aws-infection-monkey" INFECTION_MONKEY_ARN = "324264561773:product/guardicore/aws-infection-monkey"
class AWSExporter(Exporter): def handle_report(report_json: Mapping, aws_instance: AWSInstance):
@staticmethod findings_list = []
def handle_report(report_json): issues_list = report_json["recommendations"]["issues"]
if not issues_list:
findings_list = [] logger.info("No issues were found by the monkey, no need to send anything")
issues_list = report_json["recommendations"]["issues"]
if not issues_list:
logger.info("No issues were found by the monkey, no need to send anything")
return True
current_aws_region = aws_service.get_region()
for machine in issues_list:
for issue in issues_list[machine]:
try:
if "aws_instance_id" in issue:
findings_list.append(
AWSExporter._prepare_finding(issue, current_aws_region)
)
except AWSExporter.FindingNotFoundError as e:
logger.error(e)
if not AWSExporter._send_findings(findings_list, current_aws_region):
logger.error("Exporting findings to aws failed")
return False
return True return True
@staticmethod for machine in issues_list:
def merge_two_dicts(x, y): for issue in issues_list[machine]:
z = x.copy() # start with x's keys and values try:
z.update(y) # modifies z with y's keys and values & returns None if "aws_instance_id" in issue:
return z findings_list.append(_prepare_finding(issue, aws_instance))
except FindingNotFoundError as e:
logger.error(e)
@staticmethod if not _send_findings(findings_list, aws_instance.region):
def _prepare_finding(issue, region): logger.error("Exporting findings to aws failed")
findings_dict = { return False
"island_cross_segment": AWSExporter._handle_island_cross_segment_issue,
ExploiterDescriptorEnum.SSH.value.class_name: {
CredentialType.PASSWORD.value: AWSExporter._handle_ssh_issue,
CredentialType.KEY.value: AWSExporter._handle_ssh_key_issue,
},
"tunnel": AWSExporter._handle_tunnel_issue,
ExploiterDescriptorEnum.SMB.value.class_name: {
CredentialType.PASSWORD.value: AWSExporter._handle_smb_password_issue,
CredentialType.HASH.value: AWSExporter._handle_smb_pth_issue,
},
"shared_passwords": AWSExporter._handle_shared_passwords_issue,
ExploiterDescriptorEnum.WMI.value.class_name: {
CredentialType.PASSWORD.value: AWSExporter._handle_wmi_password_issue,
CredentialType.HASH.value: AWSExporter._handle_wmi_pth_issue,
},
"shared_passwords_domain": AWSExporter._handle_shared_passwords_domain_issue,
"shared_admins_domain": AWSExporter._handle_shared_admins_domain_issue,
"strong_users_on_crit": AWSExporter._handle_strong_users_on_crit_issue,
ExploiterDescriptorEnum.HADOOP.value.class_name: AWSExporter._handle_hadoop_issue,
}
configured_product_arn = INFECTION_MONKEY_ARN return True
product_arn = "arn:aws:securityhub:{region}:{arn}".format(
region=region, arn=configured_product_arn
)
instance_arn = "arn:aws:ec2:" + str(region) + ":instance:{instance_id}"
# Not suppressing error here on purpose.
account_id = aws_service.get_account_id()
logger.debug("aws account id acquired: {}".format(account_id))
aws_finding = {
"SchemaVersion": "2018-10-08",
"Id": uuid.uuid4().hex,
"ProductArn": product_arn,
"GeneratorId": issue["type"],
"AwsAccountId": account_id,
"RecordState": "ACTIVE",
"Types": ["Software and Configuration Checks/Vulnerabilities/CVE"],
"CreatedAt": datetime.now().isoformat() + "Z",
"UpdatedAt": datetime.now().isoformat() + "Z",
}
processor = AWSExporter._get_issue_processor(findings_dict, issue) def merge_two_dicts(x, y):
z = x.copy() # start with x's keys and values
z.update(y) # modifies z with y's keys and values & returns None
return z
return AWSExporter.merge_two_dicts(aws_finding, processor(issue, instance_arn))
@staticmethod def _prepare_finding(issue, aws_instance: AWSInstance):
def _get_issue_processor(finding_dict, issue): findings_dict = {
try: "island_cross_segment": _handle_island_cross_segment_issue,
processor = finding_dict[issue["type"]] ExploiterDescriptorEnum.SSH.value.class_name: {
if type(processor) == dict: CredentialType.PASSWORD.value: _handle_ssh_issue,
processor = processor[issue["credential_type"]] CredentialType.KEY.value: _handle_ssh_key_issue,
return processor },
except KeyError: "tunnel": _handle_tunnel_issue,
raise AWSExporter.FindingNotFoundError( ExploiterDescriptorEnum.SMB.value.class_name: {
f"Finding {issue['type']} not added as AWS exportable finding" CredentialType.PASSWORD.value: _handle_smb_password_issue,
) CredentialType.HASH.value: _handle_smb_pth_issue,
},
"shared_passwords": _handle_shared_passwords_issue,
ExploiterDescriptorEnum.WMI.value.class_name: {
CredentialType.PASSWORD.value: _handle_wmi_password_issue,
CredentialType.HASH.value: _handle_wmi_pth_issue,
},
"shared_passwords_domain": _handle_shared_passwords_domain_issue,
"shared_admins_domain": _handle_shared_admins_domain_issue,
"strong_users_on_crit": _handle_strong_users_on_crit_issue,
ExploiterDescriptorEnum.HADOOP.value.class_name: _handle_hadoop_issue,
}
class FindingNotFoundError(Exception): configured_product_arn = INFECTION_MONKEY_ARN
pass product_arn = "arn:aws:securityhub:{region}:{arn}".format(
region=aws_instance.region, arn=configured_product_arn
)
instance_arn = "arn:aws:ec2:" + str(aws_instance.region) + ":instance:{instance_id}"
account_id = aws_instance.account_id
logger.debug("aws account id acquired: {}".format(account_id))
@staticmethod aws_finding = {
def _send_findings(findings_list, region): "SchemaVersion": "2018-10-08",
try: "Id": uuid.uuid4().hex,
logger.debug("Trying to acquire securityhub boto3 client in " + region) "ProductArn": product_arn,
security_hub_client = boto3.client("securityhub", region_name=region) "GeneratorId": issue["type"],
logger.debug("Client acquired: {0}".format(repr(security_hub_client))) "AwsAccountId": account_id,
"RecordState": "ACTIVE",
"Types": ["Software and Configuration Checks/Vulnerabilities/CVE"],
"CreatedAt": datetime.now().isoformat() + "Z",
"UpdatedAt": datetime.now().isoformat() + "Z",
}
# Assumes the machine has the correct IAM role to do this, @see processor = _get_issue_processor(findings_dict, issue)
# https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS
# -EC2-instances
import_response = security_hub_client.batch_import_findings(Findings=findings_list)
logger.debug("Import findings response: {0}".format(repr(import_response)))
if import_response["ResponseMetadata"]["HTTPStatusCode"] == 200: return merge_two_dicts(aws_finding, processor(issue, instance_arn))
return True
else:
return False
except UnknownServiceError as e:
logger.warning(
"AWS exporter called but AWS-CLI security hub service is not installed. "
"Error: {}".format(e)
)
return False
except Exception as e:
logger.exception("AWS security hub findings failed to send. Error: {}".format(e))
return False
@staticmethod
def _get_finding_resource(instance_id, instance_arn): def _get_issue_processor(finding_dict, issue):
if instance_id: try:
return [{"Type": "AwsEc2Instance", "Id": instance_arn.format(instance_id=instance_id)}] processor = finding_dict[issue["type"]]
if type(processor) == dict:
processor = processor[issue["credential_type"]]
return processor
except KeyError:
raise FindingNotFoundError(f"Finding {issue['type']} not added as AWS exportable finding")
class FindingNotFoundError(Exception):
pass
def _send_findings(findings_list, region):
try:
logger.debug("Trying to acquire securityhub boto3 client in " + region)
security_hub_client = boto3.client("securityhub", region_name=region)
logger.debug("Client acquired: {0}".format(repr(security_hub_client)))
# Assumes the machine has the correct IAM role to do this, @see
# https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS
# -EC2-instances
import_response = security_hub_client.batch_import_findings(Findings=findings_list)
logger.debug("Import findings response: {0}".format(repr(import_response)))
if import_response["ResponseMetadata"]["HTTPStatusCode"] == 200:
return True
else: else:
return [{"Type": "Other", "Id": "None"}] return False
except UnknownServiceError as e:
@staticmethod logger.warning(
def _build_generic_finding( "AWS exporter called but AWS-CLI security hub service is not installed. "
severity, title, description, recommendation, instance_arn, instance_id=None "Error: {}".format(e)
):
finding = {
"Severity": {"Product": severity, "Normalized": 100},
"Resources": AWSExporter._get_finding_resource(instance_id, instance_arn),
"Title": title,
"Description": description,
"Remediation": {"Recommendation": {"Text": recommendation}},
}
return finding
@staticmethod
def _handle_tunnel_issue(issue, instance_arn):
return AWSExporter._build_generic_finding(
severity=5,
title="Weak segmentation - Machines were able to communicate over unused ports.",
description="Use micro-segmentation policies to disable communication other than "
"the required.",
recommendation="Machines are not locked down at port level. "
"Network tunnel was set up from {0} to {1}".format(issue["machine"], issue["dest"]),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
) )
return False
except Exception as e:
logger.exception("AWS security hub findings failed to send. Error: {}".format(e))
return False
@staticmethod
def _handle_smb_pth_issue(issue, instance_arn):
return AWSExporter._build_generic_finding( def _get_finding_resource(instance_id, instance_arn):
severity=5, if instance_id:
title="Machines are accessible using passwords supplied by the user during the " return [{"Type": "AwsEc2Instance", "Id": instance_arn.format(instance_id=instance_id)}]
"Monkey's configuration.", else:
description="Change {0}'s password to a complex one-use password that is not " return [{"Type": "Other", "Id": "None"}]
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey "
"used a pass-the-hash attack over "
"SMB protocol with user {2}.".format(
issue["machine"], issue["ip_address"], issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod
def _handle_ssh_issue(issue, instance_arn):
return AWSExporter._build_generic_finding( def _build_generic_finding(
severity=1, severity, title, description, recommendation, instance_arn, instance_id=None
title="Machines are accessible using SSH passwords supplied by the user during " ):
"the Monkey's configuration.", finding = {
description="Change {0}'s password to a complex one-use password that is not " "Severity": {"Product": severity, "Normalized": 100},
"shared with other computers on the " "Resources": _get_finding_resource(instance_id, instance_arn),
"network.".format(issue["username"]), "Title": title,
recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey " "Description": description,
"authenticated over the SSH" "Remediation": {"Recommendation": {"Text": recommendation}},
" protocol with user {2} and its " }
"password.".format(issue["machine"], issue["ip_address"], issue["username"]),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod return finding
def _handle_ssh_key_issue(issue, instance_arn):
return AWSExporter._build_generic_finding(
severity=1,
title="Machines are accessible using SSH passwords supplied by the user during "
"the Monkey's configuration.",
description="Protect {ssh_key} private key with a pass phrase.".format(
ssh_key=issue["ssh_key"]
),
recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH "
"attack. The Monkey authenticated "
"over the SSH protocol with private key {ssh_key}.".format(
machine=issue["machine"], ip_address=issue["ip_address"], ssh_key=issue["ssh_key"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_tunnel_issue(issue, instance_arn):
def _handle_island_cross_segment_issue(issue, instance_arn): return _build_generic_finding(
severity=5,
title="Weak segmentation - Machines were able to communicate over unused ports.",
description="Use micro-segmentation policies to disable communication other than "
"the required.",
recommendation="Machines are not locked down at port level. "
"Network tunnel was set up from {0} to {1}".format(issue["machine"], issue["dest"]),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Weak segmentation - Machines from different segments are able to "
"communicate.",
description="Segment your network and make sure there is no communication between "
"machines from different "
"segments.",
recommendation="The network can probably be segmented. A monkey instance on \
{0} in the networks {1} \
could directly access the Monkey Island server in the networks {2}.".format(
issue["machine"], issue["networks"], issue["server_networks"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_smb_pth_issue(issue, instance_arn):
def _handle_shared_passwords_issue(issue, instance_arn): return _build_generic_finding(
severity=5,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey "
"used a pass-the-hash attack over "
"SMB protocol with user {2}.".format(
issue["machine"], issue["ip_address"], issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Multiple users have the same password",
description="Some users are sharing passwords, this should be fixed by changing "
"passwords.",
recommendation="These users are sharing access password: {0}.".format(
issue["shared_with"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_ssh_issue(issue, instance_arn):
def _handle_smb_password_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Machines are accessible using SSH passwords supplied by the user during "
"the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey "
"authenticated over the SSH"
" protocol with user {2} and its "
"password.".format(issue["machine"], issue["ip_address"], issue["username"]),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey "
"authenticated over the SMB "
"protocol with user {2} and its password.".format(
issue["machine"], issue["ip_address"], issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_ssh_key_issue(issue, instance_arn):
def _handle_wmi_password_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Machines are accessible using SSH passwords supplied by the user during "
"the Monkey's configuration.",
description="Protect {ssh_key} private key with a pass phrase.".format(
ssh_key=issue["ssh_key"]
),
recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH "
"attack. The Monkey authenticated "
"over the SSH protocol with private key {ssh_key}.".format(
machine=issue["machine"], ip_address=issue["ip_address"], ssh_key=issue["ssh_key"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.",
recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI "
"attack. The Monkey authenticated over "
"the WMI protocol with user {username} and its password.".format(
machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_island_cross_segment_issue(issue, instance_arn):
def _handle_wmi_pth_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Weak segmentation - Machines from different segments are able to " "communicate.",
description="Segment your network and make sure there is no communication between "
"machines from different "
"segments.",
recommendation="The network can probably be segmented. A monkey instance on \
{0} in the networks {1} \
could directly access the Monkey Island server in the networks {2}.".format(
issue["machine"], issue["networks"], issue["server_networks"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI "
"attack. The Monkey used a "
"pass-the-hash attack over WMI protocol with user {username}".format(
machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_shared_passwords_issue(issue, instance_arn):
def _handle_shared_passwords_domain_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Multiple users have the same password",
description="Some users are sharing passwords, this should be fixed by changing "
"passwords.",
recommendation="These users are sharing access password: {0}.".format(issue["shared_with"]),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Multiple users have the same password.",
description="Some domain users are sharing passwords, this should be fixed by "
"changing passwords.",
recommendation="These users are sharing access password: {shared_with}.".format(
shared_with=issue["shared_with"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_smb_password_issue(issue, instance_arn):
def _handle_shared_admins_domain_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey "
"authenticated over the SMB "
"protocol with user {2} and its password.".format(
issue["machine"], issue["ip_address"], issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Shared local administrator account - Different machines have the same "
"account as a local administrator.",
description="Make sure the right administrator accounts are managing the right "
"machines, and that there isn't "
"an unintentional local admin sharing.",
recommendation="Here is a list of machines which the account {username} is "
"defined as an administrator: "
"{shared_machines}".format(
username=issue["username"], shared_machines=issue["shared_machines"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_wmi_password_issue(issue, instance_arn):
def _handle_strong_users_on_crit_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.",
recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI "
"attack. The Monkey authenticated over "
"the WMI protocol with user {username} and its password.".format(
machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=1,
title="Mimikatz found login credentials of a user who has admin access to a "
"server defined as critical.",
description="This critical machine is open to attacks via strong users with "
"access to it.",
recommendation="The services: {services} have been found on the machine thus "
"classifying it as a critical "
"machine. These users has access to it:{threatening_users}.".format(
services=issue["services"], threatening_users=issue["threatening_users"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
@staticmethod def _handle_wmi_pth_issue(issue, instance_arn):
def _handle_hadoop_issue(issue, instance_arn): return _build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the "
"Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not "
"shared with other computers on the "
"network.".format(issue["username"]),
recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI "
"attack. The Monkey used a "
"pass-the-hash attack over WMI protocol with user {username}".format(
machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
return AWSExporter._build_generic_finding(
severity=10, def _handle_shared_passwords_domain_issue(issue, instance_arn):
title="Hadoop/Yarn servers are vulnerable to remote code execution.", return _build_generic_finding(
description="Run Hadoop in secure mode, add Kerberos authentication.", severity=1,
recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to " title="Multiple users have the same password.",
"remote code execution attack." description="Some domain users are sharing passwords, this should be fixed by "
"The attack was made possible due to default Hadoop/Yarn " "changing passwords.",
"configuration being insecure.", recommendation="These users are sharing access password: {shared_with}.".format(
instance_arn=instance_arn, shared_with=issue["shared_with"]
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ),
) instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
def _handle_shared_admins_domain_issue(issue, instance_arn):
return _build_generic_finding(
severity=1,
title="Shared local administrator account - Different machines have the same "
"account as a local administrator.",
description="Make sure the right administrator accounts are managing the right "
"machines, and that there isn't "
"an unintentional local admin sharing.",
recommendation="Here is a list of machines which the account {username} is "
"defined as an administrator: "
"{shared_machines}".format(
username=issue["username"], shared_machines=issue["shared_machines"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
def _handle_strong_users_on_crit_issue(issue, instance_arn):
return _build_generic_finding(
severity=1,
title="Mimikatz found login credentials of a user who has admin access to a "
"server defined as critical.",
description="This critical machine is open to attacks via strong users with "
"access to it.",
recommendation="The services: {services} have been found on the machine thus "
"classifying it as a critical "
"machine. These users has access to it:{threatening_users}.".format(
services=issue["services"], threatening_users=issue["threatening_users"]
),
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)
def _handle_hadoop_issue(issue, instance_arn):
return _build_generic_finding(
severity=10,
title="Hadoop/Yarn servers are vulnerable to remote code execution.",
description="Run Hadoop in secure mode, add Kerberos authentication.",
recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to "
"remote code execution attack."
"The attack was made possible due to default Hadoop/Yarn "
"configuration being insecure.",
instance_arn=instance_arn,
instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None,
)

View File

@ -26,7 +26,6 @@ from monkey_island.cc.services.reporting.exploitations.monkey_exploitation impor
get_monkey_exploited, get_monkey_exploited,
) )
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_generation_synchronisation import ( from monkey_island.cc.services.reporting.report_generation_synchronisation import (
safe_generate_regular_report, safe_generate_regular_report,
) )
@ -36,6 +35,8 @@ from monkey_island.cc.services.reporting.stolen_credentials import (
) )
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 .. import AWSService
from . import aws_exporter
from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum 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.cred_exploit import CredentialType
from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo
@ -44,11 +45,18 @@ logger = logging.getLogger(__name__)
class ReportService: class ReportService:
_aws_service = None
class DerivedIssueEnum: class DerivedIssueEnum:
WEAK_PASSWORD = "weak_password" WEAK_PASSWORD = "weak_password"
STOLEN_CREDS = "stolen_creds" STOLEN_CREDS = "stolen_creds"
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed" ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
@classmethod
def initialize(cls, aws_service: AWSService):
cls._aws_service = aws_service
@staticmethod @staticmethod
def get_first_monkey_time(): def get_first_monkey_time():
return ( return (
@ -488,8 +496,8 @@ class ReportService:
"recommendations": {"issues": issues, "domain_issues": domain_issues}, "recommendations": {"issues": issues, "domain_issues": domain_issues},
"meta_info": {"latest_monkey_modifytime": monkey_latest_modify_time}, "meta_info": {"latest_monkey_modifytime": monkey_latest_modify_time},
} }
ReportExporterManager().export(report)
save_report(report) save_report(report)
aws_exporter.handle_report(report, ReportService._aws_service.island_aws_instance())
return report return report
@staticmethod @staticmethod