forked from p15670423/monkey
Merge pull request #731 from shreyamalviya/modifications-T1156-T1504
Telemetry modifications for "modify shell startup files" PBA
This commit is contained in:
commit
7d369f7399
|
@ -1,8 +1,11 @@
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from common.data.post_breach_consts import \
|
from common.data.post_breach_consts import \
|
||||||
POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
|
POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
|
||||||
from infection_monkey.post_breach.pba import PBA
|
from infection_monkey.post_breach.pba import PBA
|
||||||
from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import \
|
from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import \
|
||||||
get_commands_to_modify_shell_startup_files
|
get_commands_to_modify_shell_startup_files
|
||||||
|
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
|
||||||
|
|
||||||
|
|
||||||
class ModifyShellStartupFiles(PBA):
|
class ModifyShellStartupFiles(PBA):
|
||||||
|
@ -12,34 +15,48 @@ class ModifyShellStartupFiles(PBA):
|
||||||
and profile.ps1 in windows.
|
and profile.ps1 in windows.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION)
|
||||||
|
|
||||||
def run(self):
|
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):
|
def modify_shell_startup_PBA_list(self):
|
||||||
return ShellStartupPBAGenerator.get_modify_shell_startup_pbas()
|
return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas()
|
||||||
|
|
||||||
|
|
||||||
class ShellStartupPBAGenerator():
|
class ShellStartupPBAGenerator():
|
||||||
def get_modify_shell_startup_pbas():
|
def get_modify_shell_startup_pbas(self):
|
||||||
(cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux),\
|
(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()
|
(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:
|
for startup_file_per_user in shell_startup_files_per_user_for_windows:
|
||||||
windows_cmds = ' '.join(cmds_for_windows).format(startup_file_per_user)
|
windows_cmds = ' '.join(cmds_for_windows).format(startup_file_per_user)
|
||||||
pbas.append(ModifyShellStartupFile(linux_cmds='', windows_cmds=['powershell.exe', windows_cmds]))
|
pbas.append(self.ModifyShellStartupFile(linux_cmds='', windows_cmds=['powershell.exe', windows_cmds]))
|
||||||
|
|
||||||
for username in usernames_for_linux:
|
for username in usernames_for_linux:
|
||||||
for shell_startup_file in shell_startup_files_for_linux:
|
for shell_startup_file in shell_startup_files_for_linux:
|
||||||
linux_cmds = ' '.join(cmds_for_linux).format(shell_startup_file).format(username)
|
linux_cmds = ' '.join(cmds_for_linux).format(shell_startup_file).format(username)
|
||||||
pbas.append(ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds=''))
|
pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds=''))
|
||||||
|
|
||||||
return pbas
|
return pbas
|
||||||
|
|
||||||
|
|
||||||
class ModifyShellStartupFile(PBA):
|
class ModifyShellStartupFile(PBA):
|
||||||
def __init__(self, linux_cmds, windows_cmds):
|
def __init__(self, linux_cmds, windows_cmds):
|
||||||
super(ModifyShellStartupFile, self).__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
|
super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
|
||||||
linux_cmd=linux_cmds,
|
linux_cmd=linux_cmds,
|
||||||
windows_cmd=windows_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
|
||||||
|
|
|
@ -13,8 +13,6 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
__author__ = 'VakarisZ'
|
__author__ = 'VakarisZ'
|
||||||
|
|
||||||
EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
|
|
||||||
|
|
||||||
|
|
||||||
class PBA(Plugin):
|
class PBA(Plugin):
|
||||||
"""
|
"""
|
||||||
|
@ -90,8 +88,6 @@ class PBA(Plugin):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True).decode()
|
output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True).decode()
|
||||||
if not output:
|
|
||||||
output = EXECUTION_WITHOUT_OUTPUT
|
|
||||||
return output, True
|
return output, True
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# Return error output of the command
|
# Return error output of the command
|
||||||
|
|
|
@ -98,9 +98,9 @@ class TelemetryFeed(flask_restful.Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_post_breach_telem_brief(telem):
|
def get_post_breach_telem_brief(telem):
|
||||||
return '%s post breach action executed on %s (%s) machine.' % (telem['data']['name'],
|
return '%s post breach action executed on %s (%s) machine.' % (telem['data'][0]['name'],
|
||||||
telem['data']['hostname'],
|
telem['data'][0]['hostname'],
|
||||||
telem['data']['ip'])
|
telem['data'][0]['ip'])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_show_brief(telem):
|
def should_show_brief(telem):
|
||||||
|
|
|
@ -12,3 +12,15 @@ class T1156(PostBreachTechnique):
|
||||||
scanned_msg = "Monkey tried modifying bash startup files but failed."
|
scanned_msg = "Monkey tried modifying bash startup files but failed."
|
||||||
used_msg = "Monkey successfully modified bash startup files."
|
used_msg = "Monkey successfully modified bash startup files."
|
||||||
pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION]
|
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'}}]}}]
|
||||||
|
|
|
@ -12,3 +12,14 @@ class T1504(PostBreachTechnique):
|
||||||
scanned_msg = "Monkey tried modifying powershell startup files but failed."
|
scanned_msg = "Monkey tried modifying powershell startup files but failed."
|
||||||
used_msg = "Monkey successfully modified powershell startup files."
|
used_msg = "Monkey successfully modified powershell startup files."
|
||||||
pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION]
|
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'}}}]
|
||||||
|
|
|
@ -3,7 +3,6 @@ from typing import List
|
||||||
|
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from monkey_island.cc.database import mongo
|
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
|
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
import copy
|
||||||
|
|
||||||
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
|
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.models import Monkey
|
from monkey_island.cc.models import Monkey
|
||||||
from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import \
|
from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import \
|
||||||
test_new_user_communication
|
test_new_user_communication
|
||||||
|
|
||||||
|
EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
|
||||||
|
|
||||||
|
|
||||||
def process_communicate_as_new_user_telemetry(telemetry_json):
|
def process_communicate_as_new_user_telemetry(telemetry_json):
|
||||||
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
|
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
|
||||||
|
@ -18,10 +22,37 @@ POST_BREACH_TELEMETRY_PROCESSING_FUNCS = {
|
||||||
|
|
||||||
|
|
||||||
def process_post_breach_telemetry(telemetry_json):
|
def process_post_breach_telemetry(telemetry_json):
|
||||||
mongo.db.monkey.update(
|
def convert_telem_data_to_list(data):
|
||||||
{'guid': telemetry_json['monkey_guid']},
|
modified_data = [data]
|
||||||
{'$push': {'pba_results': telemetry_json['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 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"]
|
post_breach_action_name = telemetry_json["data"]["name"]
|
||||||
if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
|
if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
|
||||||
POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json)
|
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']:
|
||||||
|
pba_data = add_message_for_blank_outputs(pba_data)
|
||||||
|
update_data(telemetry_json, pba_data)
|
||||||
|
|
||||||
|
|
||||||
|
def update_data(telemetry_json, data):
|
||||||
|
mongo.db.monkey.update(
|
||||||
|
{'guid': telemetry_json['monkey_guid']},
|
||||||
|
{'$push': {'pba_results': data}})
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import monkey_island.cc.services.telemetry.processing.post_breach as post_breach
|
||||||
|
|
||||||
|
from .post_breach import EXECUTION_WITHOUT_OUTPUT
|
||||||
|
|
||||||
|
original_telem_multiple_results =\
|
||||||
|
{
|
||||||
|
'data': {
|
||||||
|
'command': 'COMMAND',
|
||||||
|
'hostname': 'HOST',
|
||||||
|
'ip': '127.0.1.1',
|
||||||
|
'name': 'PBA NAME',
|
||||||
|
'result': [
|
||||||
|
['SUCCESSFUL', True],
|
||||||
|
['UNSUCCESFUL', False],
|
||||||
|
['', True]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'telem_category': 'post_breach'
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_telem_multiple_results =\
|
||||||
|
{
|
||||||
|
'data': [
|
||||||
|
{
|
||||||
|
'command': 'COMMAND',
|
||||||
|
'hostname': 'HOST',
|
||||||
|
'ip': '127.0.1.1',
|
||||||
|
'name': 'PBA NAME',
|
||||||
|
'result': ['SUCCESSFUL', True]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'command': 'COMMAND',
|
||||||
|
'hostname': 'HOST',
|
||||||
|
'ip': '127.0.1.1',
|
||||||
|
'name': 'PBA NAME',
|
||||||
|
'result': ['UNSUCCESFUL', False]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'command': 'COMMAND',
|
||||||
|
'hostname': 'HOST',
|
||||||
|
'ip': '127.0.1.1',
|
||||||
|
'name': 'PBA NAME',
|
||||||
|
'result': [EXECUTION_WITHOUT_OUTPUT, True]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'telem_category': 'post_breach'
|
||||||
|
}
|
||||||
|
|
||||||
|
original_telem_single_result =\
|
||||||
|
{
|
||||||
|
'data': {
|
||||||
|
'command': 'COMMAND',
|
||||||
|
'hostname': 'HOST',
|
||||||
|
'ip': '127.0.1.1',
|
||||||
|
'name': 'PBA NAME',
|
||||||
|
'result': ['', True]
|
||||||
|
},
|
||||||
|
'telem_category': 'post_breach'
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_telem_single_result =\
|
||||||
|
{
|
||||||
|
'data': [
|
||||||
|
{
|
||||||
|
'command': 'COMMAND',
|
||||||
|
'hostname': 'HOST',
|
||||||
|
'ip': '127.0.1.1',
|
||||||
|
'name': 'PBA NAME',
|
||||||
|
'result': [EXECUTION_WITHOUT_OUTPUT, True]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'telem_category': 'post_breach'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_post_breach_telemetry():
|
||||||
|
post_breach.update_data = Mock() # actual behavior of update_data() is to access mongodb
|
||||||
|
# multiple results in PBA
|
||||||
|
post_breach.process_post_breach_telemetry(original_telem_multiple_results)
|
||||||
|
assert original_telem_multiple_results == expected_telem_multiple_results
|
||||||
|
# single result in PBA
|
||||||
|
post_breach.process_post_breach_telemetry(original_telem_single_result)
|
||||||
|
assert original_telem_single_result == expected_telem_single_result
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import Pluralize from 'pluralize';
|
import Pluralize from 'pluralize';
|
||||||
import {renderIpAddresses} from '../common/RenderArrays';
|
import {renderIpAddresses} from '../common/RenderArrays';
|
||||||
|
import parsePbaResults from './PostBreachParser';
|
||||||
|
|
||||||
let renderMachine = function (data) {
|
let renderMachine = function (data) {
|
||||||
return <div>{data.label} ( {renderIpAddresses(data)} )</div>
|
return <div>{data.label} ( {renderIpAddresses(data)} )</div>
|
||||||
|
@ -54,6 +55,7 @@ class PostBreachComponent extends React.Component {
|
||||||
let pbaMachines = this.props.data.filter(function (value) {
|
let pbaMachines = this.props.data.filter(function (value) {
|
||||||
return (value.pba_results !== 'None' && value.pba_results.length > 0);
|
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 defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length;
|
||||||
let showPagination = pbaMachines > pageSize;
|
let showPagination = pbaMachines > pageSize;
|
||||||
const pbaCount = pbaMachines.reduce((accumulated, pbaMachine) => accumulated+pbaMachine['pba_results'].length, 0);
|
const pbaCount = pbaMachines.reduce((accumulated, pbaMachine) => accumulated+pbaMachine['pba_results'].length, 0);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -60,10 +60,12 @@
|
||||||
|
|
||||||
.pba-danger {
|
.pba-danger {
|
||||||
background-color: #ffc7af;
|
background-color: #ffc7af;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pba-success {
|
.pba-success {
|
||||||
background-color: #afd2a2;
|
background-color: #afd2a2;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.report-wrapper {
|
div.report-wrapper {
|
||||||
|
|
Loading…
Reference in New Issue