forked from p34709852/monkey
commit
de435e27ad
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
{
|
{
|
||||||
"label": node["label"],
|
"hostname": machine.hostname,
|
||||||
"ip_addresses": node["ip_addresses"],
|
"ip_addresses": [str(iface.ip) for iface in machine.network_interfaces],
|
||||||
"accessible_from_nodes": nodes_that_can_access_current_node,
|
"accessible_from_nodes": [m.dict(simplify=True) for m in accessible_from],
|
||||||
"services": node["services"],
|
"domain_name": "",
|
||||||
"domain_name": node["domain_name"],
|
# TODO add services
|
||||||
"pba_results": node["pba_results"] if "pba_results" in node else "None",
|
"services": [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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='',
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue