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: try:
logger.debug(f"Sending Agent events to Island: {events}") logger.debug(f"Sending Agent events to Island: {events}")
self._island_api_client.send_events(events) self._island_api_client.send_events(events)
except Exception as err: except Exception:
logger.warning(f"Exception caught when connecting to the Island: {err}") logger.exception("Exception caught when connecting to the Island")
def _send_remaining_events(self): def _send_remaining_events(self):
self._send_events_to_island() 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 = validator("network_interfaces", pre=True, allow_reuse=True)(
make_immutable_sequence 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(AWSService),
container.resolve(IAgentConfigurationRepository), container.resolve(IAgentConfigurationRepository),
container.resolve(ICredentialsRepository), container.resolve(ICredentialsRepository),
container.resolve(IMachineRepository),
container.resolve(INodeRepository),
) )

View File

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

View File

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

View File

@ -9,6 +9,10 @@ export let renderIpAddresses = function (val) {
</div>; </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, export let renderLimitedArray = function (array,
limit, limit,
className='', className='',

View File

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