forked from p15670423/monkey
Island: extract monkey exploitations into a separate service and a separate endpoint
This change not only removes complexity from the huge report service, but also allows different UI components to call the API without forcing the whole report to be generated
This commit is contained in:
parent
1013347b3c
commit
1448bb1850
|
@ -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/<string:finding_id>")
|
||||
api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/")
|
||||
|
|
|
@ -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}
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
]
|
||||
)
|
||||
)
|
|
@ -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(),
|
||||
|
|
|
@ -566,7 +566,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
</div>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||
<BreachedServers />
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<p>
|
||||
The Monkey successfully breached <span
|
||||
className="badge badge-danger">{this.props.data.length}</span> {Pluralize('machine', this.props.data.length)}:
|
||||
</p>
|
||||
<div className="data-table-container">
|
||||
<ReactTable
|
||||
columns={columns}
|
||||
data={this.props.data}
|
||||
showPagination={showPagination}
|
||||
defaultPageSize={defaultPageSize}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BreachedServersComponent;
|
|
@ -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 <LoadingIcon />
|
||||
}
|
||||
|
||||
let defaultPageSize = exploitations.length > pageSize ? pageSize : exploitations.length;
|
||||
let showPagination = exploitations.length > pageSize;
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
The Monkey successfully breached <span
|
||||
className="badge badge-danger">{exploitations.length}</span> {Pluralize('machine', exploitations.length)}:
|
||||
</p>
|
||||
<div className="data-table-container">
|
||||
<ReactTable
|
||||
columns={columns}
|
||||
data={exploitations}
|
||||
showPagination={showPagination}
|
||||
defaultPageSize={defaultPageSize}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default BreachedServersComponent;
|
|
@ -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):
|
||||
|
|
|
@ -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 == []
|
|
@ -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 == []
|
||||
|
|
Loading…
Reference in New Issue