Merge branch '2268-update-reporting' into develop

PR #2367
This commit is contained in:
Mike Salvatore 2022-09-30 14:50:54 -04:00
commit de435e27ad
8 changed files with 148 additions and 46 deletions

View File

@ -79,8 +79,8 @@ class BatchingAgentEventForwarder:
try:
logger.debug(f"Sending Agent events to Island: {events}")
self._island_api_client.send_events(events)
except Exception as err:
logger.warning(f"Exception caught when connecting to the Island: {err}")
except Exception:
logger.exception("Exception caught when connecting to the Island")
def _send_remaining_events(self):
self._send_events_to_island()

View File

@ -38,3 +38,6 @@ class Machine(MutableInfectionMonkeyBaseModel):
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)(
make_immutable_sequence
)
def __hash__(self):
return self.id

View File

@ -94,6 +94,8 @@ def initialize_services(container: DIContainer, data_dir: Path):
container.resolve(AWSService),
container.resolve(IAgentConfigurationRepository),
container.resolve(ICredentialsRepository),
container.resolve(IMachineRepository),
container.resolve(INodeRepository),
)

View File

@ -2,15 +2,20 @@ import functools
import ipaddress
import logging
from itertools import chain, product
from typing import List
from typing import List, Optional
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
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.models import CommunicationType, Machine, Monkey
from monkey_island.cc.models.report import get_report, save_report
from monkey_island.cc.repository import IAgentConfigurationRepository, ICredentialsRepository
from monkey_island.cc.repository import (
IAgentConfigurationRepository,
ICredentialsRepository,
IMachineRepository,
INodeRepository,
)
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.reporting.exploitations.manual_exploitation import get_manual_monkeys
from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import (
@ -30,10 +35,11 @@ logger = logging.getLogger(__name__)
class ReportService:
_aws_service = None
_agent_configuration_repository = None
_credentials_repository = None
_aws_service: Optional[AWSService] = None
_agent_configuration_repository: Optional[IAgentConfigurationRepository] = None
_credentials_repository: Optional[ICredentialsRepository] = None
_machine_repository: Optional[IMachineRepository] = None
_node_repository: Optional[INodeRepository] = None
class DerivedIssueEnum:
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
@ -44,10 +50,14 @@ class ReportService:
aws_service: AWSService,
agent_configuration_repository: IAgentConfigurationRepository,
credentials_repository: ICredentialsRepository,
machine_repository: IMachineRepository,
node_repository: INodeRepository,
):
cls._aws_service = aws_service
cls._agent_configuration_repository = agent_configuration_repository
cls._credentials_repository = credentials_repository
cls._machine_repository = machine_repository
cls._node_repository = node_repository
# This should pull from Simulation entity
@staticmethod
@ -102,39 +112,42 @@ class ReportService:
def get_scanned():
formatted_nodes = []
nodes = ReportService.get_all_displayed_nodes()
machines = ReportService._machine_repository.get_machines()
for node in nodes:
# This information should be evident from the map, not sure a table/list is a good way
# to display it anyways
nodes_that_can_access_current_node = node["accessible_from_nodes_hostnames"]
formatted_nodes.append(
{
"label": node["label"],
"ip_addresses": node["ip_addresses"],
"accessible_from_nodes": nodes_that_can_access_current_node,
"services": node["services"],
"domain_name": node["domain_name"],
"pba_results": node["pba_results"] if "pba_results" in node else "None",
}
)
logger.info("Scanned nodes generated for reporting")
for machine in machines:
accessible_from = ReportService.get_scanners_of_machine(machine)
if accessible_from:
formatted_nodes.append(
{
"hostname": machine.hostname,
"ip_addresses": [str(iface.ip) for iface in machine.network_interfaces],
"accessible_from_nodes": [m.dict(simplify=True) for m in accessible_from],
"domain_name": "",
# TODO add services
"services": [],
}
)
return formatted_nodes
@staticmethod
def get_all_displayed_nodes():
nodes_without_monkeys = [
NodeService.get_displayed_node_by_id(node["_id"], True)
for node in mongo.db.node.find({}, {"_id": 1})
]
nodes_with_monkeys = [
NodeService.get_displayed_node_by_id(monkey["_id"], True)
for monkey in mongo.db.monkey.find({}, {"_id": 1})
]
nodes = nodes_without_monkeys + nodes_with_monkeys
return nodes
@classmethod
def get_scanners_of_machine(cls, machine: Machine) -> List[Machine]:
if not cls._node_repository:
raise RuntimeError("Node repository does not exist")
if not cls._machine_repository:
raise RuntimeError("Machine repository does not exist")
nodes = cls._node_repository.get_nodes()
scanner_machines = set()
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_machines.add(scanner_machine)
return list(scanner_machines)
@staticmethod
def process_exploit(exploit) -> ExploiterReportInfo:

View File

