From fe6a653f79b73bb171a490bc743a03f7da0918c6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 22 Jul 2019 12:13:54 +0300 Subject: [PATCH 1/3] 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 68aec8e336b51f8ffeb6dcde0ce0e2f424ef1b46 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 18:15:25 +0300 Subject: [PATCH 2/3] 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 b9a5ac1fe4b3822bb340e98a7b93df0fabf1191d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Aug 2019 14:04:45 +0300 Subject: [PATCH 3/3] 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