Merge pull request #799 from shreyamalviya/T1146

Add T1146 attack technique (clear command history)
This commit is contained in:
Shreya Malviya 2020-08-27 10:25:23 +05:30 committed by GitHub
commit 2ebbd24b61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 259 additions and 25 deletions

View File

@ -6,3 +6,4 @@ POST_BREACH_HIDDEN_FILES = "Hide files and directories"
POST_BREACH_TRAP_COMMAND = "Execute command when a particular signal is received" POST_BREACH_TRAP_COMMAND = "Execute command when a particular signal is received"
POST_BREACH_SETUID_SETGID = "Setuid and Setgid" POST_BREACH_SETUID_SETGID = "Setuid and Setgid"
POST_BREACH_JOB_SCHEDULING = "Schedule jobs" POST_BREACH_JOB_SCHEDULING = "Schedule jobs"
POST_BREACH_CLEAR_CMD_HISTORY = "Clear command history"

View File

@ -0,0 +1,50 @@
import subprocess
from common.data.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY
from infection_monkey.post_breach.clear_command_history.clear_command_history import \
get_commands_to_clear_command_history
from infection_monkey.post_breach.pba import PBA
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
class ClearCommandHistory(PBA):
def __init__(self):
super().__init__(name=POST_BREACH_CLEAR_CMD_HISTORY)
def run(self):
results = [pba.run() for pba in self.clear_command_history_PBA_list()]
if results:
PostBreachTelem(self, results).send()
def clear_command_history_PBA_list(self):
return self.CommandHistoryPBAGenerator().get_clear_command_history_pbas()
class CommandHistoryPBAGenerator():
def get_clear_command_history_pbas(self):
(cmds_for_linux, command_history_files_for_linux, usernames_for_linux) =\
get_commands_to_clear_command_history()
pbas = []
for username in usernames_for_linux:
for command_history_file in command_history_files_for_linux:
linux_cmds = ' '.join(cmds_for_linux).format(command_history_file).format(username)
pbas.append(self.ClearCommandHistoryFile(linux_cmds=linux_cmds))
return pbas
class ClearCommandHistoryFile(PBA):
def __init__(self, linux_cmds):
super().__init__(name=POST_BREACH_CLEAR_CMD_HISTORY,
linux_cmd=linux_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

View File

@ -0,0 +1,12 @@
from infection_monkey.post_breach.clear_command_history.linux_clear_command_history import (
get_linux_command_history_files,
get_linux_commands_to_clear_command_history, get_linux_usernames)
def get_commands_to_clear_command_history():
(linux_cmds,
linux_cmd_hist_files,
linux_usernames) = (get_linux_commands_to_clear_command_history(),
get_linux_command_history_files(),
get_linux_usernames())
return linux_cmds, linux_cmd_hist_files, linux_usernames

View File

@ -0,0 +1,55 @@
import subprocess
from infection_monkey.utils.environment import is_windows_os
def get_linux_commands_to_clear_command_history():
if is_windows_os():
return ''
TEMP_HIST_FILE = '$HOME/monkey-temp-hist-file'
return [
'3<{0} 3<&- && ', # check for existence of file
'cat {0} ' # copy contents of history file to...
f'> {TEMP_HIST_FILE} && ', # ...temporary file
'echo > {0} && ', # clear contents of file
'echo \"Successfully cleared {0}\" && ', # if successfully cleared
f'cat {TEMP_HIST_FILE} ', # restore history file back with...
'> {0} ;' # ...original contents
f'rm {TEMP_HIST_FILE} -f' # remove temp history file
]
def get_linux_command_history_files():
if is_windows_os():
return []
HOME_DIR = "/home/"
# get list of paths of different shell history files (default values) with place for username
STARTUP_FILES = [
file_path.format(HOME_DIR) for file_path in
[
"{0}{{0}}/.bash_history", # bash
"{0}{{0}}/.local/share/fish/fish_history", # fish
"{0}{{0}}/.zsh_history", # zsh
"{0}{{0}}/.sh_history", # ksh
"{0}{{0}}/.history" # csh, tcsh
]
]
return STARTUP_FILES
def get_linux_usernames():
if is_windows_os():
return []
# get list of usernames
USERS = subprocess.check_output( # noqa: DUO116
"cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1",
shell=True
).decode().split('\n')[:-1]
return USERS

View File

@ -14,11 +14,12 @@ from monkey_island.cc.services.attack.technique_reports import (T1003, T1005,
T1106, T1107, T1106, T1107,
T1110, T1129, T1110, T1129,
T1136, T1145, T1136, T1145,
T1154, T1156, T1146, T1154,
T1158, T1166, T1156, T1158,
T1168, T1188, T1166, T1168,
T1197, T1210, T1188, T1197,
T1222, T1504) T1210, T1222,
T1504)
from monkey_island.cc.services.reporting.report_generation_synchronisation import \ from monkey_island.cc.services.reporting.report_generation_synchronisation import \
safe_generate_attack_report safe_generate_attack_report
@ -57,7 +58,8 @@ TECHNIQUES = {'T1210': T1210.T1210,
'T1154': T1154.T1154, 'T1154': T1154.T1154,
'T1166': T1166.T1166, 'T1166': T1166.T1166,
'T1168': T1168.T1168, 'T1168': T1168.T1168,
'T1053': T1053.T1053 'T1053': T1053.T1053,
'T1146': T1146.T1146
} }
REPORT_NAME = 'new_report' REPORT_NAME = 'new_report'

View File

