Skip to content

Commit 6f776ee

Browse files
Merge pull request #2885 from guardicore/2857-dynamically-generate-report
Generate report with `remediation_suggestion`
2 parents dd23e9e + b21d995 commit 6f776ee

25 files changed

+1611
-702
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
3838
- Reset workflow. Now it's possible to delete data gathered by agents without
3939
resetting the configuration and reset procedure requires fewer clicks. #957
4040
- Reduced the map refresh rate from 5 seconds to 1.
41+
- Cleaned up and removed duplication in security report. #2885
4142
- The setup procedure for custom server_config.json files to be simpler. #1576
4243
- The order and content of Monkey Island's initialization logging to give
4344
clearer instructions to the user and avoid confusion. #1684

monkey/common/hard_coded_exploiter_manifests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
target_operating_systems=(OperatingSystem.LINUX,),
126126
title="SSH Exploiter",
127127
version="1.0.0",
128-
description="Attempts a brute-force attack against SMB using known credentials, "
128+
description="Attempts a brute-force attack against SSH using known credentials, "
129129
"including SSH keys",
130130
link_to_documentation="https://www.guardicore.com/infectionmonkey/docs/reference/"
131131
"exploiters/sshexec/",

monkey/common/network/network_utils.py

-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
from netifaces import AF_INET, ifaddresses, interfaces
66

77

8-
def get_my_ip_addresses_legacy() -> Sequence[str]:
9-
return [str(ip) for ip in get_my_ip_addresses()]
10-
11-
128
def get_my_ip_addresses() -> Sequence[IPv4Address]:
139
return [interface.ip for interface in get_network_interfaces()]
1410

monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py

-18
This file was deleted.

monkey/monkey_island/cc/services/reporting/report.py

+15-61
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
)
2222
from common.agent_plugins import AgentPluginManifest, AgentPluginType
2323
from common.network.network_range import NetworkRange
24-
from common.network.network_utils import get_my_ip_addresses_legacy, get_network_interfaces
2524
from common.network.segmentation_utils import get_ip_if_in_subnet
2625
from common.types import PortStatus
2726
from monkey_island.cc.models import CommunicationType, Machine
@@ -37,7 +36,6 @@
3736
get_monkey_exploited,
3837
)
3938

40-
from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum
4139
from .issue_processing.exploit_processing.exploiter_report_info import ExploiterReportInfo
4240

4341
logger = logging.getLogger(__name__)
@@ -73,9 +71,6 @@ class ReportService:
7371
_report: Dict[str, Dict] = {}
7472
_report_generation_lock: Lock = Lock()
7573

76-
class DerivedIssueEnum:
77-
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
78-
7974
@classmethod
8075
def initialize(
8176
cls,
@@ -229,35 +224,6 @@ def get_exploits(cls) -> List[dict]:
229224
# Convert the ExploitationEvent into an ExploiterReportInfo
230225
return [asdict(cls.process_exploit_event(e, password_restored)) for e in filtered_exploits]
231226

232-
@classmethod
233-
def get_island_cross_segment_issues(cls):
234-
issues = []
235-
island_ips = get_my_ip_addresses_legacy()
236-
island_machines = [m for m in cls._machine_repository.get_machines() if m.island]
237-
for island_machine in island_machines:
238-
found_good_ip = False
239-
island_subnets = island_machine.network_interfaces
240-
for subnet in island_subnets:
241-
if str(subnet.ip) in island_ips:
242-
found_good_ip = True
243-
break
244-
if found_good_ip:
245-
break
246-
if not found_good_ip:
247-
issues.append(
248-
{
249-
"machine_id": island_machine.id,
250-
"type": "island_cross_segment",
251-
"machine": island_machine.hostname,
252-
"networks": [str(subnet) for subnet in island_subnets],
253-
"server_networks": [
254-
str(interface.network) for interface in get_network_interfaces()
255-
],
256-
}
257-
)
258-
259-
return issues
260-
261227
@classmethod
262228
def get_cross_segment_issues_of_single_machine(
263229
cls, source_subnet_range: NetworkRange, target_subnet_range: NetworkRange
@@ -466,26 +432,6 @@ def get_config_scan(cls):
466432
agent_configuration = cls._agent_configuration_repository.get_configuration()
467433
return agent_configuration.propagation.network_scan.targets.scan_my_networks
468434

469-
@staticmethod
470-
def get_issue_set(issues):
471-
issue_set = set()
472-
473-
for machine in issues:
474-
for issue in issues[machine]:
475-
if ReportService._is_zerologon_pass_restore_failed(issue):
476-
issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED)
477-
478-
issue_set.add(issue["type"])
479-
480-
return issue_set
481-
482-
@staticmethod
483-
def _is_zerologon_pass_restore_failed(issue: dict):
484-
return (
485-
issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name
486-
and not issue["password_restored"]
487-
)
488-
489435
@classmethod
490436
def is_report_generated(cls) -> bool:
491437
return bool(cls._report)
@@ -498,7 +444,6 @@ def generate_report(cls):
498444
return RuntimeError("Machine repository does not exist")
499445

500446
issues = ReportService.get_issues()
501-
issue_set = ReportService.get_issue_set(issues)
502447
cross_segment_issues = ReportService.get_cross_segment_issues()
503448
latest_event_timestamp = ReportService.get_latest_event_timestamp()
504449

@@ -517,9 +462,8 @@ def generate_report(cls):
517462
"%d/%m/%Y %H:%M:%S"
518463
),
519464
"monkey_duration": ReportService.get_monkey_duration(),
520-
"issues": issue_set,
521-
"cross_segment_issues": cross_segment_issues,
522465
},
466+
"cross_segment_issues": cross_segment_issues,
523467
"glance": {
524468
"scanned": scanned_nodes,
525469
"exploited_cnt": exploited_cnt,
@@ -534,14 +478,15 @@ def generate_report(cls):
534478
def get_issues(cls):
535479
ISSUE_GENERATORS = [
536480
ReportService.get_exploits,
537-
ReportService.get_island_cross_segment_issues,
538481
]
539482

540483
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
541484

542485
issues_dict = {}
543486
for issue in issues:
544-
issue = cls.add_remediation_to_issue(issue)
487+
manifest = cls._get_exploiter_manifests().get(issue["type"])
488+
issue = cls.add_remediation_to_issue(issue, manifest)
489+
issue = cls.add_description_to_issue(issue, manifest)
545490
if issue.get("is_local", True):
546491
machine_id = issue.get("machine_id")
547492
if machine_id not in issues_dict:
@@ -551,12 +496,21 @@ def get_issues(cls):
551496
return issues_dict
552497

553498
@classmethod
554-
def add_remediation_to_issue(cls, issue: Dict[str, Any]) -> Dict[str, Any]:
555-
manifest = cls._get_exploiter_manifests().get(issue["type"])
499+
def add_remediation_to_issue(
500+
cls, issue: Dict[str, Any], manifest: Optional[AgentPluginManifest]
501+
) -> Dict[str, Any]:
556502
if manifest:
557503
issue["remediation_suggestion"] = manifest.remediation_suggestion
558504
return issue
559505

506+
@classmethod
507+
def add_description_to_issue(
508+
cls, issue: Dict[str, Any], manifest: Optional[AgentPluginManifest]
509+
) -> Dict[str, Any]:
510+
if manifest:
511+
issue["description"] = manifest.description
512+
return issue
513+
560514
@classmethod
561515
def get_latest_event_timestamp(cls) -> Optional[float]:
562516
if not cls._agent_event_repository:

0 commit comments

Comments
 (0)