diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 67640d352..b25ae476c 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -25,6 +25,7 @@ from monkey_island.cc.resources.configuration_import import ConfigurationImport from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.exploitations.manual_exploitation import ManualExploitation +from monkey_island.cc.resources.exploitations.monkey_exploitation import MonkeyExploitation from monkey_island.cc.resources.island_configuration import IslandConfiguration from monkey_island.cc.resources.island_logs import IslandLog from monkey_island.cc.resources.island_mode import IslandMode @@ -156,6 +157,7 @@ def init_api_resources(api): api.add_resource(AttackReport, "/api/report/attack") api.add_resource(RansomwareReport, "/api/report/ransomware") api.add_resource(ManualExploitation, "/api/exploitations/manual") + api.add_resource(MonkeyExploitation, "/api/exploitations/monkey") api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/") api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") diff --git a/monkey/monkey_island/cc/resources/exploitations/monkey_exploitation.py b/monkey/monkey_island/cc/resources/exploitations/monkey_exploitation.py new file mode 100644 index 000000000..5e00a51a0 --- /dev/null +++ b/monkey/monkey_island/cc/resources/exploitations/monkey_exploitation.py @@ -0,0 +1,13 @@ +import flask_restful + +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( + get_monkey_exploited, +) + + +class MonkeyExploitation(flask_restful.Resource): + @jwt_required + def get(self): + monkey_exploitations = [exploitation.__dict__ for exploitation in get_monkey_exploited()] + return {"monkey_exploitations": monkey_exploitations} diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index 0e0f1c299..5dd384511 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -1,11 +1,15 @@ from typing import Dict, List +from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( + MonkeyExploitation, + get_monkey_exploited, +) from monkey_island.cc.services.reporting.report import ReportService def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() - exploited = ReportService.get_exploited() + exploited = get_monkey_exploited() return { "num_scanned_nodes": len(scanned), @@ -14,11 +18,11 @@ def get_propagation_stats() -> Dict: } -def _get_exploit_counts(exploited: List[Dict]) -> Dict: +def _get_exploit_counts(exploited: List[MonkeyExploitation]) -> Dict: exploit_counts = {} for node in exploited: - for exploit in node["exploits"]: + for exploit in node.exploits: exploit_counts[exploit] = exploit_counts.get(exploit, 0) + 1 return exploit_counts diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py new file mode 100644 index 000000000..ac400d8cc --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py @@ -0,0 +1,60 @@ +import logging +from dataclasses import dataclass +from typing import List + +from monkey_island.cc.database import mongo +from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 + ExploiterDescriptorEnum, +) + +logger = logging.getLogger(__name__) + + +@dataclass +class MonkeyExploitation: + label: str + ip_addresses: List[str] + domain_name: str + exploits: List[str] + + +def get_monkey_exploited() -> List[MonkeyExploitation]: + exploited_with_monkeys = [ + NodeService.get_displayed_node_by_id(monkey["_id"], True) + for monkey in mongo.db.monkey.find({}, {"_id": 1}) + if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey["_id"])) + ] + + exploited_without_monkeys = [ + NodeService.get_displayed_node_by_id(node["_id"], True) + for node in mongo.db.node.find({"exploited": True}, {"_id": 1}) + ] + + exploited = exploited_with_monkeys + exploited_without_monkeys + + exploited = [ + MonkeyExploitation( + label=exploited_node["label"], + ip_addresses=exploited_node["ip_addresses"], + domain_name=exploited_node["domain_name"], + exploits=get_exploits_used_on_node(exploited_node), + ) + for exploited_node in exploited + ] + + logger.info("Exploited nodes generated for reporting") + + return exploited + + +def get_exploits_used_on_node(node: dict) -> List[str]: + return list( + set( + [ + ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name + for exploit in node["exploits"] + if exploit["result"] + ] + ) + ) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 20574c54f..6b9265435 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -21,8 +21,11 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.configuration.utils import ( get_config_network_segments_as_subnet_groups, ) -from monkey_island.cc.services.exploitations.manual_exploitation import get_manual_monkeys 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 ( + get_monkey_exploited, +) from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 ExploiterDescriptorEnum, ) @@ -150,47 +153,6 @@ class ReportService: nodes = nodes_without_monkeys + nodes_with_monkeys return nodes - @staticmethod - def get_exploited(): - exploited_with_monkeys = [ - NodeService.get_displayed_node_by_id(monkey["_id"], True) - for monkey in mongo.db.monkey.find({}, {"_id": 1}) - if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey["_id"])) - ] - - exploited_without_monkeys = [ - NodeService.get_displayed_node_by_id(node["_id"], True) - for node in mongo.db.node.find({"exploited": True}, {"_id": 1}) - ] - - exploited = exploited_with_monkeys + exploited_without_monkeys - - exploited = [ - { - "label": exploited_node["label"], - "ip_addresses": exploited_node["ip_addresses"], - "domain_name": exploited_node["domain_name"], - "exploits": ReportService.get_exploits_used_on_node(exploited_node), - } - for exploited_node in exploited - ] - - logger.info("Exploited nodes generated for reporting") - - return exploited - - @staticmethod - def get_exploits_used_on_node(node: dict) -> List[str]: - return list( - set( - [ - ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name - for exploit in node["exploits"] - if exploit["result"] - ] - ) - ) - @staticmethod def get_stolen_creds(): creds = [] @@ -648,7 +610,7 @@ class ReportService: monkey_latest_modify_time = Monkey.get_latest_modifytime() scanned_nodes = ReportService.get_scanned() - exploited_nodes = ReportService.get_exploited() + exploited_nodes = get_monkey_exploited() report = { "overview": { "manual_monkeys": ReportService.get_manual_monkey_hostnames(), diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index c9fdd2c52..f7cffd277 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -566,7 +566,7 @@ class ReportPageComponent extends AuthComponent {
- +
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js deleted file mode 100644 index 827549c1a..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table'; -import Pluralize from 'pluralize'; -import {renderArray, renderIpAddresses} from '../common/RenderArrays'; - - -const columns = [ - { - Header: 'Breached Servers', - columns: [ - {Header: 'Machine', accessor: 'label'}, - { - Header: 'IP Addresses', id: 'ip_addresses', - accessor: x => renderIpAddresses(x) - }, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} - ] - } -]; - -const pageSize = 10; - -class BreachedServersComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; - return ( - <> -

- The Monkey successfully breached {this.props.data.length} {Pluralize('machine', this.props.data.length)}: -

-
- -
- - ); - } -} - -export default BreachedServersComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.tsx new file mode 100644 index 000000000..f2f774d61 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.tsx @@ -0,0 +1,59 @@ +import React, {useEffect, useState} from 'react'; +import ReactTable from 'react-table'; +import Pluralize from 'pluralize'; +import {renderArray, renderIpAddresses} from '../common/RenderArrays'; +import LoadingIcon from '../../ui-components/LoadingIcon'; +import IslandHttpClient from '../../IslandHttpClient'; + + +const columns = [ + { + Header: 'Breached Servers', + columns: [ + {Header: 'Machine', accessor: 'label'}, + { + Header: 'IP Addresses', id: 'ip_addresses', + accessor: x => renderIpAddresses(x) + }, + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} + ] + } +]; + +const pageSize = 10; + +function BreachedServersComponent() { + + const [exploitations, setExploitations] = useState(null); + + useEffect(() => { + IslandHttpClient.get('/api/exploitations/monkey') + .then(res => setExploitations(res.body['monkey_exploitations'])) + }, []); + + if(exploitations === null){ + return + } + + let defaultPageSize = exploitations.length > pageSize ? pageSize : exploitations.length; + let showPagination = exploitations.length > pageSize; + return ( + <> +

+ The Monkey successfully breached {exploitations.length} {Pluralize('machine', exploitations.length)}: +

+
+ +
+ + ); + +} + +export default BreachedServersComponent; diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 4c586aa81..a12b2aa9c 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,6 +1,7 @@ import pytest from monkey_island.cc.services.ransomware import ransomware_report +from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import MonkeyExploitation from monkey_island.cc.services.reporting.report import ReportService @@ -8,13 +9,13 @@ from monkey_island.cc.services.reporting.report import ReportService def patch_report_service_for_stats(monkeypatch): TEST_SCANNED_RESULTS = [{}, {}, {}, {}] TEST_EXPLOITED_RESULTS = [ - {"exploits": ["SSH Exploiter"]}, - {"exploits": ["SSH Exploiter", "SMB Exploiter"]}, - {"exploits": ["WMI Exploiter"]}, + MonkeyExploitation("", [], "", exploits=["SSH Exploiter"]), + MonkeyExploitation("", [], "", exploits=["SSH Exploiter", "SMB Exploiter"]), + MonkeyExploitation("", [], "", exploits=["WMI Exploiter"]), ] monkeypatch.setattr(ReportService, "get_scanned", lambda: TEST_SCANNED_RESULTS) - monkeypatch.setattr(ReportService, "get_exploited", lambda: TEST_EXPLOITED_RESULTS) + monkeypatch.setattr(ransomware_report, "get_monkey_exploited", lambda: TEST_EXPLOITED_RESULTS) def test_get_propagation_stats__num_scanned(patch_report_service_for_stats): diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py new file mode 100644 index 000000000..cdee46ba1 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py @@ -0,0 +1,20 @@ +from tests.unit_tests.monkey_island.cc.services.reporting.test_report import ( + NODE_DICT, + NODE_DICT_DUPLICATE_EXPLOITS, + NODE_DICT_FAILED_EXPLOITS, +) + +from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( + get_exploits_used_on_node, +) + + +def test_get_exploits_used_on_node(): + exploits = get_exploits_used_on_node(NODE_DICT) + assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"]) + + exploits = get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS) + assert exploits == ["Drupal Server Exploiter"] + + exploits = get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS) + assert exploits == [] diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py index 989c46eed..0093e4235 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py @@ -164,14 +164,3 @@ def test_get_stolen_creds_no_creds(fake_mongo): expected_stolen_creds_no_creds = [] assert expected_stolen_creds_no_creds == stolen_creds_no_creds - - -def test_get_exploits_used_on_node(): - exploits = ReportService.get_exploits_used_on_node(NODE_DICT) - assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"]) - - exploits = ReportService.get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS) - assert exploits == ["Drupal Server Exploiter"] - - exploits = ReportService.get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS) - assert exploits == []