@ -2,7 +2,6 @@ import React, {Fragment} from 'react';
import Pluralize from 'pluralize';
import BreachedServers from 'components/report-components/security/BreachedServers';
import ScannedServers from 'components/report-components/security/ScannedServers';
import PostBreach from 'components/report-components/security/PostBreach';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, getOptions} from 'components/map/MapOptions';
import StolenPasswords from 'components/report-components/security/StolenPasswords';
@ -23,7 +22,7 @@ import {smbPasswordReport, smbPthReport} from './security/issues/SmbIssue';
import {hadoopIssueOverview, hadoopIssueReport} from './security/issues/HadoopIssue';
import {mssqlIssueOverview, mssqlIssueReport} from './security/issues/MssqlIssue';
import {wmiPasswordIssueReport, wmiPthIssueReport} from './security/issues/WmiIssue';
import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues/SshIssue';
import {shhIssueReport, sshIssueOverview, sshKeysReport} from './security/issues/SshIssue';
import {log4shellIssueOverview, log4shellIssueReport} from './security/issues/Log4ShellIssue';
import {
crossSegmentIssueOverview,
@ -31,11 +30,13 @@ import {
islandCrossSegmentIssueReport
} from './security/issues/CrossSegmentIssue';
import {
sharedCredsDomainIssueReport, sharedCredsIssueReport, sharedLocalAdminsIssueReport,
sharedAdminsDomainIssueOverview,
sharedCredsDomainIssueReport,
sharedCredsIssueReport,
sharedLocalAdminsIssueReport,
sharedPasswordsIssueOverview
} from './security/issues/SharedPasswordsIssue';
import {tunnelIssueReport, tunnelIssueOverview} from './security/issues/TunnelIssue';
import {tunnelIssueOverview, tunnelIssueReport} from './security/issues/TunnelIssue';
import {stolenCredsIssueOverview} from './security/issues/StolenCredsIssue';
import {strongUsersOnCritIssueReport} from './security/issues/StrongUsersOnCritIssue';
import {
@ -538,9 +539,11 @@ class ReportPageComponent extends AuthComponent {
<BreachedServers />
</div>
{/* Post breach data should be separated from scanned machines
<div style={{marginBottom: '20px'}}>
<PostBreach data={this.state.report.glance.scanned}/>
</div>
*/}
<div style={{marginBottom: '20px'}}>
<StolenPasswords

View File

@ -9,6 +9,10 @@ export let renderIpAddresses = function (val) {
</div>;
};
export let renderMachineArray = function(array) {
return <>{array.map(x => <div key={x.network_interfaces[0]}>{x.network_interfaces[0]}</div>)}</>;
}
export let renderLimitedArray = function (array,
limit,
className='',

View File

@ -1,19 +1,20 @@
import React from 'react';
import ReactTable from 'react-table';
import Pluralize from 'pluralize';
import {renderArray, renderIpAddresses} from '../common/RenderArrays';
import {renderArray, renderIpAddresses, renderMachineArray} from '../common/RenderArrays';
const columns = [
{
Header: 'Scanned Servers',
columns: [
{Header: 'Machine', accessor: 'label'},
{Header: 'Machine', id: 'machine', accessor: x => x.ip_addresses[0]},
{
Header: 'IP Addresses', id: 'ip_addresses',
accessor: x => renderIpAddresses(x)
},
{Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)},
{Header: 'Accessible From', id: 'accessible_from_nodes',
accessor: x => renderMachineArray(x.accessible_from_nodes)},
{Header: 'Services', id: 'services', accessor: x => renderArray(x.services)}
]
}
@ -27,7 +28,6 @@ class ScannedServersComponent extends React.Component {
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;

View File

@ -0,0 +1,77 @@
from ipaddress import IPv4Interface
from unittest.mock import MagicMock
from monkey_island.cc.models import CommunicationType, Machine, Node
from monkey_island.cc.services.reporting.report import ReportService
ISLAND_MACHINE = Machine(
id=99,
island=True,
hostname="Island",
hardware_id=5,
network_interfaces=[IPv4Interface("10.10.10.99/24")],
)
MACHINE_1 = Machine(
id=1,
hardware_id=9,
hostname="machine_1",
network_interfaces=[IPv4Interface("10.10.10.1/24")],
)
MACHINE_2 = Machine(
id=2,
hardware_id=9,
network_interfaces=[IPv4Interface("10.10.10.2/24")],
)
MACHINE_3 = Machine(
id=3,
hardware_id=9,
network_interfaces=[IPv4Interface("10.10.10.3/24")],
)
NODES = [
Node(
machine_id=1,
connections={"2": frozenset([CommunicationType.EXPLOITED, CommunicationType.SCANNED])},
),
Node(machine_id=99, connections={"1": frozenset([CommunicationType.SCANNED])}),
Node(
machine_id=3,
connections={"99": frozenset([CommunicationType.CC, CommunicationType.EXPLOITED])},
),
]
MACHINES = [MACHINE_1, MACHINE_2, MACHINE_3, ISLAND_MACHINE]
EXPECTED_SCANNED_MACHINES = [
{
"hostname": MACHINE_1.hostname,
"ip_addresses": [str(iface.ip) for iface in MACHINE_1.network_interfaces],
"accessible_from_nodes": [ISLAND_MACHINE.dict(simplify=True)],
"services": [],
"domain_name": "",
},
{
"hostname": MACHINE_2.hostname,
"ip_addresses": [str(iface.ip) for iface in MACHINE_2.network_interfaces],
"accessible_from_nodes": [MACHINE_1.dict(simplify=True)],
"services": [],
"domain_name": "",
},
]
def get_machine_by_id(machine_id):
return [machine for machine in MACHINES if machine_id == machine.id][0]
def test_get_scanned():
ReportService._node_repository = MagicMock()
ReportService._node_repository.get_nodes.return_value = NODES
ReportService._machine_repository = MagicMock()
ReportService._machine_repository.get_machines.return_value = MACHINES
ReportService._machine_repository.get_machine_by_id = get_machine_by_id
scanned = ReportService.get_scanned()
assert scanned == EXPECTED_SCANNED_MACHINES