@ -168,6 +168,15 @@ SCHEMA = {
"description": "Adversaries may abuse BITS to download, execute, " "description": "Adversaries may abuse BITS to download, execute, "
"and even clean up after running malicious code." "and even clean up after running malicious code."
}, },
"T1146": {
"title": "Clear command history",
"type": "bool",
"value": False,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1146",
"description": "Adversaries may clear/disable command history of a compromised "
"account to conceal the actions undertaken during an intrusion."
},
"T1107": { "T1107": {
"title": "File Deletion", "title": "File Deletion",
"type": "bool", "type": "bool",

View File

@ -0,0 +1,22 @@
from common.data.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1146(PostBreachTechnique):
tech_id = "T1146"
unscanned_msg = "Monkey didn't try clearing the command history since it didn't run on any Linux machines."
scanned_msg = "Monkey tried clearing the command history but failed."
used_msg = "Monkey successfully cleared the command history (and then restored it back)."
pba_names = [POST_BREACH_CLEAR_CMD_HISTORY]
@staticmethod
def get_pba_query(*args):
return [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_CLEAR_CMD_HISTORY}},
{'$project': {'_id': 0,
'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]},
'ips': [{'$arrayElemAt': ['$data.ip', 0]}]},
'result': '$data.result'}}]

View File

@ -70,6 +70,15 @@ POST_BREACH_ACTIONS = {
"title": "Job scheduling", "title": "Job scheduling",
"info": "Attempts to create a scheduled job on the system and remove it.", "info": "Attempts to create a scheduled job on the system and remove it.",
"attack_techniques": ["T1168", "T1053"] "attack_techniques": ["T1168", "T1053"]
},
{
"type": "string",
"enum": [
"ClearCommandHistory"
],
"title": "Clear command history",
"info": "Attempts to clear the command history.",
"attack_techniques": ["T1146"]
} }
] ]
} }

View File

@ -0,0 +1,45 @@
import React from 'react';
import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers';
import MitigationsComponent from './MitigationsComponent';
class T1146 extends React.Component {
constructor(props) {
super(props);
}
static getColumns() {
return ([{
columns: [
{ Header: 'Machine',
id: 'machine',
accessor: x => renderMachineFromSystemData(x.machine),
style: {'whiteSpace': 'unset'}},
{ Header: 'Result',
id: 'result',
accessor: x => x.result,
style: {'whiteSpace': 'unset'}}
]
}])
}
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1146.getColumns()}
data={this.props.data.info}
showPagination={false}
defaultPageSize={this.props.data.info.length}
/> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div>
);
}
}
export default T1146;

View File

@ -1,33 +1,62 @@
export default function parsePbaResults(results) { export default function parsePbaResults(results) {
results.pba_results = aggregateShellStartupPba(results.pba_results); results.pba_results = aggregateMultipleResultsPba(results.pba_results);
return results; return results;
} }
const SHELL_STARTUP_NAME = 'Modify shell startup file'; const SHELL_STARTUP_NAME = 'Modify shell startup file';
const CMD_HISTORY_NAME = 'Clear command history';
function aggregateShellStartupPba(results) { const multipleResultsPbas = [SHELL_STARTUP_NAME, CMD_HISTORY_NAME]
let isSuccess = false;
let aggregatedPbaResult = undefined;
let successfulOutputs = '';
let failedOutputs = '';
for(let i = 0; i < results.length; i++){ function aggregateMultipleResultsPba(results) {
if(results[i].name === SHELL_STARTUP_NAME && aggregatedPbaResult === undefined){ let aggregatedPbaResults = {};
aggregatedPbaResult = results[i]; multipleResultsPbas.forEach(function(pba) {
aggregatedPbaResults[pba] = {
aggregatedResult: undefined,
successfulOutputs: '',
failedOutputs: '',
isSuccess: false
} }
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 = results.filter(result => result.name !== SHELL_STARTUP_NAME); function aggregateResults(result) {
aggregatedPbaResult.result[0] = successfulOutputs + failedOutputs; if (aggregatedPbaResults[result.name].aggregatedResult === undefined) {
aggregatedPbaResult.result[1] = isSuccess; aggregatedPbaResults[result.name].aggregatedResult = result;
results.push(aggregatedPbaResult); }
if (result.result[1]) {
aggregatedPbaResults[result.name].successfulOutputs += result.result[0];
aggregatedPbaResults[result.name].isSuccess = true;
}
else if (!result.result[1]) {
aggregatedPbaResults[result.name].failedOutputs += result.result[0];
}
}
function checkAggregatedResults(pbaName) { // if this pba's results were aggregated, push to `results`
if (aggregatedPbaResults[pbaName].aggregatedResult !== undefined) {
aggregatedPbaResults[pbaName].aggregatedResult.result[0] = (aggregatedPbaResults[pbaName].successfulOutputs +
aggregatedPbaResults[pbaName].failedOutputs);
aggregatedPbaResults[pbaName].aggregatedResult.result[1] = aggregatedPbaResults[pbaName].isSuccess;
results.push(aggregatedPbaResults[pbaName].aggregatedResult);
}
}
// check for pbas with multiple results and aggregate their results
for (let i = 0; i < results.length; i++)
if (multipleResultsPbas.includes(results[i].name))
aggregateResults(results[i]);
// if no modifications were made to the results, i.e. if no pbas had mutiple results, return `results` as it is
let noResultsModifications = true;
multipleResultsPbas.forEach((pba) => {
if (aggregatedPbaResults[pba].aggregatedResult !== undefined)
noResultsModifications = false;
})
if (noResultsModifications)
return results;
// if modifications were made, push aggregated results to `results` and return
results = results.filter(result => !multipleResultsPbas.includes(result.name));
multipleResultsPbas.forEach(pba => checkAggregatedResults(pba));
return results; return results;
} }