From 91ca828c721bcc974a843259f6ac1e83a801a88b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Jul 2021 11:20:48 +0300 Subject: [PATCH 1/6] Monkey: add launch time to the monkey collection Launch time is needed if we want to tell the user when exactly the exploit occurred/monkey got run --- monkey/common/common_consts/time_formats.py | 3 +++ monkey/infection_monkey/control.py | 3 +++ monkey/monkey_island/cc/models/monkey.py | 1 + vulture_allowlist.py | 1 + 4 files changed, 8 insertions(+) create mode 100644 monkey/common/common_consts/time_formats.py diff --git a/monkey/common/common_consts/time_formats.py b/monkey/common/common_consts/time_formats.py new file mode 100644 index 000000000..d150ce46e --- /dev/null +++ b/monkey/common/common_consts/time_formats.py @@ -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" diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index feff589c1..beac0f716 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -1,6 +1,7 @@ import json import logging import platform +from datetime import datetime from pprint import pformat from socket import gethostname from urllib.parse import urljoin @@ -11,6 +12,7 @@ from requests.exceptions import ConnectionError import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel 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 ( LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT, @@ -60,6 +62,7 @@ class ControlClient(object): "internet_access": has_internet_access, "config": WormConfiguration.as_dict(), "parent": parent, + "launch_time": str(datetime.now().strftime(DEFAULT_TIME_FORMAT)), } if ControlClient.proxies: diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index fc87c4605..70ca9fbf9 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -40,6 +40,7 @@ class Monkey(Document): hostname = StringField() internet_access = BooleanField() ip_addresses = ListField(StringField()) + launch_time = StringField() keepalive = DateTimeField() modifytime = DateTimeField() # TODO make "parent" an embedded document, so this can be removed and the schema explained ( diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 5a430dc6c..b39d61dd8 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -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) config_error # unused variable (monkey/monkey_island/cc/models/monkey.py:53) 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) 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) From 7360b3c4f807f834b73433f1e547a7071e15df23 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Jul 2021 11:23:38 +0300 Subject: [PATCH 2/6] Island: add an endpoint and service to get manual runs/exploitations --- monkey/monkey_island/cc/app.py | 2 ++ .../exploitations/manual_exploitation.py | 13 ++++++++ .../exploitations/manual_exploitation.py | 31 +++++++++++++++++++ .../cc/services/reporting/report.py | 11 +++---- 4 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/exploitations/manual_exploitation.py create mode 100644 monkey/monkey_island/cc/services/exploitations/manual_exploitation.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 817a43333..67640d352 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -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.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.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(ZeroTrustReport, "/api/report/zero-trust/") api.add_resource(AttackReport, "/api/report/attack") api.add_resource(RansomwareReport, "/api/report/ransomware") + api.add_resource(ManualExploitation, "/api/exploitations/manual") 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/manual_exploitation.py b/monkey/monkey_island/cc/resources/exploitations/manual_exploitation.py new file mode 100644 index 000000000..5754bd49f --- /dev/null +++ b/monkey/monkey_island/cc/resources/exploitations/manual_exploitation.py @@ -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} diff --git a/monkey/monkey_island/cc/services/exploitations/manual_exploitation.py b/monkey/monkey_island/cc/services/exploitations/manual_exploitation.py new file mode 100644 index 000000000..303fe8db5 --- /dev/null +++ b/monkey/monkey_island/cc/services/exploitations/manual_exploitation.py @@ -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"], + ) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 5a27b8933..20574c54f 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -21,6 +21,7 @@ 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.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 ExploiterDescriptorEnum, @@ -553,12 +554,8 @@ class ReportService: return None @staticmethod - def get_manual_monkeys(): - return [ - monkey["hostname"] - for monkey in mongo.db.monkey.find({}, {"hostname": 1, "parent": 1, "guid": 1}) - if NodeService.get_monkey_manual_run(monkey) - ] + def get_manual_monkey_hostnames(): + return [monkey["hostname"] for monkey in get_manual_monkeys()] @staticmethod def get_config_users(): @@ -654,7 +651,7 @@ class ReportService: exploited_nodes = ReportService.get_exploited() report = { "overview": { - "manual_monkeys": ReportService.get_manual_monkeys(), + "manual_monkeys": ReportService.get_manual_monkey_hostnames(), "config_users": config_users, "config_passwords": config_passwords, "config_exploits": ReportService.get_config_exploits(), From fcb52b82233875ee34f387b6ca8f6e7c75cad144 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Jul 2021 11:26:20 +0300 Subject: [PATCH 3/6] Island UI: alter ransomware report to contain a section describing manual executions --- .../report-components/RansomwareReport.js | 2 + .../report-components/common/RenderArrays.js | 32 ++++++++++-- .../ransomware/BreachSection.tsx | 49 +++++++++++++++++++ .../styles/pages/report/RansomwareReport.scss | 4 ++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index dd0cc2f53..3e7a96311 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -6,6 +6,7 @@ import FileEncryptionTable from './ransomware/FileEncryptionTable'; import LateralMovement from './ransomware/LateralMovement'; import '../../styles/pages/report/RansomwareReport.scss'; +import BreachSection from './ransomware/BreachSection'; class RansomwareReport extends React.Component { @@ -16,6 +17,7 @@ class RansomwareReport extends React.Component { generateReportContent() { return (
+
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js index 7bbef33bc..f06ee4849 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js @@ -1,8 +1,34 @@ import React from 'react'; -export let renderArray = function (val) { - return <>{val.map(x =>
{x}
)}; +export let renderArray = function (val, className='') { + return <>{val.map(x =>
{x}
)}; }; export let renderIpAddresses = function (val) { - return
{renderArray(val.ip_addresses)} {(val.domain_name ? ' ('.concat(val.domain_name, ')') : '')}
; + return
+ {renderArray(val.ip_addresses, 'ip-address')} {(val.domain_name ? ' ('.concat(val.domain_name, ')') : '')} +
; }; + +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(
{element}
); + } + let remainder = array.length - limit; + if(remainder > 0){ + elements.push(
 and {remainder} more
); + } + return elements +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx new file mode 100644 index 000000000..0cf23dfdd --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx @@ -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 monkeys manually.'; + + useEffect(() => { + IslandHttpClient.get('/api/exploitations/manual') + .then(resp => setMachines(resp.body['manual_exploitations'])); + }, []); + + if(machines !== null){ + let body = getBreachSectionBody(machines); + return () + } else { + return + } +} + +function getBreachSectionBody(machines) { + let machineList = []; + for(let i = 0; i < machines.length; i++){ + machineList.push(
  • {getMachine(machines[i])}
  • ); + } + return ( +
    +

    Ransomware attack started from these machines on the network:

    +
      + {machineList} +
    +
    + ) + } + +function getMachine(machine) { + return ( + <> + {machine['hostname']} + ({renderLimitedArray(machine['ip_addresses'], 2, 'ip-address')}) at {machine['start_time']} + + ) +} + +export default BreachSection; diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss index 0a3ea2bf9..143e3f835 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss @@ -18,3 +18,7 @@ margin-top: .28em; margin-right: .5em; } + +.ransomware-breach-section .ip-address { + display: inline-block; +} From e6a87839d6f07a7484ba482d4a998b8f68bf7758 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Jul 2021 11:45:29 +0300 Subject: [PATCH 4/6] Island UI: add keys to items in ransomware report, breach section --- .../src/components/report-components/common/RenderArrays.js | 6 ++++-- .../report-components/ransomware/BreachSection.tsx | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js index f06ee4849..692484f27 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderArrays.js @@ -24,11 +24,13 @@ export let renderLimitedArray = function (array, } else { element = (<>{array[i]}); } - elements.push(
    {element}
    ); + elements.push(
    {element}
    ); } let remainder = array.length - limit; if(remainder > 0){ - elements.push(
     and {remainder} more
    ); + elements.push(
    +  and {remainder} more +
    ); } return elements } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx index 0cf23dfdd..abf7eb277 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx @@ -25,7 +25,7 @@ function BreachSection() { function getBreachSectionBody(machines) { let machineList = []; for(let i = 0; i < machines.length; i++){ - machineList.push(
  • {getMachine(machines[i])}
  • ); + machineList.push(getMachine(machines[i])); } return (
    @@ -39,10 +39,10 @@ function getBreachSectionBody(machines) { function getMachine(machine) { return ( - <> +
  • {machine['hostname']} ({renderLimitedArray(machine['ip_addresses'], 2, 'ip-address')}) at {machine['start_time']} - +
  • ) } From a6374e06781d47b061728d59bdad88e41c858064 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Jul 2021 11:49:08 +0300 Subject: [PATCH 5/6] Island UI: replace double quotes with single quotes in BreachSection.tsx --- .../report-components/ransomware/BreachSection.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx index abf7eb277..5e217bb08 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx @@ -1,8 +1,8 @@ -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"; +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); From 2f40fc4e54390804d8f20ef9178601d245c0f089 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Jul 2021 14:22:53 +0300 Subject: [PATCH 6/6] Island UI: improved the UI/readability in BreachSection.tsx --- .../report-components/ransomware/BreachSection.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx index 5e217bb08..1c2b71d99 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/BreachSection.tsx @@ -7,7 +7,7 @@ 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 monkeys manually.'; + 'The initial compromise was simulated by running Monkey Agents manually.'; useEffect(() => { IslandHttpClient.get('/api/exploitations/manual') @@ -40,8 +40,8 @@ function getBreachSectionBody(machines) { function getMachine(machine) { return (
  • - {machine['hostname']} - ({renderLimitedArray(machine['ip_addresses'], 2, 'ip-address')}) at {machine['start_time']} + {machine['hostname']}  + ({renderLimitedArray(machine['ip_addresses'], 2, 'ip-address')}) at {machine['start_time']}
  • ) }