diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py index dc7bb7310..c3bba9950 100644 --- a/monkey/common/data/post_breach_consts.py +++ b/monkey/common/data/post_breach_consts.py @@ -5,3 +5,4 @@ POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = "Modify shell startup file" POST_BREACH_HIDDEN_FILES = "Hide files and directories" POST_BREACH_TRAP_COMMAND = "Execute command when a particular signal is received" POST_BREACH_SETUID_SETGID = "Setuid and Setgid" +POST_BREACH_JOB_SCHEDULING = "Schedule jobs" diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py new file mode 100644 index 000000000..02cc4e19e --- /dev/null +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -0,0 +1,19 @@ +from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING +from infection_monkey.post_breach.pba import PBA +from infection_monkey.post_breach.job_scheduling.job_scheduling import\ + get_commands_to_schedule_jobs, remove_scheduled_jobs + + +class ScheduleJobs(PBA): + """ + This PBA attempts to schedule jobs on the system. + """ + + def __init__(self): + linux_cmds, windows_cmds = get_commands_to_schedule_jobs() + + super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING, + linux_cmd=' '.join(linux_cmds), + windows_cmd=windows_cmds) + + remove_scheduled_jobs() diff --git a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py new file mode 100644 index 000000000..8d18124f6 --- /dev/null +++ b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py @@ -0,0 +1,18 @@ +import subprocess +from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import\ + get_linux_commands_to_schedule_jobs +from infection_monkey.post_breach.job_scheduling.windows_job_scheduling import\ + get_windows_commands_to_schedule_jobs,\ + get_windows_commands_to_remove_scheduled_jobs +from infection_monkey.utils.environment import is_windows_os + + +def get_commands_to_schedule_jobs(): + linux_cmds = get_linux_commands_to_schedule_jobs() + windows_cmds = get_windows_commands_to_schedule_jobs() + return linux_cmds, windows_cmds + + +def remove_scheduled_jobs(): + if is_windows_os(): + subprocess.run(get_windows_commands_to_remove_scheduled_jobs(), shell=True) # noqa: DUO116 diff --git a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py new file mode 100644 index 000000000..4ed5ff970 --- /dev/null +++ b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py @@ -0,0 +1,12 @@ +TEMP_CRON = "$HOME/monkey-schedule-jobs" + + +def get_linux_commands_to_schedule_jobs(): + return [ + f'touch {TEMP_CRON} &&', + f'crontab -l > {TEMP_CRON} &&', + 'echo \"# Successfully scheduled a job using crontab\" |', + f'tee -a {TEMP_CRON} &&', + f'crontab {TEMP_CRON} ;', + f'rm {TEMP_CRON}' + ] diff --git a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py new file mode 100644 index 000000000..fe3dad525 --- /dev/null +++ b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py @@ -0,0 +1,12 @@ +SCHEDULED_TASK_NAME = 'monkey-spawn-cmd' +SCHEDULED_TASK_COMMAND = 'C:\windows\system32\cmd.exe' + +# Commands from: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005/T1053.005.md + + +def get_windows_commands_to_schedule_jobs(): + return f'schtasks /Create /SC monthly /TN {SCHEDULED_TASK_NAME} /TR {SCHEDULED_TASK_COMMAND}' + + +def get_windows_commands_to_remove_scheduled_jobs(): + return f'schtasks /Delete /TN {SCHEDULED_TASK_NAME} /F > nul 2>&1' diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index c96db0651..113dfc942 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -4,7 +4,7 @@ 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, T1064 -from monkey_island.cc.services.attack.technique_reports import T1136, T1156, T1504, T1158, T1154, T1166 +from monkey_island.cc.services.attack.technique_reports import T1136, T1156, T1504, T1158, T1154, T1166, T1168, T1053 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report @@ -42,7 +42,9 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1504': T1504.T1504, 'T1158': T1158.T1158, 'T1154': T1154.T1154, - 'T1166': T1166.T1166 + 'T1166': T1166.T1166, + 'T1168': T1168.T1168, + 'T1053': T1053.T1053 } 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 abb26b71a..4c9889df3 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -109,6 +109,16 @@ SCHEMA = { "and evade a typical user or system analysis that does not " "incorporate investigation of hidden files." }, + "T1168": { + "title": "Local job scheduling", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1168/", + "description": "Linux supports multiple methods for creating pre-scheduled and " + "periodic background jobs. Job scheduling can be used by adversaries to " + "schedule running malicious code at some specified date and time." + }, "T1504": { "title": "PowerShell profile", "type": "bool", @@ -119,6 +129,16 @@ SCHEMA = { "in certain situations by abusing PowerShell profiles which " "are scripts that run when PowerShell starts." }, + "T1053": { + "title": "Scheduled task", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1053", + "description": "Windows utilities can be used to schedule programs or scripts to " + "be executed at a date and time. An adversary may use task scheduling to " + "execute programs at system startup or on a scheduled basis for persistence." + }, "T1166": { "title": "Setuid and Setgid", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py new file mode 100644 index 000000000..8484e78ed --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py @@ -0,0 +1,35 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo +from common.utils.attack_utils import ScanStatus +from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING + + +__author__ = "shreyamalviya" + + +class T1053(AttackTechnique): + tech_id = "T1053" + unscanned_msg = "Monkey did not try scheduling a job on Windows." + scanned_msg = "Monkey tried scheduling a job on the Windows system but failed." + used_msg = "Monkey scheduled a job on the Windows system." + + query = [{'$match': {'telem_category': 'post_breach', + 'data.name': POST_BREACH_JOB_SCHEDULING, + 'data.command': {'$regex': 'schtasks'}}}, + {'$project': {'_id': 0, + 'machine': {'hostname': '$data.hostname', + 'ips': ['$data.ip']}, + 'result': '$data.result'}}] + + @staticmethod + def get_report_data(): + data = {'title': T1053.technique_title()} + + job_scheduling_info = list(mongo.db.telemetry.aggregate(T1053.query)) + + status = (ScanStatus.USED.value if job_scheduling_info[0]['result'][1] + else ScanStatus.SCANNED.value) if job_scheduling_info else ScanStatus.UNSCANNED.value + + data.update(T1053.get_base_data_by_status(status)) + data.update({'info': job_scheduling_info}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py new file mode 100644 index 000000000..41afbc302 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py @@ -0,0 +1,35 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo +from common.utils.attack_utils import ScanStatus +from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING + + +__author__ = "shreyamalviya" + + +class T1168(AttackTechnique): + tech_id = "T1168" + unscanned_msg = "Monkey did not try scheduling a job on Linux." + scanned_msg = "Monkey tried scheduling a job on the Linux system but failed." + used_msg = "Monkey scheduled a job on the Linux system." + + query = [{'$match': {'telem_category': 'post_breach', + 'data.name': POST_BREACH_JOB_SCHEDULING, + 'data.command': {'$regex': 'crontab'}}}, + {'$project': {'_id': 0, + 'machine': {'hostname': '$data.hostname', + 'ips': ['$data.ip']}, + 'result': '$data.result'}}] + + @staticmethod + def get_report_data(): + data = {'title': T1168.technique_title()} + + job_scheduling_info = list(mongo.db.telemetry.aggregate(T1168.query)) + + status = (ScanStatus.USED.value if job_scheduling_info[0]['result'][1] + else ScanStatus.SCANNED.value) if job_scheduling_info else ScanStatus.UNSCANNED.value + + data.update(T1168.get_base_data_by_status(status)) + data.update({'info': job_scheduling_info}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 4f7027cbc..367c281f7 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -191,6 +191,14 @@ SCHEMA = { ], "title": "Setuid and Setgid", "attack_techniques": ["T1166"] + }, + { + "type": "string", + "enum": [ + "ScheduleJobs" + ], + "title": "Job scheduling", + "attack_techniques": ["T1168", "T1053"] } ], }, @@ -415,7 +423,8 @@ SCHEMA = { "ModifyShellStartupFiles", "HiddenFiles", "TrapCommand", - "ChangeSetuidSetgid" + "ChangeSetuidSetgid", + "ScheduleJobs" ], "description": "List of actions the Monkey will run post breach" }, diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1053.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1053.js new file mode 100644 index 000000000..11a27e156 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1053.js @@ -0,0 +1,45 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import {renderMachineFromSystemData, ScanStatus} from './Helpers'; +import MitigationsComponent from './MitigationsComponent'; + +class T1053 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 ( +
+
{this.props.data.message}
+
+ {this.props.data.status === ScanStatus.USED ? + : ''} + +
+ ); + } +} + +export default T1053; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1168.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1168.js new file mode 100644 index 000000000..1e8374440 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1168.js @@ -0,0 +1,45 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import {renderMachineFromSystemData, ScanStatus} from './Helpers'; +import MitigationsComponent from './MitigationsComponent'; + +class T1168 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 ( +
+
{this.props.data.message}
+
+ {this.props.data.status === ScanStatus.USED ? + : ''} + +
+ ); + } +} + +export default T1168;