Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2269 update reports with exploitation events #2414

Merged
merged 22 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6c4c535
Island: Add IAgentEventRepository to ReportService
cakekoa Oct 7, 2022
c4b7bfb
UI: Remove credentials from SMB issue report
cakekoa Oct 10, 2022
a836ae8
UI: Remove credentials from SSH issue report
cakekoa Oct 10, 2022
69b5cfd
UI: Remove credentials from WMI issue report
cakekoa Oct 10, 2022
8df8b1e
UI: Remove service/port from Log4Shell issue report
cakekoa Oct 10, 2022
da7aecf
Island: Add method to process ExploitationEvent
cakekoa Oct 10, 2022
ff93ddf
Island: Add method to filter exploits by target ip
cakekoa Oct 10, 2022
c7529f1
Island: Update get_exploits to use repositories
cakekoa Oct 10, 2022
9d1cc39
Island: Remove unused method process_exploit
cakekoa Oct 10, 2022
a5d183f
Island: Access classmethod via cls
cakekoa Oct 10, 2022
3cfe5ed
Island: Remove unused fields from ExploiterReportInfo
cakekoa Oct 10, 2022
666fa11
Island: Add zerologon info to exploit report data
cakekoa Oct 10, 2022
2b85e82
Island: Remove processor from ExploiterDescriptor
cakekoa Oct 10, 2022
1653386
Docs: Update doc for ExploiterDescriptorEnum
cakekoa Oct 10, 2022
d3d63de
Island: Remove disused exploit processors
cakekoa Oct 10, 2022
e24922d
Island: Fix bad reference to ExploiterReportInfo
cakekoa Oct 10, 2022
645b119
UI: Simplify exploit reports
cakekoa Oct 10, 2022
3de4ed2
Island: Remove disused NodeService methods
cakekoa Oct 11, 2022
c92f067
Island: Use asdict() on ExploiterReportInfo
cakekoa Oct 11, 2022
7086638
Island: Fix grammar on SSH issue report
cakekoa Oct 11, 2022
2d1a748
UI: Fix grammar in SMB issue report
cakekoa Oct 13, 2022
d1cb7e1
Island: Rename exploit -> exploitation_event
cakekoa Oct 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/content/development/adding-exploits.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ A good example of an exploiter class is the [`SSHExploiter`](https://github.com/

#### Reporting

1. In the [report generation pipeline](https://github.com/guardicore/monkey/blob/develop/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py), define how your **exploiter's data** should be processed and displayed in the report. Use the default `ExploitProcessor` or create a custom exploit processor if needed.
1. In the [report generation pipeline](https://github.com/guardicore/monkey/blob/develop/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py), define how your **exploiter** should be displayed in the report.

```py
class ExploiterDescriptorEnum(Enum):
SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter", CredExploitProcessor)
SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter")
...
ZEROLOGON = ExploiterDescriptor("ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor)
MYNEWEXPLOITER = ExploitDescriptor("MyNewExploiter", "My New Eexploiter", ExploitProcessor) <=================================
ZEROLOGON = ExploiterDescriptor("ZerologonExploiter", "Zerologon Exploiter")
MYNEWEXPLOITER = ExploitDescriptor("MyNewExploiter", "My New Exploiter") <=================================
```

2. Describe how the Monkey Island should **display your exploiter's results** by defining the UI contents in the [security report](https://github.com/guardicore/monkey/blob/develop/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js).
Expand Down
1 change: 1 addition & 0 deletions monkey/monkey_island/cc/services/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def initialize_services(container: DIContainer, data_dir: Path):
ReportService.initialize(
container.resolve(AWSService),
container.resolve(IAgentConfigurationRepository),
container.resolve(IAgentEventRepository),
container.resolve(ICredentialsRepository),
container.resolve(IMachineRepository),
container.resolve(INodeRepository),
Expand Down
11 changes: 0 additions & 11 deletions monkey/monkey_island/cc/services/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,6 @@ def get_monkey_by_guid(monkey_guid):
def get_monkey_by_ip(ip_address):
return mongo.db.monkey.find_one({"ip_addresses": ip_address})

@staticmethod
def get_node_by_ip(ip_address):
return mongo.db.node.find_one({"ip_addresses": ip_address})

@staticmethod
def get_node_by_id(node_id):
return mongo.db.node.find_one({"_id": ObjectId(node_id)})
Expand Down Expand Up @@ -322,13 +318,6 @@ def is_any_monkey_exists():
def is_monkey_finished_running():
return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive()

@staticmethod
def get_node_or_monkey_by_ip(ip_address):
node = NodeService.get_node_by_ip(ip_address)
if node is not None:
return node
return NodeService.get_monkey_by_ip(ip_address)

@staticmethod
def get_node_or_monkey_by_id(node_id):
node = NodeService.get_node_by_id(node_id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,23 @@
from dataclasses import dataclass
from enum import Enum
from typing import Type

from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501
CredExploitProcessor,
)
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501
ExploitProcessor,
)
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.log4shell import ( # noqa: E501
Log4ShellProcessor,
)
from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( # noqa: E501
ZerologonExploitProcessor,
)


@dataclass
class ExploiterDescriptor:
# Must match with class names of exploiters in Infection Monkey code
class_name: str
display_name: str
processor: Type[object] = ExploitProcessor


class ExploiterDescriptorEnum(Enum):
SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter", CredExploitProcessor)
WMI = ExploiterDescriptor("WmiExploiter", "WMI Exploiter", CredExploitProcessor)
SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter", CredExploitProcessor)
HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter", ExploitProcessor)
MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter", ExploitProcessor)
ZEROLOGON = ExploiterDescriptor(
"ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor
)
POWERSHELL = ExploiterDescriptor(
"PowerShellExploiter", "PowerShell Remoting Exploiter", ExploitProcessor
)
LOG4SHELL = ExploiterDescriptor("Log4ShellExploiter", "Log4Shell Exploiter", Log4ShellProcessor)
SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter")
WMI = ExploiterDescriptor("WmiExploiter", "WMI Exploiter")
SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter")
HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter")
MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter")
ZEROLOGON = ExploiterDescriptor("ZerologonExploiter", "Zerologon Exploiter")
POWERSHELL = ExploiterDescriptor("PowerShellExploiter", "PowerShell Remoting Exploiter")
LOG4SHELL = ExploiterDescriptor("Log4ShellExploiter", "Log4Shell Exploiter")

@staticmethod
def get_by_class_name(class_name: str) -> ExploiterDescriptor:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from enum import Enum
from typing import List, Union
from typing import Union


class CredentialType(Enum):
Expand All @@ -14,11 +14,5 @@ class ExploiterReportInfo:
machine: str
ip_address: str
type: str
username: Union[str, None] = None
credential_type: Union[str, None] = None
ssh_key: Union[str, None] = None
password: Union[str, None] = None
port: Union[str, None] = None
paths: Union[List[str], None] = None
password_restored: Union[bool, None] = None
service: Union[str, None] = None

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

84 changes: 55 additions & 29 deletions monkey/monkey_island/cc/services/reporting/report.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import functools
import ipaddress
import logging
from collections import defaultdict
from dataclasses import asdict
from itertools import chain, product
from typing import List, Optional
from typing import Dict, Iterable, List, Optional

from common.agent_events import ExploitationEvent, PasswordRestorationEvent
from common.network.network_range import NetworkRange
from common.network.network_utils import get_my_ip_addresses_legacy, get_network_interfaces
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
Expand All @@ -12,6 +15,7 @@
from monkey_island.cc.models.report import get_report, save_report
from monkey_island.cc.repository import (
IAgentConfigurationRepository,
IAgentEventRepository,
ICredentialsRepository,
IMachineRepository,
INodeRepository,
Expand All @@ -29,14 +33,15 @@
from .. import AWSService
from . import aws_exporter
from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum
from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo
from .issue_processing.exploit_processing.exploiter_report_info import ExploiterReportInfo

logger = logging.getLogger(__name__)


class ReportService:
_aws_service: Optional[AWSService] = None
_agent_configuration_repository: Optional[IAgentConfigurationRepository] = None
_agent_event_repository: Optional[IAgentEventRepository] = None
_credentials_repository: Optional[ICredentialsRepository] = None
_machine_repository: Optional[IMachineRepository] = None
_node_repository: Optional[INodeRepository] = None
Expand All @@ -49,12 +54,14 @@ def initialize(
cls,
aws_service: AWSService,
agent_configuration_repository: IAgentConfigurationRepository,
agent_event_repository: IAgentEventRepository,
credentials_repository: ICredentialsRepository,
machine_repository: IMachineRepository,
node_repository: INodeRepository,
):
cls._aws_service = aws_service
cls._agent_configuration_repository = agent_configuration_repository
cls._agent_event_repository = agent_event_repository
cls._credentials_repository = credentials_repository
cls._machine_repository = machine_repository
cls._node_repository = node_repository
Expand Down Expand Up @@ -142,39 +149,58 @@ def get_scanners_of_machine(cls, machine: Machine) -> List[Machine]:
for node in nodes:
for dest, conn in node.connections.items():
if CommunicationType.SCANNED in conn and dest == machine.id:
scanner_machine = ReportService._machine_repository.get_machine_by_id(
node.machine_id
)
scanner_machine = cls._machine_repository.get_machine_by_id(node.machine_id)
scanner_machines.add(scanner_machine)

return list(scanner_machines)

@staticmethod
def process_exploit(exploit) -> ExploiterReportInfo:
exploiter_type = exploit["data"]["exploiter"]
exploiter_descriptor = ExploiterDescriptorEnum.get_by_class_name(exploiter_type)
processor = exploiter_descriptor.processor()
exploiter_info = processor.get_exploit_info_by_dict(exploiter_type, exploit)
return exploiter_info
@classmethod
def process_exploit_event(
cls,
exploitation_event: ExploitationEvent,
password_restored: Dict[ipaddress.IPv4Address, bool],
) -> ExploiterReportInfo:
if not cls._machine_repository:
raise RuntimeError("Machine repository does not exist")

target_machine = cls._machine_repository.get_machines_by_ip(exploitation_event.target)[0]
return ExploiterReportInfo(
target_machine.hostname,
str(exploitation_event.target),
exploitation_event.exploiter_name,
password_restored=password_restored[exploitation_event.target],
)

@staticmethod
def get_exploits() -> List[dict]:
query = [
{"$match": {"telem_category": "exploit", "data.exploitation_result": True}},
{
"$group": {
"_id": {"ip_address": "$data.machine.ip_addr"},
"data": {"$first": "$$ROOT"},
}
},
{"$replaceRoot": {"newRoot": "$data"}},
]
exploits = []
for exploit in mongo.db.telemetry.aggregate(query):
new_exploit = ReportService.process_exploit(exploit)
if new_exploit not in exploits:
exploits.append(new_exploit.__dict__)
return exploits
def filter_single_exploit_per_ip(
exploitation_events: Iterable[ExploitationEvent],
) -> Iterable[ExploitationEvent]:
"""
Yields the first exploit for each target IP
"""
ips = set()
for exploit in exploitation_events:
if exploit.target not in ips:
ips.add(exploit.target)
yield exploit

@classmethod
def get_exploits(cls) -> List[dict]:
if not cls._agent_event_repository:
raise RuntimeError("Agent event repository does not exist")

# Get the successful exploits
exploits = cls._agent_event_repository.get_events_by_type(ExploitationEvent)
successful_exploits = filter(lambda x: x.success, exploits)
filtered_exploits = ReportService.filter_single_exploit_per_ip(successful_exploits)

zerologon_events = cls._agent_event_repository.get_events_by_type(PasswordRestorationEvent)
password_restored = defaultdict(
lambda: None, {e.target: e.success for e in zerologon_events}
)

# Convert the ExploitationEvent into an ExploiterReportInfo
return [asdict(cls.process_exploit_event(e, password_restored)) for e in filtered_exploits]

@staticmethod
def get_monkey_subnets(monkey_guid):
Expand Down
Loading