Merge pull request #1375 from guardicore/ransomware_exploits_table

Ransomware report: Exploits table in lateral movement section
This commit is contained in:
Mike Salvatore 2021-07-28 13:11:47 -04:00 committed by GitHub
commit fceb52ba38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 192 additions and 119 deletions

View File

@ -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.edge import Edge
from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.environment import Environment
from monkey_island.cc.resources.exploitations.manual_exploitation import ManualExploitation 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_configuration import IslandConfiguration
from monkey_island.cc.resources.island_logs import IslandLog from monkey_island.cc.resources.island_logs import IslandLog
from monkey_island.cc.resources.island_mode import IslandMode 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(AttackReport, "/api/report/attack")
api.add_resource(RansomwareReport, "/api/report/ransomware") api.add_resource(RansomwareReport, "/api/report/ransomware")
api.add_resource(ManualExploitation, "/api/exploitations/manual") 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(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/<string:finding_id>")
api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/")

View File

@ -1,7 +1,9 @@
import flask_restful import flask_restful
from monkey_island.cc.resources.auth.auth import jwt_required 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): class ManualExploitation(flask_restful.Resource):

View File

@ -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}

View File

@ -1,11 +1,15 @@
from typing import Dict, List 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 from monkey_island.cc.services.reporting.report import ReportService
def get_propagation_stats() -> Dict: def get_propagation_stats() -> Dict:
scanned = ReportService.get_scanned() scanned = ReportService.get_scanned()
exploited = ReportService.get_exploited() exploited = get_monkey_exploited()
return { return {
"num_scanned_nodes": len(scanned), "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 = {} exploit_counts = {}
for node in exploited: for node in exploited:
for exploit in node["exploits"]: for exploit in node.exploits:
exploit_counts[exploit] = exploit_counts.get(exploit, 0) + 1 exploit_counts[exploit] = exploit_counts.get(exploit, 0) + 1
return exploit_counts return exploit_counts

View File

@ -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"]
]
)
)

View File

