Merge pull request #1360 from guardicore/manual_executions_endpoint

Manual executions endpoint
This commit is contained in:
VakarisZ 2021-07-26 14:30:48 +03:00 committed by GitHub
commit d4d055ed95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 144 additions and 10 deletions

View File

@ -0,0 +1,3 @@
# Default time format used in the application, follows European standard.
# Example: 1992-03-04 10:32:05
DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

View File

@ -1,6 +1,7 @@
import json import json
import logging import logging
import platform import platform
from datetime import datetime
from pprint import pformat from pprint import pformat
from socket import gethostname from socket import gethostname
from urllib.parse import urljoin from urllib.parse import urljoin
@ -11,6 +12,7 @@ from requests.exceptions import ConnectionError
import infection_monkey.monkeyfs as monkeyfs import infection_monkey.monkeyfs as monkeyfs
import infection_monkey.tunnel as tunnel import infection_monkey.tunnel as tunnel
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
from common.common_consts.time_formats import DEFAULT_TIME_FORMAT
from common.common_consts.timeouts import ( from common.common_consts.timeouts import (
LONG_REQUEST_TIMEOUT, LONG_REQUEST_TIMEOUT,
MEDIUM_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT,
@ -60,6 +62,7 @@ class ControlClient(object):
"internet_access": has_internet_access, "internet_access": has_internet_access,
"config": WormConfiguration.as_dict(), "config": WormConfiguration.as_dict(),
"parent": parent, "parent": parent,
"launch_time": str(datetime.now().strftime(DEFAULT_TIME_FORMAT)),
} }
if ControlClient.proxies: if ControlClient.proxies:

View File

