Merge pull request #1375 from guardicore/ransomware_exploits_table
Ransomware report: Exploits table in lateral movement section
This commit is contained in:
commit
fceb52ba38
|
@ -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
|
||||
|
@ -154,6 +155,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/")
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import flask_restful
|
||||
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
from monkey_island.cc.services.exploitations.manual_exploitation import get_manual_exploitations
|
||||
from monkey_island.cc.services.reporting.exploitations.manual_exploitation import (
|
||||
get_manual_exploitations,
|
||||
)
|
||||
|
||||
|
||||
class ManualExploitation(flask_restful.Resource):
|
||||
|
|
|
@ -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,62 @@
|
|||
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_nodes_monkeys_launched = [
|
||||
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"]))
|
||||
]
|
||||
|
||||
# The node got exploited, but no monkeys got launched.
|
||||
# For example the exploited machine was too old.
|
||||
exploited_nodes_monkeys_failed = [
|
||||
NodeService.get_displayed_node_by_id(node["_id"], True)
|
||||
for node in mongo.db.node.find({"exploited": True}, {"_id": 1})
|
||||
]
|
||||
|
||||
exploited = exploited_nodes_monkeys_launched + exploited_nodes_monkeys_failed
|
||||
|
||||
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,
|
||||
)
|
||||
|
@ -148,47 +151,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 = []
|
||||
|
@ -646,7 +608,7 @@ class ReportService:
|
|||
monkey_latest_modify_time = Monkey.get_latest_modifytime()
|
||||
|
||||
scanned_nodes = ReportService.get_scanned()
|
||||
exploited_nodes = ReportService.get_exploited()
|
||||
exploited_cnt = len(get_monkey_exploited())
|
||||
report = {
|
||||
"overview": {
|
||||
"manual_monkeys": ReportService.get_manual_monkey_hostnames(),
|
||||
|
@ -664,7 +626,7 @@ class ReportService:
|
|||
},
|
||||
"glance": {
|
||||
"scanned": scanned_nodes,
|
||||
"exploited": exploited_nodes,
|
||||
"exploited_cnt": exploited_cnt,
|
||||
"stolen_creds": ReportService.get_stolen_creds(),
|
||||
"azure_passwords": ReportService.get_azure_creds(),
|
||||
"ssh_keys": ReportService.get_ssh_keys(),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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';
|
||||
|
@ -288,9 +289,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
<h2>
|
||||
Overview
|
||||
</h2>
|
||||
<SecurityIssuesGlance issuesFound={this.state.report.glance.exploited.length > 0}/>
|
||||
<SecurityIssuesGlance issuesFound={this.state.report.glance.exploited_cnt > 0}/>
|
||||
{
|
||||
this.state.report.glance.exploited.length > 0 ?
|
||||
this.state.report.glance.exploited_cnt > 0 ?
|
||||
''
|
||||
:
|
||||
<p className='alert alert-info'>
|
||||
|
@ -524,7 +525,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
|
||||
generateReportGlanceSection() {
|
||||
let exploitPercentage =
|
||||
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
|
||||
(100 * this.state.report.glance.exploited_cnt) / this.state.report.glance.scanned.length;
|
||||
return (
|
||||
<div id='glance'>
|
||||
<h3>
|
||||
|
@ -535,7 +536,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
The Monkey discovered <span
|
||||
className='badge badge-warning'>{this.state.report.glance.scanned.length}</span> machines and
|
||||
successfully breached <span
|
||||
className='badge badge-danger'>{this.state.report.glance.exploited.length}</span> of them.
|
||||
className='badge badge-danger'>{this.state.report.glance.exploited_cnt}</span> of them.
|
||||
</p>
|
||||
<div className='text-center' style={{margin: '10px'}}>
|
||||
<Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth='4'
|
||||
|
@ -566,7 +567,13 @@ class ReportPageComponent extends AuthComponent {
|
|||
</div>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||
<p>
|
||||
The Monkey successfully breached
|
||||
<span className="badge badge-danger">
|
||||
{this.state.report.glance.exploited_cnt}
|
||||
</span> {Pluralize('machine', this.state.report.glance.exploited_cnt)}:
|
||||
</p>
|
||||
<BreachedServers />
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React, {ReactElement} from 'react';
|
||||
import NumberedReportSection from './NumberedReportSection';
|
||||
import pluralize from 'pluralize'
|
||||
import BreachedServersComponent from '../security/BreachedServers';
|
||||
|
||||
const LATERAL_MOVEMENT_DESCRIPTION = 'After the initial breach, the attacker will begin the Lateral \
|
||||
Movement phase of the attack. They will employ various \
|
||||
techniques in order to compromise other systems in your \
|
||||
network and encrypt as many files as possible.'
|
||||
network.'
|
||||
|
||||
type PropagationStats = {
|
||||
num_scanned_nodes: number,
|
||||
|
@ -18,6 +19,8 @@ function LateralMovement({propagationStats}: {propagationStats: PropagationStats
|
|||
<>
|
||||
{getScannedVsExploitedStats(propagationStats.num_scanned_nodes, propagationStats.num_exploited_nodes)}
|
||||
{getExploitationStatsPerExploit(propagationStats.num_exploited_per_exploit)}
|
||||
<br/>
|
||||
<BreachedServersComponent />
|
||||
</>
|
||||
)
|
||||
|
||||
|
|
|
@ -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,54 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
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 (
|
||||
<>
|
||||
<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,24 @@
|
|||
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__2_exploits():
|
||||
exploits = get_exploits_used_on_node(NODE_DICT)
|
||||
assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"])
|
||||
|
||||
|
||||
def test_get_exploits_used_on_node__duplicate_exploits():
|
||||
exploits = get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS)
|
||||
assert exploits == ["Drupal Server Exploiter"]
|
||||
|
||||
|
||||
def test_get_exploits_used_on_node__failed():
|
||||
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