@ -21,8 +21,11 @@ from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.configuration.utils import ( from monkey_island.cc.services.configuration.utils import (
get_config_network_segments_as_subnet_groups, 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.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 from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501
ExploiterDescriptorEnum, ExploiterDescriptorEnum,
) )
@ -148,47 +151,6 @@ class ReportService:
nodes = nodes_without_monkeys + nodes_with_monkeys nodes = nodes_without_monkeys + nodes_with_monkeys
return nodes 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 @staticmethod
def get_stolen_creds(): def get_stolen_creds():
creds = [] creds = []
@ -646,7 +608,7 @@ class ReportService:
monkey_latest_modify_time = Monkey.get_latest_modifytime() monkey_latest_modify_time = Monkey.get_latest_modifytime()
scanned_nodes = ReportService.get_scanned() scanned_nodes = ReportService.get_scanned()
exploited_nodes = ReportService.get_exploited() exploited_cnt = len(get_monkey_exploited())
report = { report = {
"overview": { "overview": {
"manual_monkeys": ReportService.get_manual_monkey_hostnames(), "manual_monkeys": ReportService.get_manual_monkey_hostnames(),
@ -664,7 +626,7 @@ class ReportService:
}, },
"glance": { "glance": {
"scanned": scanned_nodes, "scanned": scanned_nodes,
"exploited": exploited_nodes, "exploited_cnt": exploited_cnt,
"stolen_creds": ReportService.get_stolen_creds(), "stolen_creds": ReportService.get_stolen_creds(),
"azure_passwords": ReportService.get_azure_creds(), "azure_passwords": ReportService.get_azure_creds(),
"ssh_keys": ReportService.get_ssh_keys(), "ssh_keys": ReportService.get_ssh_keys(),

View File

@ -1,4 +1,5 @@
import React, {Fragment} from 'react'; import React, {Fragment} from 'react';
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 PostBreach from 'components/report-components/security/PostBreach';
@ -288,9 +289,9 @@ class ReportPageComponent extends AuthComponent {
<h2> <h2>
Overview Overview
</h2> </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'> <p className='alert alert-info'>
@ -524,7 +525,7 @@ class ReportPageComponent extends AuthComponent {
generateReportGlanceSection() { generateReportGlanceSection() {
let exploitPercentage = 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 ( return (
<div id='glance'> <div id='glance'>
<h3> <h3>
@ -535,7 +536,7 @@ class ReportPageComponent extends AuthComponent {
The Monkey discovered <span The Monkey discovered <span
className='badge badge-warning'>{this.state.report.glance.scanned.length}</span> machines and className='badge badge-warning'>{this.state.report.glance.scanned.length}</span> machines and
successfully breached <span 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> </p>
<div className='text-center' style={{margin: '10px'}}> <div className='text-center' style={{margin: '10px'}}>
<Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth='4' <Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth='4'
@ -566,7 +567,13 @@ class ReportPageComponent extends AuthComponent {
</div> </div>
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>
<BreachedServers data={this.state.report.glance.exploited}/> <p>
The Monkey successfully breached&nbsp;
<span className="badge badge-danger">
{this.state.report.glance.exploited_cnt}
</span> {Pluralize('machine', this.state.report.glance.exploited_cnt)}:
</p>
<BreachedServers />
</div> </div>
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>

View File

@ -1,11 +1,12 @@
import React, {ReactElement} from 'react'; import React, {ReactElement} from 'react';
import NumberedReportSection from './NumberedReportSection'; import NumberedReportSection from './NumberedReportSection';
import pluralize from 'pluralize' import pluralize from 'pluralize'
import BreachedServersComponent from '../security/BreachedServers';
const LATERAL_MOVEMENT_DESCRIPTION = 'After the initial breach, the attacker will begin the Lateral \ const LATERAL_MOVEMENT_DESCRIPTION = 'After the initial breach, the attacker will begin the Lateral \
Movement phase of the attack. They will employ various \ Movement phase of the attack. They will employ various \
techniques in order to compromise other systems in your \ techniques in order to compromise other systems in your \
network and encrypt as many files as possible.' network.'
type PropagationStats = { type PropagationStats = {
num_scanned_nodes: number, num_scanned_nodes: number,
@ -18,6 +19,8 @@ function LateralMovement({propagationStats}: {propagationStats: PropagationStats
<> <>
{getScannedVsExploitedStats(propagationStats.num_scanned_nodes, propagationStats.num_exploited_nodes)} {getScannedVsExploitedStats(propagationStats.num_scanned_nodes, propagationStats.num_exploited_nodes)}
{getExploitationStatsPerExploit(propagationStats.num_exploited_per_exploit)} {getExploitationStatsPerExploit(propagationStats.num_exploited_per_exploit)}
<br/>
<BreachedServersComponent />
</> </>
) )

View File

@ -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;

View File

@ -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;

View File

@ -1,6 +1,7 @@
import pytest import pytest
from monkey_island.cc.services.ransomware import ransomware_report 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 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): def patch_report_service_for_stats(monkeypatch):
TEST_SCANNED_RESULTS = [{}, {}, {}, {}] TEST_SCANNED_RESULTS = [{}, {}, {}, {}]
TEST_EXPLOITED_RESULTS = [ TEST_EXPLOITED_RESULTS = [
{"exploits": ["SSH Exploiter"]}, MonkeyExploitation("", [], "", exploits=["SSH Exploiter"]),
{"exploits": ["SSH Exploiter", "SMB Exploiter"]}, MonkeyExploitation("", [], "", exploits=["SSH Exploiter", "SMB Exploiter"]),
{"exploits": ["WMI Exploiter"]}, MonkeyExploitation("", [], "", exploits=["WMI Exploiter"]),
] ]
monkeypatch.setattr(ReportService, "get_scanned", lambda: TEST_SCANNED_RESULTS) 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): def test_get_propagation_stats__num_scanned(patch_report_service_for_stats):

View File

@ -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 == []

View File

@ -164,14 +164,3 @@ def test_get_stolen_creds_no_creds(fake_mongo):
expected_stolen_creds_no_creds = [] expected_stolen_creds_no_creds = []
assert expected_stolen_creds_no_creds == 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 == []