diff --git a/monkey/common/utils/attack_status_enum.py b/monkey/common/utils/attack_utils.py similarity index 72% rename from monkey/common/utils/attack_status_enum.py rename to monkey/common/utils/attack_utils.py index c7d2dc62c..50271c132 100644 --- a/monkey/common/utils/attack_status_enum.py +++ b/monkey/common/utils/attack_utils.py @@ -8,3 +8,6 @@ class ScanStatus(Enum): SCANNED = 1 # Technique was attempted and succeeded USED = 2 + + +BITS_UPLOAD_STRING = "Bits job was used to upload monkey to a remote system." diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index faa6681b4..1c530653e 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -11,6 +11,8 @@ from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\ DOWNLOAD_TIMEOUT from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING import re @@ -58,6 +60,12 @@ class ElasticGroovyExploiter(WebRCE): return False return result[0] + def upload_monkey(self, url, commands=None): + result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands) + if 'windows' in self.host.os['type'] and result: + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) + return result + def get_results(self, response): """ Extracts the result data from our attack diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index dcef9551c..3b81f7109 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -17,6 +17,8 @@ from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.utils import utf_to_ascii from common.utils.exploit_enum import ExploitType +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'hoffer' @@ -312,6 +314,7 @@ class RdpExploiter(HostExploiter): client_factory.done_event.wait() if client_factory.success: + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) exploited = True self.report_login_attempt(True, user, password) break diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index d797f3a95..912d7c108 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -7,6 +7,8 @@ from infection_monkey.exploit import HostExploiter from infection_monkey.model import * from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'VakarisZ' @@ -307,6 +309,7 @@ class WebRCE(HostExploiter): if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: LOG.info("Powershell not found in host. Using bitsadmin to download.") backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path} + VictimHostTelem("T1197", ScanStatus.USED.value, self.host, BITS_UPLOAD_STRING) resp = self.exploit(url, backup_command) return resp diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 841a5521d..e80e15396 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,8 +17,6 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from common.utils.attack_status_enum import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' @@ -181,11 +179,9 @@ class InfectionMonkey(object): for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True - VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/monkey_island/cc/resources/attack/__init__.py b/monkey/monkey_island/cc/resources/attack/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py similarity index 100% rename from monkey/monkey_island/cc/resources/attack_config.py rename to monkey/monkey_island/cc/resources/attack/attack_config.py diff --git a/monkey/monkey_island/cc/resources/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py similarity index 69% rename from monkey/monkey_island/cc/resources/attack_report.py rename to monkey/monkey_island/cc/resources/attack/attack_report.py index 82987dde9..714836925 100644 --- a/monkey/monkey_island/cc/resources/attack_report.py +++ b/monkey/monkey_island/cc/resources/attack/attack_report.py @@ -3,11 +3,11 @@ from flask import jsonify from cc.auth import jwt_required from cc.services.attack.attack_report import AttackReportService -__author__ = "itay.mizeretz" +__author__ = "VakarisZ" class AttackReport(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(AttackReportService.get_report()) + return jsonify(AttackReportService.get_latest_report()['techniques']) diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack/attack_telem.py similarity index 100% rename from monkey/monkey_island/cc/resources/attack_telem.py rename to monkey/monkey_island/cc/resources/attack/attack_telem.py diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index b6d2af7dc..4bc43c601 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -69,10 +69,12 @@ class Root(flask_restful.Resource): infection_done = NodeService.is_monkey_finished_running() if not infection_done: report_done = False + attack_report_done = False else: if is_any_exists: ReportService.get_report() - AttackReportService.get_report() + AttackReportService.get_latest_report() report_done = ReportService.is_report_generated() attack_report_done = AttackReportService.is_report_generated() - return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) + return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, + report_done=report_done, attack_report_done=attack_report_done) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 591255fa5..52d4d6529 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,37 +1,58 @@ import logging from cc.services.attack.technique_reports import T1210 from cc.services.attack.attack_telem import get_latest_telem +from cc.services.attack.attack_config import get_technique_values from cc.database import mongo __author__ = "VakarisZ" -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) TECHNIQUES = {'T1210': T1210} +REPORT_NAME = 'new_report' + + class AttackReportService: def __init__(self): pass @staticmethod def generate_new_report(): - report = {'techniques': {}, 'meta': {get_latest_telem()}} - for tech_id, value in - report.update({'T1210': T1210.get_report_data()}) - report.update({''}) + """ + Generates new report based on telemetries, replaces old report in db with new one. + :return: Report object + """ + report = {'techniques': {}, 'meta': get_latest_telem(), 'name': REPORT_NAME} + for tech_id, value in get_technique_values().items(): + if value: + try: + report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()}) + except KeyError as e: + LOG.error("Attack technique does not have it's report component added " + "to attack report service. %s" % e) + mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True) return report @staticmethod def get_latest_report(): + """ + Gets latest report (by retrieving it from db or generating a new one). + :return: report dict. + """ if AttackReportService.is_report_generated(): - telem_time = get_latest_telem_time() - lates_report = mongo.db.attack_report.find_one({'name': 'new_report'}) - if telem_time == lates_report['telem_time']: - return lates_report + telem_time = get_latest_telem() + latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) + if telem_time and telem_time['timestamp'] == latest_report['meta']['timestamp']: + return latest_report return AttackReportService.generate_new_report() @staticmethod def is_report_generated(): + """ + Checks if report is generated + :return: True if report exists, False otherwise + """ generated_report = mongo.db.attack_report.find_one({}) return generated_report is not None diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py index 7521bbb6c..139837835 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -22,4 +22,4 @@ def set_results(technique, data): def get_latest_telem(): - return mongo.db.attack_results.find({'name': 'latest'}) + return mongo.db.attack_results.find_one({'name': 'latest'}) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py new file mode 100644 index 000000000..9d260bc45 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -0,0 +1,16 @@ +from monkey_island.cc.services.attack.technique_reports.technique_service import * +from cc.services.report import ReportService + +__author__ = "VakarisZ" + +TECHNIQUE = "T1197" +MESSAGES = { + 'unscanned': "Monkey didn't try to use any bits jobs.", + 'scanned': "Monkey tried to use bits jobs but failed.", + 'used': "Monkey successfully used bits jobs at least once in the network." +} + + +def get_report_data(): + data = get_tech_base_data(TECHNIQUE, MESSAGES) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 20cdcc9f3..4fb244e45 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -1,27 +1,18 @@ -from monkey_island.cc.services.attack.technique_reports.technique_service import technique_status, technique_title -from common.utils.attack_status_enum import ScanStatus +from monkey_island.cc.services.attack.technique_reports.technique_service import * from cc.services.report import ReportService __author__ = "VakarisZ" TECHNIQUE = "T1210" -UNSCANNED_MSG = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?" -SCANNED_MSG = "Monkey scanned for remote services on the network, but couldn't exploit any of them." -USED_MSG = "Monkey scanned for remote services and exploited some on the network." +MESSAGES = { + 'unscanned': "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?", + 'scanned': "Monkey scanned for remote services on the network, but couldn't exploit any of them.", + 'used': "Monkey scanned for remote services and exploited some on the network." +} def get_report_data(): - data = {} - status = technique_status(TECHNIQUE) - title = technique_title(TECHNIQUE) - data.update({'status': status.name, 'title': title}) - if status == ScanStatus.UNSCANNED: - data.update({'message': UNSCANNED_MSG}) - return data - elif status == ScanStatus.SCANNED: - data.update({'message': SCANNED_MSG}) - else: - data.update({'message': USED_MSG}) + data = get_tech_base_data(TECHNIQUE, MESSAGES) data.update({'scanned_machines': ReportService.get_scanned()}) data.update({'exploited_machines': ReportService.get_exploited()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py index 9533ae0b9..b59c1838d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_service.py @@ -1,5 +1,5 @@ from cc.database import mongo -from common.utils.attack_status_enum import ScanStatus +from common.utils.attack_utils import ScanStatus from cc.services.attack.attack_config import get_technique __author__ = "VakarisZ" @@ -16,3 +16,17 @@ def technique_status(technique): def technique_title(technique): return get_technique(technique)['title'] + + +def get_tech_base_data(technique, messages): + data = {} + status = technique_status(technique) + title = technique_title(technique) + data.update({'status': status.name, 'title': title}) + if status == ScanStatus.UNSCANNED: + data.update({'message': messages['unscanned']}) + elif status == ScanStatus.SCANNED: + data.update({'message': messages['scanned']}) + else: + data.update({'message': messages['used']}) + return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1197.js b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js new file mode 100644 index 000000000..a5156c3f4 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1197.js @@ -0,0 +1,63 @@ +import React from 'react'; +import '../../styles/Collapse.scss' +import {Link} from "react-router-dom"; + +let renderArray = function(val) { + return {val.map(x => {x} )}; +}; + + +let renderMachine = function (val, index, exploited=false) { + return ( +
+ {renderArray(val.ip_addresses)} + {(val.domain_name ? " (".concat(val.domain_name, ")") : " (".concat(val.label, ")"))} : + {exploited ? renderArray(val.exploits) : renderArray(val.services)} +
+ ) +}; + +class T1210 extends React.Component { + + renderScannedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].services.length !== 0){ + content.push(renderMachine(machines[i], i)) + } + } + return
{content}
; + }; + + renderExploitedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].exploits.length !== 0){ + content.push(renderMachine(machines[i], i, true)) + } + } + return
{content}
; + }; + + constructor(props) { + super(props); + } + + render() { + console.log(this.props); + return ( +
+
{this.props.data.message}
+
Found services:
+ {this.renderScannedMachines(this.props.data.scanned_machines)} +
Successful exploiters:
+ {this.renderExploitedMachines(this.props.data.exploited_machines)} +
+ To get more info about scanned and exploited machines view standard report. +
+
+ ); + } +} + +export default T1210; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js index ed36aa75d..a5156c3f4 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/T1210.js @@ -1,30 +1,61 @@ import React from 'react'; -import ReactTable from 'react-table' import '../../styles/Collapse.scss' -import Collapse from '@kunukn/react-collapse'; +import {Link} from "react-router-dom"; let renderArray = function(val) { - return
{val.map(x =>
{x}
)}
; + return {val.map(x => {x} )}; }; -let renderIpAddresses = function (val) { - return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; + +let renderMachine = function (val, index, exploited=false) { + return ( +
+ {renderArray(val.ip_addresses)} + {(val.domain_name ? " (".concat(val.domain_name, ")") : " (".concat(val.label, ")"))} : + {exploited ? renderArray(val.exploits) : renderArray(val.services)} +
+ ) }; -const columns = [ -]; - -const pageSize = 10; - class T1210 extends React.Component { + + renderScannedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].services.length !== 0){ + content.push(renderMachine(machines[i], i)) + } + } + return
{content}
; + }; + + renderExploitedMachines = (machines) => { + let content = []; + for (let i = 0; i < machines.length; i++ ){ + if (machines[i].exploits.length !== 0){ + content.push(renderMachine(machines[i], i, true)) + } + } + return
{content}
; + }; + constructor(props) { super(props); } + render() { + console.log(this.props); return ( - +
{this.props.data.message}
- +
Found services:
+ {this.renderScannedMachines(this.props.data.scanned_machines)} +
Successful exploiters:
+ {this.renderExploitedMachines(this.props.data.exploited_machines)} +
+ To get more info about scanned and exploited machines view standard report. +
+
); } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js index b34e45dde..41ff2a428 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackReportPage.js @@ -18,7 +18,7 @@ class AttackReportPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { - report: {}, + report: false, allMonkeysAreDead: false, runStarted: true, index: 1 @@ -105,27 +105,8 @@ class AttackReportPageComponent extends AuthComponent { Object.keys(this.state.report).forEach((tech_id) => { content = this.getTechniqueCollapse(tech_id) }); - return
{content}
- } - - render() { - let content; - if (Object.keys(this.state.report).length === 0) { - if (this.state.runStarted) { - content = (

Generating Report...

); - } else { - content = -

- - You have to run a monkey before generating a report! -

; - } - } else { - content = this.generateReportContent(); - } return ( - -

5. ATT&CK Report

+
+
{content}
+
+ ) + } + + render() { + let content; + console.log(this.state.report); + if (this.state.report === false){ + content = (

Generating Report...

); + } else if (Object.keys(this.state.report).length === 0) { + if (this.state.runStarted) { + content = (

No techniques were scanned

); + } else { + content = +

+ + You have to run a monkey before generating a report! +

; + } + } else { + content = this.generateReportContent(); + } + return ( + +

5. ATT&CK Report

{content}
diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 83a844670..ade81039e 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -541,3 +541,9 @@ body { text-align: center; margin-bottom: 20px; } + +.attack-report.footer-text{ + text-align: right; + font-size: 0.8em; + margin-top: 20px; +}