@ -24,6 +24,7 @@ from monkey_island.cc.resources.configuration_export import ConfigurationExport
from monkey_island.cc.resources.configuration_import import ConfigurationImport 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.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(ZeroTrustReport, "/api/report/zero-trust/<string:report_data>") api.add_resource(ZeroTrustReport, "/api/report/zero-trust/<string:report_data>")
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(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

@ -40,6 +40,7 @@ class Monkey(Document):
hostname = StringField() hostname = StringField()
internet_access = BooleanField() internet_access = BooleanField()
ip_addresses = ListField(StringField()) ip_addresses = ListField(StringField())
launch_time = StringField()
keepalive = DateTimeField() keepalive = DateTimeField()
modifytime = DateTimeField() modifytime = DateTimeField()
# TODO make "parent" an embedded document, so this can be removed and the schema explained ( # TODO make "parent" an embedded document, so this can be removed and the schema explained (

View File

@ -0,0 +1,13 @@
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
class ManualExploitation(flask_restful.Resource):
@jwt_required
def get(self):
manual_exploitations = [
exploitation.__dict__ for exploitation in get_manual_exploitations()
]
return {"manual_exploitations": manual_exploitations}

View File

@ -0,0 +1,31 @@
from dataclasses import dataclass
from typing import List
from monkey_island.cc.database import mongo
from monkey_island.cc.services.node import NodeService
@dataclass
class ManualExploitation:
hostname: str
ip_addresses: List[str]
start_time: str
def get_manual_exploitations() -> List[ManualExploitation]:
monkeys = get_manual_monkeys()
return [monkey_to_manual_exploitation(monkey) for monkey in monkeys]
def get_manual_monkeys():
return [
monkey for monkey in mongo.db.monkey.find({}) if NodeService.get_monkey_manual_run(monkey)
]
def monkey_to_manual_exploitation(monkey: dict) -> ManualExploitation:
return ManualExploitation(
hostname=monkey["hostname"],
ip_addresses=monkey["ip_addresses"],
start_time=monkey["launch_time"],
)

View File

@ -21,6 +21,7 @@ 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.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,
@ -553,12 +554,8 @@ class ReportService:
return None return None
@staticmethod @staticmethod
def get_manual_monkeys(): def get_manual_monkey_hostnames():
return [ return [monkey["hostname"] for monkey in get_manual_monkeys()]
monkey["hostname"]
for monkey in mongo.db.monkey.find({}, {"hostname": 1, "parent": 1, "guid": 1})
if NodeService.get_monkey_manual_run(monkey)
]
@staticmethod @staticmethod
def get_config_users(): def get_config_users():
@ -654,7 +651,7 @@ class ReportService:
exploited_nodes = ReportService.get_exploited() exploited_nodes = ReportService.get_exploited()
report = { report = {
"overview": { "overview": {
"manual_monkeys": ReportService.get_manual_monkeys(), "manual_monkeys": ReportService.get_manual_monkey_hostnames(),
"config_users": config_users, "config_users": config_users,
"config_passwords": config_passwords, "config_passwords": config_passwords,
"config_exploits": ReportService.get_config_exploits(), "config_exploits": ReportService.get_config_exploits(),

View File

@ -6,6 +6,7 @@ import FileEncryptionTable from './ransomware/FileEncryptionTable';
import LateralMovement from './ransomware/LateralMovement'; import LateralMovement from './ransomware/LateralMovement';
import '../../styles/pages/report/RansomwareReport.scss'; import '../../styles/pages/report/RansomwareReport.scss';
import BreachSection from './ransomware/BreachSection';
class RansomwareReport extends React.Component { class RansomwareReport extends React.Component {
@ -16,6 +17,7 @@ class RansomwareReport extends React.Component {
generateReportContent() { generateReportContent() {
return ( return (
<div> <div>
<BreachSection/>
<LateralMovement propagationStats={this.props.report.propagation_stats} /> <LateralMovement propagationStats={this.props.report.propagation_stats} />
<FileEncryptionTable tableData={this.props.report.encrypted_files_table} /> <FileEncryptionTable tableData={this.props.report.encrypted_files_table} />
</div> </div>

View File

@ -1,8 +1,36 @@
import React from 'react'; import React from 'react';
export let renderArray = function (val) { export let renderArray = function (val, className='') {
return <>{val.map(x => <div key={x}>{x}</div>)}</>; return <>{val.map(x => <div key={x} className={className}>{x}</div>)}</>;
}; };
export let renderIpAddresses = function (val) { export let renderIpAddresses = function (val) {
return <div>{renderArray(val.ip_addresses)} {(val.domain_name ? ' ('.concat(val.domain_name, ')') : '')} </div>; return <div>
{renderArray(val.ip_addresses, 'ip-address')} {(val.domain_name ? ' ('.concat(val.domain_name, ')') : '')}
</div>;
}; };
export let renderLimitedArray = function (array,
limit,
className='',
separator=',') {
let elements = [];
if(array.length < limit){
limit = array.length;
}
for(let i = 0; i < limit; i++){
let element = '';
if(i !== 0) {
element = (<>{separator} {array[i]}</>);
} else {
element = (<>{array[i]}</>);
}
elements.push(<div className={className} key={array[i]}>{element}</div>);
}
let remainder = array.length - limit;
if(remainder > 0){
elements.push(<div className={className} key={'remainder'}>
&nbsp;and {remainder} more
</div>);
}
return elements
}

View File

@ -0,0 +1,49 @@
import React, {useEffect, useState} from 'react';
import IslandHttpClient from '../../IslandHttpClient';
import NumberedReportSection from './NumberedReportSection';
import LoadingIcon from '../../ui-components/LoadingIcon';
import {renderLimitedArray} from '../common/RenderArrays';
function BreachSection() {
const [machines, setMachines] = useState(null);
let description = 'Ransomware attacks start after machines in the internal network get compromised. ' +
'The initial compromise was simulated by running Monkey Agents manually.';
useEffect(() => {
IslandHttpClient.get('/api/exploitations/manual')
.then(resp => setMachines(resp.body['manual_exploitations']));
}, []);
if(machines !== null){
let body = getBreachSectionBody(machines);
return (<NumberedReportSection index={1} title={'Breach'} description={description} body={body}/>)
} else {
return <LoadingIcon />
}
}
function getBreachSectionBody(machines) {
let machineList = [];
for(let i = 0; i < machines.length; i++){
machineList.push(getMachine(machines[i]));
}
return (
<div className={'ransomware-breach-section'}>
<p>Ransomware attack started from these machines on the network:</p>
<ul>
{machineList}
</ul>
</div>
)
}
function getMachine(machine) {
return (
<li key={machine['hostname']}>
<b>{machine['hostname']}</b>&nbsp;
({renderLimitedArray(machine['ip_addresses'], 2, 'ip-address')}) at <b>{machine['start_time']}</b>
</li>
)
}
export default BreachSection;

View File

@ -18,3 +18,7 @@
margin-top: .28em; margin-top: .28em;
margin-right: .5em; margin-right: .5em;
} }
.ransomware-breach-section .ip-address {
display: inline-block;
}

View File

@ -70,6 +70,7 @@ PbaResults # unused class (monkey/monkey_island/cc/models/pba_results.py:4)
internet_access # unused variable (monkey/monkey_island/cc/models/monkey.py:43) internet_access # unused variable (monkey/monkey_island/cc/models/monkey.py:43)
config_error # unused variable (monkey/monkey_island/cc/models/monkey.py:53) config_error # unused variable (monkey/monkey_island/cc/models/monkey.py:53)
pba_results # unused variable (monkey/monkey_island/cc/models/monkey.py:55) pba_results # unused variable (monkey/monkey_island/cc/models/monkey.py:55)
launch_time # unused variable (monkey/monkey_island/cc/models/monkey.py)
command_control_channel # unused variable (monkey/monkey_island/cc/models/monkey.py:58) command_control_channel # unused variable (monkey/monkey_island/cc/models/monkey.py:58)
meta # unused variable (monkey/monkey_island/cc/models/zero_trust/finding.py:37) meta # unused variable (monkey/monkey_island/cc/models/zero_trust/finding.py:37)
meta # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:34) meta # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:34)