diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index 1ab524e64..f7bd43a6e 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -1,8 +1,11 @@ +import subprocess + from common.data.post_breach_consts import \ POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import \ get_commands_to_modify_shell_startup_files +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem class ModifyShellStartupFiles(PBA): @@ -12,34 +15,48 @@ class ModifyShellStartupFiles(PBA): and profile.ps1 in windows. """ + def __init__(self): + super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION) + def run(self): - [pba.run() for pba in self.modify_shell_startup_PBA_list()] + results = [pba.run() for pba in self.modify_shell_startup_PBA_list()] + PostBreachTelem(self, results).send() def modify_shell_startup_PBA_list(self): - return ShellStartupPBAGenerator.get_modify_shell_startup_pbas() + return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() + class ShellStartupPBAGenerator(): + def get_modify_shell_startup_pbas(self): + (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux),\ + (cmds_for_windows, shell_startup_files_per_user_for_windows) =\ + get_commands_to_modify_shell_startup_files() -class ShellStartupPBAGenerator(): - def get_modify_shell_startup_pbas(): - (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux),\ - (cmds_for_windows, shell_startup_files_per_user_for_windows) = get_commands_to_modify_shell_startup_files() + pbas = [] - pbas = [] + for startup_file_per_user in shell_startup_files_per_user_for_windows: + windows_cmds = ' '.join(cmds_for_windows).format(startup_file_per_user) + pbas.append(self.ModifyShellStartupFile(linux_cmds='', windows_cmds=['powershell.exe', windows_cmds])) - for startup_file_per_user in shell_startup_files_per_user_for_windows: - windows_cmds = ' '.join(cmds_for_windows).format(startup_file_per_user) - pbas.append(ModifyShellStartupFile(linux_cmds='', windows_cmds=['powershell.exe', windows_cmds])) + for username in usernames_for_linux: + for shell_startup_file in shell_startup_files_for_linux: + linux_cmds = ' '.join(cmds_for_linux).format(shell_startup_file).format(username) + pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds='')) - for username in usernames_for_linux: - for shell_startup_file in shell_startup_files_for_linux: - linux_cmds = ' '.join(cmds_for_linux).format(shell_startup_file).format(username) - pbas.append(ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds='')) + return pbas - return pbas + class ModifyShellStartupFile(PBA): + def __init__(self, linux_cmds, windows_cmds): + super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + linux_cmd=linux_cmds, + windows_cmd=windows_cmds) - -class ModifyShellStartupFile(PBA): - def __init__(self, linux_cmds, windows_cmds): - super(ModifyShellStartupFile, self).__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, - linux_cmd=linux_cmds, - windows_cmd=windows_cmds) + def run(self): + if self.command: + try: + output = subprocess.check_output(self.command, # noqa: DUO116 + stderr=subprocess.STDOUT, + shell=True).decode() + return output, True + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output.decode(), False diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index a6a89edf8..93d10d45e 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -13,8 +13,6 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" - class PBA(Plugin): """ @@ -90,8 +88,6 @@ class PBA(Plugin): """ try: output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True).decode() - if not output: - output = EXECUTION_WITHOUT_OUTPUT return output, True except subprocess.CalledProcessError as e: # Return error output of the command diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index c278d2f36..17f263320 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -98,9 +98,9 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - return '%s post breach action executed on %s (%s) machine.' % (telem['data']['name'], - telem['data']['hostname'], - telem['data']['ip']) + return '%s post breach action executed on %s (%s) machine.' % (telem['data'][0]['name'], + telem['data'][0]['hostname'], + telem['data'][0]['ip']) @staticmethod def should_show_brief(telem): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py index f09b70391..2841ed0ad 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py @@ -12,3 +12,15 @@ class T1156(PostBreachTechnique): scanned_msg = "Monkey tried modifying bash startup files but failed." used_msg = "Monkey successfully modified bash startup files." pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION] + + @staticmethod + def get_pba_query(*args): + return [{'$match': {'telem_category': 'post_breach', + 'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}}, + {'$project': {'_id': 0, + 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]}, + 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]}, + 'result': '$data.result'}}, + {'$unwind': '$result'}, + {'$match': {'$or': [{'result': {'$regex': r'\.bash'}}, + {'result': {'$regex': r'\.profile'}}]}}] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py index db1ea8aa5..8d8956e6b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py @@ -12,3 +12,14 @@ class T1504(PostBreachTechnique): scanned_msg = "Monkey tried modifying powershell startup files but failed." used_msg = "Monkey successfully modified powershell startup files." pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION] + + @staticmethod + def get_pba_query(*args): + return [{'$match': {'telem_category': 'post_breach', + 'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}}, + {'$project': {'_id': 0, + 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]}, + 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]}, + 'result': '$data.result'}}, + {'$unwind': '$result'}, + {'$match': {'result': {'$regex': r'profile\.ps1'}}}] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py index 72188eb4e..da475c697 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py @@ -3,7 +3,6 @@ from typing import List from common.utils.attack_utils import ScanStatus from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.attack.technique_reports import AttackTechnique diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 974a696d5..fb54522b6 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,9 +1,13 @@ +import copy + from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import \ test_new_user_communication +EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" + def process_communicate_as_new_user_telemetry(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) @@ -18,10 +22,36 @@ POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { def process_post_breach_telemetry(telemetry_json): - mongo.db.monkey.update( - {'guid': telemetry_json['monkey_guid']}, - {'$push': {'pba_results': telemetry_json['data']}}) + def convert_telem_data_to_list(data): + modified_data = [data] + if type(data['result'][0]) is list: # multiple results in one pba + modified_data = separate_results_to_single_pba_telems(data) + return modified_data + + def separate_results_to_single_pba_telems(data): + modified_data = [] + for result in data['result']: + temp = copy.deepcopy(data) + temp['result'] = result + modified_data.append(temp) + return modified_data + + def update_data(data): + data = add_message_for_blank_outputs(data) + mongo.db.monkey.update( + {'guid': telemetry_json['monkey_guid']}, + {'$push': {'pba_results': data}}) + + def add_message_for_blank_outputs(data): + if not data['result'][0]: + data['result'][0] = EXECUTION_WITHOUT_OUTPUT + return data post_breach_action_name = telemetry_json["data"]["name"] if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json) + + telemetry_json['data'] = convert_telem_data_to_list(telemetry_json['data']) + + for pba_data in telemetry_json['data']: + update_data(pba_data) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js index 390ad6189..841c50c4d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactTable from 'react-table'; import Pluralize from 'pluralize'; import {renderIpAddresses} from '../common/RenderArrays'; +import parsePbaResults from './PostBreachParser'; let renderMachine = function (data) { return
{data.label} ( {renderIpAddresses(data)} )
@@ -54,6 +55,7 @@ class PostBreachComponent extends React.Component { let pbaMachines = this.props.data.filter(function (value) { return (value.pba_results !== 'None' && value.pba_results.length > 0); }); + pbaMachines = pbaMachines.map(pbaData => parsePbaResults(pbaData)); let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length; let showPagination = pbaMachines > pageSize; const pbaCount = pbaMachines.reduce((accumulated, pbaMachine) => accumulated+pbaMachine['pba_results'].length, 0); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js new file mode 100644 index 000000000..39dfecf7e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js @@ -0,0 +1,33 @@ +export default function parsePbaResults(results) { + results.pba_results = aggregateShellStartupPba(results.pba_results); + return results; +} + +const SHELL_STARTUP_NAME = 'Modify shell startup file'; + +function aggregateShellStartupPba(results) { + let isSuccess = false; + let aggregatedPbaResult = undefined; + let successfulOutputs = ''; + let failedOutputs = ''; + + for(let i = 0; i < results.length; i++){ + if(results[i].name === SHELL_STARTUP_NAME && aggregatedPbaResult === undefined){ + aggregatedPbaResult = results[i]; + } + if(results[i].name === SHELL_STARTUP_NAME && results[i].result[1]){ + successfulOutputs += results[i].result[0]; + isSuccess = true; + } + if(results[i].name === SHELL_STARTUP_NAME && ! results[i].result[1]){ + failedOutputs += results[i].result[0]; + } + } + if(aggregatedPbaResult === undefined) return; + + results = results.filter(result => result.name !== SHELL_STARTUP_NAME); + aggregatedPbaResult.result[0] = successfulOutputs + failedOutputs; + aggregatedPbaResult.result[1] = isSuccess; + results.push(aggregatedPbaResult); + return results; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss index 0c618bd7f..5fb8252fe 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss @@ -60,10 +60,12 @@ .pba-danger { background-color: #ffc7af; + white-space: pre-wrap; } .pba-success { background-color: #afd2a2; + white-space: pre-wrap; } div.report-wrapper {