From f22e69234cd72c99cd28d419d8a974463ab39231 Mon Sep 17 00:00:00 2001 From: Kyle Heaton Date: Tue, 23 Nov 2021 15:46:05 -0500 Subject: [PATCH 1/9] Fix event_logs download functionality Functionality was previously listed but not working, as mentioned in issue @2329. This fix creates another function that calls the correct API endpoint. --- templates/operations.html | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/templates/operations.html b/templates/operations.html index 01849f3b4..580b4f48e 100644 --- a/templates/operations.html +++ b/templates/operations.html @@ -1392,7 +1392,7 @@

Decisions

} }, - downloadInfo(formatType) { + downloadReport(formatType) { apiV2('POST', `${this.ENDPOINT}/${this.selectedOperationID}/report`, { enable_agent_output: this.isAgentOutputSelected }) .then((res) => { this.createDownloadReport(res, `${this.selectedOperation.name}_${formatType}`); @@ -1400,6 +1400,14 @@

Decisions

.catch(() => toast('Error generating operation report.')); }, + downloadEventLogs(formatType) { + apiV2('POST', `${this.ENDPOINT}/${this.selectedOperationID}/event-logs`, { enable_agent_output: this.isAgentOutputSelected }) + .then((res) => { + this.createDownloadReport(res, `${this.selectedOperation.name}_${formatType}`); + }) + .catch(() => toast('Error generating operation report.')); + }, + downloadCSV() { const csv = []; const rows = document.querySelector('#operationsPage').querySelectorAll('table tr:not(.csv-exclude)'); @@ -1428,8 +1436,8 @@

Decisions

}, handleDownload() { - if (this.selectedReportType === 'full-report') this.downloadInfo('full-report'); - else if (this.selectedReportType === 'event-logs') this.downloadInfo('event-logs'); + if (this.selectedReportType === 'full-report') this.downloadReport('full-report'); + else if (this.selectedReportType === 'event-logs') this.downloadEventLogs('event-logs'); else if (this.selectedReportType === 'csv') this.downloadCSV(); }, From 4cdd07ca666316c3978230eacca1498583fde981 Mon Sep 17 00:00:00 2001 From: William Booth <18699738+wbooth@users.noreply.github.com> Date: Mon, 29 Nov 2021 12:25:54 -0500 Subject: [PATCH 2/9] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6242052af..fc1b1a1ce 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,14 @@ These requirements are for the computer running the core framework: Concise installation steps: ```Bash -git clone https://github.com/mitre/caldera.git --recursive --branch 4.0.0-alpha +git clone https://github.com/mitre/caldera.git --recursive cd caldera pip3 install -r requirements.txt python3 server.py --insecure ``` Full steps: -Start by cloning this repository recursively, passing the desired version/release in x.x.x format. This will pull in all available plugins. If you clone master - or any non-release branch - you may experience bugs. +Start by cloning this repository recursively, passing the desired version/release in x.x.x format. This will pull in all available plugins. ```Bash git clone https://github.com/mitre/caldera.git --recursive --branch 4.0.0-alpha ``` From 3e2dc4dd4396f0d96dd1ff1ab27e5692941fe5b1 Mon Sep 17 00:00:00 2001 From: William Booth <18699738+wbooth@users.noreply.github.com> Date: Mon, 29 Nov 2021 12:53:53 -0500 Subject: [PATCH 3/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc1b1a1ce..480d7fc04 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ python3 server.py --insecure Full steps: Start by cloning this repository recursively, passing the desired version/release in x.x.x format. This will pull in all available plugins. ```Bash -git clone https://github.com/mitre/caldera.git --recursive --branch 4.0.0-alpha +git clone https://github.com/mitre/caldera.git --recursive --branch x.x.x ``` Next, install the PIP requirements: From 8623f9c8aedf48d7048eb6de107cf56cfb7c8b68 Mon Sep 17 00:00:00 2001 From: Adam Gaudreau Date: Mon, 29 Nov 2021 14:31:28 -0500 Subject: [PATCH 4/9] Resolve flake8 errors --- app/api/packs/advanced.py | 2 +- app/api/packs/campaign.py | 4 ++-- app/api/rest_api.py | 6 +++--- app/api/v2/handlers/base_object_api.py | 12 ++++++------ app/api/v2/handlers/operation_api.py | 4 ++-- app/api/v2/managers/ability_api_manager.py | 2 +- app/api/v2/managers/base_api_manager.py | 12 ++++++------ app/api/v2/managers/operation_api_manager.py | 2 +- app/contacts/contact_gist.py | 2 +- app/contacts/contact_html.py | 2 +- app/contacts/contact_http.py | 2 +- app/contacts/contact_slack.py | 2 +- app/contacts/contact_tcp.py | 2 +- app/objects/c_agent.py | 2 +- app/objects/c_operation.py | 2 +- app/objects/c_planner.py | 2 +- app/objects/c_plugin.py | 2 +- app/objects/secondclass/c_link.py | 2 +- app/service/app_svc.py | 2 +- app/service/contact_svc.py | 2 +- app/service/data_svc.py | 2 +- app/service/file_svc.py | 2 +- app/service/learning_svc.py | 2 +- app/service/planning_svc.py | 2 +- app/service/rest_svc.py | 2 +- app/utility/base_planning_svc.py | 2 +- 26 files changed, 40 insertions(+), 40 deletions(-) diff --git a/app/api/packs/advanced.py b/app/api/packs/advanced.py index b12207c0f..8ea075d51 100644 --- a/app/api/packs/advanced.py +++ b/app/api/packs/advanced.py @@ -22,7 +22,7 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/advanced/configurations', self._section_configurations) self.app_svc.application.router.add_route('GET', '/advanced/exfills', self._section_exfil_files) - """ PRIVATE """ + @check_authorization @template('planners.html') diff --git a/app/api/packs/campaign.py b/app/api/packs/campaign.py index 3c5ca1dbe..3e96eacf6 100644 --- a/app/api/packs/campaign.py +++ b/app/api/packs/campaign.py @@ -21,7 +21,7 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/campaign/profiles', self._section_profiles) self.app_svc.application.router.add_route('GET', '/campaign/operations', self._section_operations) - """ PRIVATE """ + @check_authorization @template('agents.html') @@ -110,7 +110,7 @@ def load_usage_markdown(header): return dict(operations=operations, groups=groups, adversaries=adversaries, sources=sources, planners=planners, obfuscators=obfuscators, usage=usage) - """ PRIVATE """ + @staticmethod def _rollup_abilities(abilities): diff --git a/app/api/rest_api.py b/app/api/rest_api.py index be4e10112..29ad85999 100644 --- a/app/api/rest_api.py +++ b/app/api/rest_api.py @@ -43,7 +43,7 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/api/{index}', self.rest_core_info) self.app_svc.application.router.add_route('GET', '/file/download_exfil', self.download_exfil_file) - """ BOILERPLATE """ + @template('login.html', status=401) async def login(self, request): @@ -65,7 +65,7 @@ async def landing(self, request): data = dict(plugins=[p.display for p in plugins], errors=self.app_svc.errors + self._request_errors(request)) return render_template('%s.html' % access[0].name, request, data) - """ API ENDPOINTS """ + @check_authorization async def rest_core(self, request): @@ -165,7 +165,7 @@ def is_in_exfil_dir(f): return web.HTTPNotFound(body=str(e)) return web.HTTPBadRequest(body='A file needs to be specified for download') - """ PRIVATE """ + @staticmethod def _request_errors(request): diff --git a/app/api/v2/handlers/base_object_api.py b/app/api/v2/handlers/base_object_api.py index ae579aa91..2ef545bcc 100644 --- a/app/api/v2/handlers/base_object_api.py +++ b/app/api/v2/handlers/base_object_api.py @@ -23,7 +23,7 @@ def __init__(self, description, obj_class, schema, ram_key, id_property, auth_sv def add_routes(self, app: web.Application): pass - """GET""" + async def get_all_objects(self, request: web.Request): access = await self.get_request_permissions(request) @@ -48,7 +48,7 @@ async def get_object(self, request: web.Request): return self._api_manager.dump_object_with_filters(obj, include, exclude) - """POST""" + async def create_object(self, request: web.Request): data = await request.json() @@ -75,7 +75,7 @@ async def _error_if_object_with_id_exists(self, obj_id: str): if self._api_manager.find_object(self.ram_key, search): raise JsonHttpBadRequest(f'{self.description.capitalize()} with given id already exists: {obj_id}') - """PATCH""" + async def update_object(self, request: web.Request): data, access, obj_id, query, search = await self._parse_common_data_from_request(request) @@ -94,7 +94,7 @@ async def update_on_disk_object(self, request: web.Request): raise JsonHttpNotFound(f'{self.description.capitalize()} not found: {obj_id}') return obj - """PUT""" + async def create_or_update_object(self, request: web.Request): data, access, obj_id, query, search = await self._parse_common_data_from_request(request) @@ -120,7 +120,7 @@ async def create_or_update_on_disk_object(self, request: web.Request): return obj - """DELETE""" + async def delete_object(self, request: web.Request): obj_id = request.match_info.get(self.id_property) @@ -141,7 +141,7 @@ async def delete_on_disk_object(self, request: web.Request): obj_id = request.match_info.get(self.id_property) await self._api_manager.remove_object_from_disk_by_id(identifier=obj_id, ram_key=self.ram_key) - """Helpers""" + async def _parse_common_data_from_request(self, request) -> (dict, dict, str, dict, dict): data = {} diff --git a/app/api/v2/handlers/operation_api.py b/app/api/v2/handlers/operation_api.py index da2790ebd..e673da055 100644 --- a/app/api/v2/handlers/operation_api.py +++ b/app/api/v2/handlers/operation_api.py @@ -163,7 +163,7 @@ async def get_potential_links_by_paw(self, request: web.Request): potential_links = await self._api_manager.get_potential_links(operation_id, access, paw) return web.json_response(potential_links) - '''Overridden Methods''' + async def create_object(self, request: web.Request): data = await request.json() @@ -178,7 +178,7 @@ async def update_object(self, request: web.Request): raise JsonHttpNotFound(f'{self.description.capitalize()} not found: {obj_id}') return obj - ''' PRIVATE ''' + async def _read_output_parameter_(self, request: web.Request): raw_body = await request.read() diff --git a/app/api/v2/managers/ability_api_manager.py b/app/api/v2/managers/ability_api_manager.py index 5f432d4a6..83d17b685 100644 --- a/app/api/v2/managers/ability_api_manager.py +++ b/app/api/v2/managers/ability_api_manager.py @@ -45,7 +45,7 @@ async def update_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pro await self._save_and_reload_object(file_path, existing_obj_data, obj_class, obj.access) return next(self.find_objects(ram_key, {id_property: obj_id})) - '''Helpers''' + def _validate_ability_data(self, create: bool, data: dict): # Correct ability_id key for ability file saving. data['id'] = data.pop('ability_id', '') diff --git a/app/api/v2/managers/base_api_manager.py b/app/api/v2/managers/base_api_manager.py index adfb6f956..1c9f3b8f8 100644 --- a/app/api/v2/managers/base_api_manager.py +++ b/app/api/v2/managers/base_api_manager.py @@ -23,7 +23,7 @@ def __init__(self, data_svc, file_svc, logger=None): def log(self): return self._log - """Object Retrieval""" + def find_objects(self, ram_key: str, search: dict = None): """Find objects matching the given criteria""" @@ -54,7 +54,7 @@ def dump_object_with_filters(obj: Any, include: List[str] = None, exclude: List[ dumped.pop(exclude_attribute, None) return dumped - """Object Creation""" + def create_object_from_schema(self, schema: SchemaMeta, data: dict, access: BaseWorld.Access): obj_schema = schema() @@ -79,7 +79,7 @@ def _get_allowed_from_access(self, access) -> BaseWorld.Access: else: return self._data_svc.Access.RED - """Object Updates""" + def find_and_update_object(self, ram_key: str, data: dict, search: dict = None): for obj in self.find_objects(ram_key, search): @@ -112,7 +112,7 @@ async def update_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pro await self._save_and_reload_object(file_path, existing_obj_data, obj_class, obj.access) return next(self.find_objects(ram_key, {id_property: obj_id})) - """"Object Replacement""" + async def replace_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_property: str): obj_id = getattr(obj, id_property) @@ -121,7 +121,7 @@ async def replace_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pr await self._save_and_reload_object(file_path, data, type(obj), obj.access) return next(self.find_objects(ram_key, {id_property: obj_id})) - """"Object Removal""" + async def remove_object_from_memory_by_id(self, identifier: str, ram_key: str, id_property: str): await self._data_svc.remove(ram_key, {id_property: identifier}) @@ -132,7 +132,7 @@ async def remove_object_from_disk_by_id(self, identifier: str, ram_key: str): if os.path.exists(file_path): os.remove(file_path) - """Helpers""" + @staticmethod async def _get_new_object_file_path(identifier: str, ram_key: str) -> str: diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index 9b1791af3..0453d1684 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -122,7 +122,7 @@ async def get_potential_links(self, operation_id: str, access: dict, paw: str = potential_links = [potential_link.display for potential_link in operation.potential_links] return potential_links - """Object Creation Helpers""" + async def get_operation_object(self, operation_id: str, access: dict): try: operation = (await self._data_svc.locate('operations', {'id': operation_id}))[0] diff --git a/app/contacts/contact_gist.py b/app/contacts/contact_gist.py index ab64d51f4..a2948b419 100644 --- a/app/contacts/contact_gist.py +++ b/app/contacts/contact_gist.py @@ -141,7 +141,7 @@ async def get_uploads(self): self.log.error('Receiving file uploads over c2 (%s) failed: %s' % (self.__class__.__name__, e)) return [] - """ PRIVATE """ + async def _send_instructions(self, agent, instructions): response = dict(paw=agent.paw, diff --git a/app/contacts/contact_html.py b/app/contacts/contact_html.py index d5d628098..17f315c7e 100644 --- a/app/contacts/contact_html.py +++ b/app/contacts/contact_html.py @@ -16,7 +16,7 @@ def __init__(self, services): async def start(self): self.app_svc.application.router.add_route('*', self.get_config('app.contact.html'), self._accept_beacon) - """ PRIVATE """ + @template('weather.html') async def _accept_beacon(self, request): diff --git a/app/contacts/contact_http.py b/app/contacts/contact_http.py index 6e162d761..7669f959b 100644 --- a/app/contacts/contact_http.py +++ b/app/contacts/contact_http.py @@ -17,7 +17,7 @@ def __init__(self, services): async def start(self): self.app_svc.application.router.add_route('POST', '/beacon', self._beacon) - """ PRIVATE """ + async def _beacon(self, request): try: diff --git a/app/contacts/contact_slack.py b/app/contacts/contact_slack.py index 8d803f754..632236df2 100644 --- a/app/contacts/contact_slack.py +++ b/app/contacts/contact_slack.py @@ -150,7 +150,7 @@ async def get_uploads(self): self.log.error('Receiving file uploads over c2 (%s) failed: %s' % (self.__class__.__name__, e)) return [] - """ PRIVATE """ + async def _send_instructions(self, agent, instructions): response = dict(paw=agent.paw, diff --git a/app/contacts/contact_tcp.py b/app/contacts/contact_tcp.py index ccbc0316f..6c0decfc6 100644 --- a/app/contacts/contact_tcp.py +++ b/app/contacts/contact_tcp.py @@ -93,7 +93,7 @@ async def send(self, session_id: int, cmd: str, timeout: int = 60) -> Tuple[int, self.log.exception(e) return 1, '~$ ', str(e), '' - """ PRIVATE """ + @staticmethod async def _handshake(reader): diff --git a/app/objects/c_agent.py b/app/objects/c_agent.py index 6b52d3765..ae3cc2af2 100644 --- a/app/objects/c_agent.py +++ b/app/objects/c_agent.py @@ -332,7 +332,7 @@ def assign_pending_executor_change(self): self._executor_change_to_assign = None return executor_change - """ PRIVATE """ + def _replace_payload_data(self, decoded_cmd, file_svc): for uuid in re.findall(self.RESERVED['payload'], decoded_cmd): diff --git a/app/objects/c_operation.py b/app/objects/c_operation.py index 04d28e6a8..44900e5d3 100644 --- a/app/objects/c_operation.py +++ b/app/objects/c_operation.py @@ -337,7 +337,7 @@ async def write_event_logs_to_disk(self, file_svc, data_svc, output=False): await self._write_logs_to_disk(event_logs, file_name, event_logs_dir, file_svc) logging.debug('Wrote event logs for operation %s to disk at %s/%s' % (self.name, event_logs_dir, file_name)) - """ PRIVATE """ + async def _write_logs_to_disk(self, logs, file_name, dest_dir, file_svc): logs_dumps = json.dumps(logs) diff --git a/app/objects/c_planner.py b/app/objects/c_planner.py index 8b0e671c2..03cb51e5a 100644 --- a/app/objects/c_planner.py +++ b/app/objects/c_planner.py @@ -62,7 +62,7 @@ async def which_plugin(self): return plugin return None - """ PRIVATE """ + @staticmethod def _set_stopping_conditions(conditions): diff --git a/app/objects/c_plugin.py b/app/objects/c_plugin.py index 8d4e879c1..f6dbad7f5 100644 --- a/app/objects/c_plugin.py +++ b/app/objects/c_plugin.py @@ -84,7 +84,7 @@ async def expand(self, services): except Exception as e: logging.error('Error expanding plugin=%s, %s' % (self.name, e)) - """ PRIVATE """ + def _load_module(self): try: diff --git a/app/objects/secondclass/c_link.py b/app/objects/secondclass/c_link.py index f260b602d..b59a0d8bc 100644 --- a/app/objects/secondclass/c_link.py +++ b/app/objects/secondclass/c_link.py @@ -223,7 +223,7 @@ def replace_origin_link_id(self): decoded_cmd = self.decode_bytes(self.command) self.command = self.encode_string(decoded_cmd.replace(self.RESERVED['origin_link_id'], self.id)) - """ PRIVATE """ + def _emit_status_change_event(self, from_status, to_status): event_svc = BaseService.get_service('event_svc') diff --git a/app/service/app_svc.py b/app/service/app_svc.py index ee6518442..814a41c56 100644 --- a/app/service/app_svc.py +++ b/app/service/app_svc.py @@ -193,7 +193,7 @@ def register_subapp(self, path: str, app: web.Application): def get_loaded_plugins(self): return tuple(self._loaded_plugins) - """ PRIVATE """ + async def _save_configurations(self, main_config_file='default'): for cfg_name, cfg_file in [('main', main_config_file), ('agents', 'agents'), ('payloads', 'payloads')]: diff --git a/app/service/contact_svc.py b/app/service/contact_svc.py index d4a108850..7a0b3bdff 100644 --- a/app/service/contact_svc.py +++ b/app/service/contact_svc.py @@ -93,7 +93,7 @@ async def get_tunnel(self, name): tunnel = [t for t in self.tunnels if t.name == name] return tunnel[0] if len(tunnel) > 0 else None - """ PRIVATE """ + async def _sanitize_paw(self, input_paw): """ diff --git a/app/service/data_svc.py b/app/service/data_svc.py index f5a5e3b89..1fcae09f2 100644 --- a/app/service/data_svc.py +++ b/app/service/data_svc.py @@ -241,7 +241,7 @@ async def load_yaml_file(self, object_class, filename, access): obj.access = access await self.store(obj) - """ PRIVATE """ + async def _load(self, plugins=()): try: diff --git a/app/service/file_svc.py b/app/service/file_svc.py index 51abcedb2..dd438d272 100644 --- a/app/service/file_svc.py +++ b/app/service/file_svc.py @@ -201,7 +201,7 @@ def add_xored_extension(filename): return filename return '%s.xored' % filename - """ PRIVATE """ + def _save(self, filename, content, encrypt=True): if encrypt and (self.encryptor and self.encrypt_output): diff --git a/app/service/learning_svc.py b/app/service/learning_svc.py index 7d67bb203..0c477d3a9 100644 --- a/app/service/learning_svc.py +++ b/app/service/learning_svc.py @@ -50,7 +50,7 @@ async def learn(self, facts, link, blob, operation=None): await update_scores(operation=None, increment=len(found_facts), used=facts, facts=link.facts) await self._store_results(link, found_facts, operation) - """ PRIVATE """ + @staticmethod async def _save_fact(link, facts, fact, operation=None): diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index ebc0688b5..beca54663 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -274,7 +274,7 @@ async def sort_links(links): """ return sorted(links, key=lambda k: (-k.score)) - """ PRIVATE """ + async def _stop_bucket_exhaustion(self, planner, operation, condition_stop): """Determine whether to continue running the bucket. diff --git a/app/service/rest_svc.py b/app/service/rest_svc.py index 8e1b5b3be..1686da37e 100644 --- a/app/service/rest_svc.py +++ b/app/service/rest_svc.py @@ -341,7 +341,7 @@ async def build_potential_links(self, operation, agents, abilities): potential_links.append(pl) return await self.get_service('planning_svc').sort_links(potential_links) - """ PRIVATE """ + async def _build_operation_object(self, access, data): name = data.pop('name') diff --git a/app/utility/base_planning_svc.py b/app/utility/base_planning_svc.py index f482d46a8..907b1bd1b 100644 --- a/app/utility/base_planning_svc.py +++ b/app/utility/base_planning_svc.py @@ -166,7 +166,7 @@ async def obfuscate_commands(self, agent, obfuscator, links): s_link.command = self.encode_string(mod.run(s_link)) return links - """ PRIVATE """ + @staticmethod def _list_historic_duplicate_singletons(operation): From 5a0eddf6f5b7396336ded9ee182890cd0130fd0c Mon Sep 17 00:00:00 2001 From: Adam Gaudreau Date: Mon, 29 Nov 2021 14:48:27 -0500 Subject: [PATCH 5/9] Fix tests --- app/api/packs/advanced.py | 1 - app/api/packs/campaign.py | 2 -- app/api/rest_api.py | 3 --- app/api/v2/handlers/base_object_api.py | 6 ------ app/api/v2/managers/base_api_manager.py | 6 ------ app/contacts/contact_gist.py | 2 -- app/contacts/contact_html.py | 1 - app/contacts/contact_http.py | 1 - app/contacts/contact_slack.py | 1 - app/contacts/contact_tcp.py | 1 - app/objects/c_agent.py | 1 - app/objects/c_operation.py | 1 - app/objects/c_planner.py | 1 - app/objects/c_plugin.py | 1 - app/objects/secondclass/c_link.py | 1 - app/service/app_svc.py | 1 - app/service/contact_svc.py | 1 - app/service/data_svc.py | 1 - app/service/file_svc.py | 1 - app/service/learning_svc.py | 1 - app/service/planning_svc.py | 1 - app/service/rest_svc.py | 1 - app/utility/base_planning_svc.py | 1 - 23 files changed, 37 deletions(-) diff --git a/app/api/packs/advanced.py b/app/api/packs/advanced.py index 8ea075d51..b354119ef 100644 --- a/app/api/packs/advanced.py +++ b/app/api/packs/advanced.py @@ -23,7 +23,6 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/advanced/exfills', self._section_exfil_files) - @check_authorization @template('planners.html') async def _section_planners(self, request): diff --git a/app/api/packs/campaign.py b/app/api/packs/campaign.py index 3e96eacf6..113de9559 100644 --- a/app/api/packs/campaign.py +++ b/app/api/packs/campaign.py @@ -22,7 +22,6 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/campaign/operations', self._section_operations) - @check_authorization @template('agents.html') async def _section_agent(self, request): @@ -111,7 +110,6 @@ def load_usage_markdown(header): obfuscators=obfuscators, usage=usage) - @staticmethod def _rollup_abilities(abilities): rolled = defaultdict(list) diff --git a/app/api/rest_api.py b/app/api/rest_api.py index 29ad85999..b7d0b2de0 100644 --- a/app/api/rest_api.py +++ b/app/api/rest_api.py @@ -44,7 +44,6 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/file/download_exfil', self.download_exfil_file) - @template('login.html', status=401) async def login(self, request): return dict() @@ -66,7 +65,6 @@ async def landing(self, request): return render_template('%s.html' % access[0].name, request, data) - @check_authorization async def rest_core(self, request): try: @@ -166,7 +164,6 @@ def is_in_exfil_dir(f): return web.HTTPBadRequest(body='A file needs to be specified for download') - @staticmethod def _request_errors(request): errors = [] diff --git a/app/api/v2/handlers/base_object_api.py b/app/api/v2/handlers/base_object_api.py index 2ef545bcc..e0181ba2b 100644 --- a/app/api/v2/handlers/base_object_api.py +++ b/app/api/v2/handlers/base_object_api.py @@ -24,7 +24,6 @@ def add_routes(self, app: web.Application): pass - async def get_all_objects(self, request: web.Request): access = await self.get_request_permissions(request) @@ -49,7 +48,6 @@ async def get_object(self, request: web.Request): return self._api_manager.dump_object_with_filters(obj, include, exclude) - async def create_object(self, request: web.Request): data = await request.json() @@ -76,7 +74,6 @@ async def _error_if_object_with_id_exists(self, obj_id: str): raise JsonHttpBadRequest(f'{self.description.capitalize()} with given id already exists: {obj_id}') - async def update_object(self, request: web.Request): data, access, obj_id, query, search = await self._parse_common_data_from_request(request) @@ -95,7 +92,6 @@ async def update_on_disk_object(self, request: web.Request): return obj - async def create_or_update_object(self, request: web.Request): data, access, obj_id, query, search = await self._parse_common_data_from_request(request) @@ -121,7 +117,6 @@ async def create_or_update_on_disk_object(self, request: web.Request): return obj - async def delete_object(self, request: web.Request): obj_id = request.match_info.get(self.id_property) @@ -142,7 +137,6 @@ async def delete_on_disk_object(self, request: web.Request): await self._api_manager.remove_object_from_disk_by_id(identifier=obj_id, ram_key=self.ram_key) - async def _parse_common_data_from_request(self, request) -> (dict, dict, str, dict, dict): data = {} raw_body = await request.read() diff --git a/app/api/v2/managers/base_api_manager.py b/app/api/v2/managers/base_api_manager.py index 1c9f3b8f8..1e19f13df 100644 --- a/app/api/v2/managers/base_api_manager.py +++ b/app/api/v2/managers/base_api_manager.py @@ -24,7 +24,6 @@ def log(self): return self._log - def find_objects(self, ram_key: str, search: dict = None): """Find objects matching the given criteria""" for obj in self._data_svc.ram[ram_key]: @@ -55,7 +54,6 @@ def dump_object_with_filters(obj: Any, include: List[str] = None, exclude: List[ return dumped - def create_object_from_schema(self, schema: SchemaMeta, data: dict, access: BaseWorld.Access): obj_schema = schema() obj = obj_schema.load(data) @@ -80,7 +78,6 @@ def _get_allowed_from_access(self, access) -> BaseWorld.Access: return self._data_svc.Access.RED - def find_and_update_object(self, ram_key: str, data: dict, search: dict = None): for obj in self.find_objects(ram_key, search): new_obj = self.update_object(obj, data) @@ -113,7 +110,6 @@ async def update_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pro return next(self.find_objects(ram_key, {id_property: obj_id})) - async def replace_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_property: str): obj_id = getattr(obj, id_property) file_path = await self._get_existing_object_file_path(obj_id, ram_key) @@ -122,7 +118,6 @@ async def replace_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pr return next(self.find_objects(ram_key, {id_property: obj_id})) - async def remove_object_from_memory_by_id(self, identifier: str, ram_key: str, id_property: str): await self._data_svc.remove(ram_key, {id_property: identifier}) @@ -133,7 +128,6 @@ async def remove_object_from_disk_by_id(self, identifier: str, ram_key: str): os.remove(file_path) - @staticmethod async def _get_new_object_file_path(identifier: str, ram_key: str) -> str: """Create file path for new object""" diff --git a/app/contacts/contact_gist.py b/app/contacts/contact_gist.py index a2948b419..59dc99839 100644 --- a/app/contacts/contact_gist.py +++ b/app/contacts/contact_gist.py @@ -141,8 +141,6 @@ async def get_uploads(self): self.log.error('Receiving file uploads over c2 (%s) failed: %s' % (self.__class__.__name__, e)) return [] - - async def _send_instructions(self, agent, instructions): response = dict(paw=agent.paw, sleep=await agent.calculate_sleep(), diff --git a/app/contacts/contact_html.py b/app/contacts/contact_html.py index 17f315c7e..b2cb244c3 100644 --- a/app/contacts/contact_html.py +++ b/app/contacts/contact_html.py @@ -17,7 +17,6 @@ async def start(self): self.app_svc.application.router.add_route('*', self.get_config('app.contact.html'), self._accept_beacon) - @template('weather.html') async def _accept_beacon(self, request): try: diff --git a/app/contacts/contact_http.py b/app/contacts/contact_http.py index 7669f959b..01b1589e0 100644 --- a/app/contacts/contact_http.py +++ b/app/contacts/contact_http.py @@ -18,7 +18,6 @@ async def start(self): self.app_svc.application.router.add_route('POST', '/beacon', self._beacon) - async def _beacon(self, request): try: profile = json.loads(self.contact_svc.decode_bytes(await request.read())) diff --git a/app/contacts/contact_slack.py b/app/contacts/contact_slack.py index 632236df2..e006763bc 100644 --- a/app/contacts/contact_slack.py +++ b/app/contacts/contact_slack.py @@ -151,7 +151,6 @@ async def get_uploads(self): return [] - async def _send_instructions(self, agent, instructions): response = dict(paw=agent.paw, sleep=await agent.calculate_sleep(), diff --git a/app/contacts/contact_tcp.py b/app/contacts/contact_tcp.py index 6c0decfc6..f6234fcce 100644 --- a/app/contacts/contact_tcp.py +++ b/app/contacts/contact_tcp.py @@ -94,7 +94,6 @@ async def send(self, session_id: int, cmd: str, timeout: int = 60) -> Tuple[int, return 1, '~$ ', str(e), '' - @staticmethod async def _handshake(reader): profile_bites = (await reader.readline()).strip() diff --git a/app/objects/c_agent.py b/app/objects/c_agent.py index ae3cc2af2..c9117d891 100644 --- a/app/objects/c_agent.py +++ b/app/objects/c_agent.py @@ -333,7 +333,6 @@ def assign_pending_executor_change(self): return executor_change - def _replace_payload_data(self, decoded_cmd, file_svc): for uuid in re.findall(self.RESERVED['payload'], decoded_cmd): if self.is_uuid4(uuid): diff --git a/app/objects/c_operation.py b/app/objects/c_operation.py index 44900e5d3..12a8c32d1 100644 --- a/app/objects/c_operation.py +++ b/app/objects/c_operation.py @@ -338,7 +338,6 @@ async def write_event_logs_to_disk(self, file_svc, data_svc, output=False): logging.debug('Wrote event logs for operation %s to disk at %s/%s' % (self.name, event_logs_dir, file_name)) - async def _write_logs_to_disk(self, logs, file_name, dest_dir, file_svc): logs_dumps = json.dumps(logs) await file_svc.save_file(file_name, logs_dumps.encode(), dest_dir, encrypt=False) diff --git a/app/objects/c_planner.py b/app/objects/c_planner.py index 03cb51e5a..ad281ade6 100644 --- a/app/objects/c_planner.py +++ b/app/objects/c_planner.py @@ -63,7 +63,6 @@ async def which_plugin(self): return None - @staticmethod def _set_stopping_conditions(conditions): if conditions: diff --git a/app/objects/c_plugin.py b/app/objects/c_plugin.py index f6dbad7f5..0a18238c6 100644 --- a/app/objects/c_plugin.py +++ b/app/objects/c_plugin.py @@ -85,7 +85,6 @@ async def expand(self, services): logging.error('Error expanding plugin=%s, %s' % (self.name, e)) - def _load_module(self): try: return import_module('plugins.%s.hook' % self.name) diff --git a/app/objects/secondclass/c_link.py b/app/objects/secondclass/c_link.py index b59a0d8bc..ac41464fe 100644 --- a/app/objects/secondclass/c_link.py +++ b/app/objects/secondclass/c_link.py @@ -224,7 +224,6 @@ def replace_origin_link_id(self): self.command = self.encode_string(decoded_cmd.replace(self.RESERVED['origin_link_id'], self.id)) - def _emit_status_change_event(self, from_status, to_status): event_svc = BaseService.get_service('event_svc') diff --git a/app/service/app_svc.py b/app/service/app_svc.py index 814a41c56..02316afa5 100644 --- a/app/service/app_svc.py +++ b/app/service/app_svc.py @@ -194,7 +194,6 @@ def get_loaded_plugins(self): return tuple(self._loaded_plugins) - async def _save_configurations(self, main_config_file='default'): for cfg_name, cfg_file in [('main', main_config_file), ('agents', 'agents'), ('payloads', 'payloads')]: with open('conf/%s.yml' % cfg_file, 'w') as config: diff --git a/app/service/contact_svc.py b/app/service/contact_svc.py index 7a0b3bdff..aadaafd18 100644 --- a/app/service/contact_svc.py +++ b/app/service/contact_svc.py @@ -94,7 +94,6 @@ async def get_tunnel(self, name): return tunnel[0] if len(tunnel) > 0 else None - async def _sanitize_paw(self, input_paw): """ Remove any characters from the given paw that do not fall in the following set: diff --git a/app/service/data_svc.py b/app/service/data_svc.py index 1fcae09f2..4d189147a 100644 --- a/app/service/data_svc.py +++ b/app/service/data_svc.py @@ -242,7 +242,6 @@ async def load_yaml_file(self, object_class, filename, access): await self.store(obj) - async def _load(self, plugins=()): try: async_tasks = [] diff --git a/app/service/file_svc.py b/app/service/file_svc.py index dd438d272..927c1c246 100644 --- a/app/service/file_svc.py +++ b/app/service/file_svc.py @@ -202,7 +202,6 @@ def add_xored_extension(filename): return '%s.xored' % filename - def _save(self, filename, content, encrypt=True): if encrypt and (self.encryptor and self.encrypt_output): content = bytes(FILE_ENCRYPTION_FLAG, 'utf-8') + self.encryptor.encrypt(content) diff --git a/app/service/learning_svc.py b/app/service/learning_svc.py index 0c477d3a9..54c805a7c 100644 --- a/app/service/learning_svc.py +++ b/app/service/learning_svc.py @@ -51,7 +51,6 @@ async def learn(self, facts, link, blob, operation=None): await self._store_results(link, found_facts, operation) - @staticmethod async def _save_fact(link, facts, fact, operation=None): fact.origin_type = OriginType.LEARNED diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index beca54663..22e9baca5 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -275,7 +275,6 @@ async def sort_links(links): return sorted(links, key=lambda k: (-k.score)) - async def _stop_bucket_exhaustion(self, planner, operation, condition_stop): """Determine whether to continue running the bucket. diff --git a/app/service/rest_svc.py b/app/service/rest_svc.py index 1686da37e..be799316e 100644 --- a/app/service/rest_svc.py +++ b/app/service/rest_svc.py @@ -342,7 +342,6 @@ async def build_potential_links(self, operation, agents, abilities): return await self.get_service('planning_svc').sort_links(potential_links) - async def _build_operation_object(self, access, data): name = data.pop('name') group = data.pop('group', '') diff --git a/app/utility/base_planning_svc.py b/app/utility/base_planning_svc.py index 907b1bd1b..25058c6fe 100644 --- a/app/utility/base_planning_svc.py +++ b/app/utility/base_planning_svc.py @@ -167,7 +167,6 @@ async def obfuscate_commands(self, agent, obfuscator, links): return links - @staticmethod def _list_historic_duplicate_singletons(operation): """ From 719615633887b3b485a5b34f60ab01a3528fd149 Mon Sep 17 00:00:00 2001 From: Adam Gaudreau Date: Mon, 29 Nov 2021 15:03:51 -0500 Subject: [PATCH 6/9] Fix tests --- app/api/packs/advanced.py | 1 - app/api/packs/campaign.py | 2 -- app/api/rest_api.py | 3 --- app/api/v2/handlers/base_object_api.py | 6 ------ app/api/v2/handlers/operation_api.py | 4 ---- app/api/v2/managers/ability_api_manager.py | 1 - app/api/v2/managers/base_api_manager.py | 6 ------ app/api/v2/managers/operation_api_manager.py | 1 - app/contacts/contact_html.py | 1 - app/contacts/contact_http.py | 1 - app/contacts/contact_slack.py | 1 - app/contacts/contact_tcp.py | 1 - app/objects/c_agent.py | 1 - app/objects/c_operation.py | 1 - app/objects/c_planner.py | 1 - app/objects/c_plugin.py | 1 - app/objects/secondclass/c_link.py | 1 - app/service/app_svc.py | 1 - app/service/contact_svc.py | 1 - app/service/data_svc.py | 1 - app/service/file_svc.py | 1 - app/service/learning_svc.py | 1 - app/service/planning_svc.py | 1 - app/service/rest_svc.py | 1 - app/utility/base_planning_svc.py | 1 - 25 files changed, 41 deletions(-) diff --git a/app/api/packs/advanced.py b/app/api/packs/advanced.py index b354119ef..70e0c195c 100644 --- a/app/api/packs/advanced.py +++ b/app/api/packs/advanced.py @@ -22,7 +22,6 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/advanced/configurations', self._section_configurations) self.app_svc.application.router.add_route('GET', '/advanced/exfills', self._section_exfil_files) - @check_authorization @template('planners.html') async def _section_planners(self, request): diff --git a/app/api/packs/campaign.py b/app/api/packs/campaign.py index 113de9559..6e4099350 100644 --- a/app/api/packs/campaign.py +++ b/app/api/packs/campaign.py @@ -21,7 +21,6 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/campaign/profiles', self._section_profiles) self.app_svc.application.router.add_route('GET', '/campaign/operations', self._section_operations) - @check_authorization @template('agents.html') async def _section_agent(self, request): @@ -109,7 +108,6 @@ def load_usage_markdown(header): return dict(operations=operations, groups=groups, adversaries=adversaries, sources=sources, planners=planners, obfuscators=obfuscators, usage=usage) - @staticmethod def _rollup_abilities(abilities): rolled = defaultdict(list) diff --git a/app/api/rest_api.py b/app/api/rest_api.py index b7d0b2de0..63bedbd16 100644 --- a/app/api/rest_api.py +++ b/app/api/rest_api.py @@ -43,7 +43,6 @@ async def enable(self): self.app_svc.application.router.add_route('GET', '/api/{index}', self.rest_core_info) self.app_svc.application.router.add_route('GET', '/file/download_exfil', self.download_exfil_file) - @template('login.html', status=401) async def login(self, request): return dict() @@ -64,7 +63,6 @@ async def landing(self, request): data = dict(plugins=[p.display for p in plugins], errors=self.app_svc.errors + self._request_errors(request)) return render_template('%s.html' % access[0].name, request, data) - @check_authorization async def rest_core(self, request): try: @@ -163,7 +161,6 @@ def is_in_exfil_dir(f): return web.HTTPNotFound(body=str(e)) return web.HTTPBadRequest(body='A file needs to be specified for download') - @staticmethod def _request_errors(request): errors = [] diff --git a/app/api/v2/handlers/base_object_api.py b/app/api/v2/handlers/base_object_api.py index e0181ba2b..63660928c 100644 --- a/app/api/v2/handlers/base_object_api.py +++ b/app/api/v2/handlers/base_object_api.py @@ -23,7 +23,6 @@ def __init__(self, description, obj_class, schema, ram_key, id_property, auth_sv def add_routes(self, app: web.Application): pass - async def get_all_objects(self, request: web.Request): access = await self.get_request_permissions(request) @@ -47,7 +46,6 @@ async def get_object(self, request: web.Request): return self._api_manager.dump_object_with_filters(obj, include, exclude) - async def create_object(self, request: web.Request): data = await request.json() @@ -73,7 +71,6 @@ async def _error_if_object_with_id_exists(self, obj_id: str): if self._api_manager.find_object(self.ram_key, search): raise JsonHttpBadRequest(f'{self.description.capitalize()} with given id already exists: {obj_id}') - async def update_object(self, request: web.Request): data, access, obj_id, query, search = await self._parse_common_data_from_request(request) @@ -91,7 +88,6 @@ async def update_on_disk_object(self, request: web.Request): raise JsonHttpNotFound(f'{self.description.capitalize()} not found: {obj_id}') return obj - async def create_or_update_object(self, request: web.Request): data, access, obj_id, query, search = await self._parse_common_data_from_request(request) @@ -116,7 +112,6 @@ async def create_or_update_on_disk_object(self, request: web.Request): return obj - async def delete_object(self, request: web.Request): obj_id = request.match_info.get(self.id_property) @@ -136,7 +131,6 @@ async def delete_on_disk_object(self, request: web.Request): obj_id = request.match_info.get(self.id_property) await self._api_manager.remove_object_from_disk_by_id(identifier=obj_id, ram_key=self.ram_key) - async def _parse_common_data_from_request(self, request) -> (dict, dict, str, dict, dict): data = {} raw_body = await request.read() diff --git a/app/api/v2/handlers/operation_api.py b/app/api/v2/handlers/operation_api.py index e673da055..1acd811b2 100644 --- a/app/api/v2/handlers/operation_api.py +++ b/app/api/v2/handlers/operation_api.py @@ -163,8 +163,6 @@ async def get_potential_links_by_paw(self, request: web.Request): potential_links = await self._api_manager.get_potential_links(operation_id, access, paw) return web.json_response(potential_links) - - async def create_object(self, request: web.Request): data = await request.json() await self._error_if_object_with_id_exists(data.get(self.id_property)) @@ -178,8 +176,6 @@ async def update_object(self, request: web.Request): raise JsonHttpNotFound(f'{self.description.capitalize()} not found: {obj_id}') return obj - - async def _read_output_parameter_(self, request: web.Request): raw_body = await request.read() output = False diff --git a/app/api/v2/managers/ability_api_manager.py b/app/api/v2/managers/ability_api_manager.py index 83d17b685..1bef993b3 100644 --- a/app/api/v2/managers/ability_api_manager.py +++ b/app/api/v2/managers/ability_api_manager.py @@ -45,7 +45,6 @@ async def update_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pro await self._save_and_reload_object(file_path, existing_obj_data, obj_class, obj.access) return next(self.find_objects(ram_key, {id_property: obj_id})) - def _validate_ability_data(self, create: bool, data: dict): # Correct ability_id key for ability file saving. data['id'] = data.pop('ability_id', '') diff --git a/app/api/v2/managers/base_api_manager.py b/app/api/v2/managers/base_api_manager.py index 1e19f13df..9cb570e4a 100644 --- a/app/api/v2/managers/base_api_manager.py +++ b/app/api/v2/managers/base_api_manager.py @@ -23,7 +23,6 @@ def __init__(self, data_svc, file_svc, logger=None): def log(self): return self._log - def find_objects(self, ram_key: str, search: dict = None): """Find objects matching the given criteria""" for obj in self._data_svc.ram[ram_key]: @@ -53,7 +52,6 @@ def dump_object_with_filters(obj: Any, include: List[str] = None, exclude: List[ dumped.pop(exclude_attribute, None) return dumped - def create_object_from_schema(self, schema: SchemaMeta, data: dict, access: BaseWorld.Access): obj_schema = schema() obj = obj_schema.load(data) @@ -77,7 +75,6 @@ def _get_allowed_from_access(self, access) -> BaseWorld.Access: else: return self._data_svc.Access.RED - def find_and_update_object(self, ram_key: str, data: dict, search: dict = None): for obj in self.find_objects(ram_key, search): new_obj = self.update_object(obj, data) @@ -109,7 +106,6 @@ async def update_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pro await self._save_and_reload_object(file_path, existing_obj_data, obj_class, obj.access) return next(self.find_objects(ram_key, {id_property: obj_id})) - async def replace_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_property: str): obj_id = getattr(obj, id_property) file_path = await self._get_existing_object_file_path(obj_id, ram_key) @@ -117,7 +113,6 @@ async def replace_on_disk_object(self, obj: Any, data: dict, ram_key: str, id_pr await self._save_and_reload_object(file_path, data, type(obj), obj.access) return next(self.find_objects(ram_key, {id_property: obj_id})) - async def remove_object_from_memory_by_id(self, identifier: str, ram_key: str, id_property: str): await self._data_svc.remove(ram_key, {id_property: identifier}) @@ -127,7 +122,6 @@ async def remove_object_from_disk_by_id(self, identifier: str, ram_key: str): if os.path.exists(file_path): os.remove(file_path) - @staticmethod async def _get_new_object_file_path(identifier: str, ram_key: str) -> str: """Create file path for new object""" diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index 0453d1684..24b5a6799 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -122,7 +122,6 @@ async def get_potential_links(self, operation_id: str, access: dict, paw: str = potential_links = [potential_link.display for potential_link in operation.potential_links] return potential_links - async def get_operation_object(self, operation_id: str, access: dict): try: operation = (await self._data_svc.locate('operations', {'id': operation_id}))[0] diff --git a/app/contacts/contact_html.py b/app/contacts/contact_html.py index b2cb244c3..bdefa63d9 100644 --- a/app/contacts/contact_html.py +++ b/app/contacts/contact_html.py @@ -16,7 +16,6 @@ def __init__(self, services): async def start(self): self.app_svc.application.router.add_route('*', self.get_config('app.contact.html'), self._accept_beacon) - @template('weather.html') async def _accept_beacon(self, request): try: diff --git a/app/contacts/contact_http.py b/app/contacts/contact_http.py index 01b1589e0..9650a9544 100644 --- a/app/contacts/contact_http.py +++ b/app/contacts/contact_http.py @@ -17,7 +17,6 @@ def __init__(self, services): async def start(self): self.app_svc.application.router.add_route('POST', '/beacon', self._beacon) - async def _beacon(self, request): try: profile = json.loads(self.contact_svc.decode_bytes(await request.read())) diff --git a/app/contacts/contact_slack.py b/app/contacts/contact_slack.py index e006763bc..e194c03bc 100644 --- a/app/contacts/contact_slack.py +++ b/app/contacts/contact_slack.py @@ -150,7 +150,6 @@ async def get_uploads(self): self.log.error('Receiving file uploads over c2 (%s) failed: %s' % (self.__class__.__name__, e)) return [] - async def _send_instructions(self, agent, instructions): response = dict(paw=agent.paw, sleep=await agent.calculate_sleep(), diff --git a/app/contacts/contact_tcp.py b/app/contacts/contact_tcp.py index f6234fcce..4ad5044fc 100644 --- a/app/contacts/contact_tcp.py +++ b/app/contacts/contact_tcp.py @@ -93,7 +93,6 @@ async def send(self, session_id: int, cmd: str, timeout: int = 60) -> Tuple[int, self.log.exception(e) return 1, '~$ ', str(e), '' - @staticmethod async def _handshake(reader): profile_bites = (await reader.readline()).strip() diff --git a/app/objects/c_agent.py b/app/objects/c_agent.py index c9117d891..008c083d6 100644 --- a/app/objects/c_agent.py +++ b/app/objects/c_agent.py @@ -332,7 +332,6 @@ def assign_pending_executor_change(self): self._executor_change_to_assign = None return executor_change - def _replace_payload_data(self, decoded_cmd, file_svc): for uuid in re.findall(self.RESERVED['payload'], decoded_cmd): if self.is_uuid4(uuid): diff --git a/app/objects/c_operation.py b/app/objects/c_operation.py index 12a8c32d1..9ec052a44 100644 --- a/app/objects/c_operation.py +++ b/app/objects/c_operation.py @@ -337,7 +337,6 @@ async def write_event_logs_to_disk(self, file_svc, data_svc, output=False): await self._write_logs_to_disk(event_logs, file_name, event_logs_dir, file_svc) logging.debug('Wrote event logs for operation %s to disk at %s/%s' % (self.name, event_logs_dir, file_name)) - async def _write_logs_to_disk(self, logs, file_name, dest_dir, file_svc): logs_dumps = json.dumps(logs) await file_svc.save_file(file_name, logs_dumps.encode(), dest_dir, encrypt=False) diff --git a/app/objects/c_planner.py b/app/objects/c_planner.py index ad281ade6..0d371f864 100644 --- a/app/objects/c_planner.py +++ b/app/objects/c_planner.py @@ -62,7 +62,6 @@ async def which_plugin(self): return plugin return None - @staticmethod def _set_stopping_conditions(conditions): if conditions: diff --git a/app/objects/c_plugin.py b/app/objects/c_plugin.py index 0a18238c6..5c5308f1f 100644 --- a/app/objects/c_plugin.py +++ b/app/objects/c_plugin.py @@ -84,7 +84,6 @@ async def expand(self, services): except Exception as e: logging.error('Error expanding plugin=%s, %s' % (self.name, e)) - def _load_module(self): try: return import_module('plugins.%s.hook' % self.name) diff --git a/app/objects/secondclass/c_link.py b/app/objects/secondclass/c_link.py index ac41464fe..65745bb89 100644 --- a/app/objects/secondclass/c_link.py +++ b/app/objects/secondclass/c_link.py @@ -223,7 +223,6 @@ def replace_origin_link_id(self): decoded_cmd = self.decode_bytes(self.command) self.command = self.encode_string(decoded_cmd.replace(self.RESERVED['origin_link_id'], self.id)) - def _emit_status_change_event(self, from_status, to_status): event_svc = BaseService.get_service('event_svc') diff --git a/app/service/app_svc.py b/app/service/app_svc.py index 02316afa5..0d5fee86f 100644 --- a/app/service/app_svc.py +++ b/app/service/app_svc.py @@ -193,7 +193,6 @@ def register_subapp(self, path: str, app: web.Application): def get_loaded_plugins(self): return tuple(self._loaded_plugins) - async def _save_configurations(self, main_config_file='default'): for cfg_name, cfg_file in [('main', main_config_file), ('agents', 'agents'), ('payloads', 'payloads')]: with open('conf/%s.yml' % cfg_file, 'w') as config: diff --git a/app/service/contact_svc.py b/app/service/contact_svc.py index aadaafd18..511bcae4a 100644 --- a/app/service/contact_svc.py +++ b/app/service/contact_svc.py @@ -93,7 +93,6 @@ async def get_tunnel(self, name): tunnel = [t for t in self.tunnels if t.name == name] return tunnel[0] if len(tunnel) > 0 else None - async def _sanitize_paw(self, input_paw): """ Remove any characters from the given paw that do not fall in the following set: diff --git a/app/service/data_svc.py b/app/service/data_svc.py index 4d189147a..6243dfafd 100644 --- a/app/service/data_svc.py +++ b/app/service/data_svc.py @@ -241,7 +241,6 @@ async def load_yaml_file(self, object_class, filename, access): obj.access = access await self.store(obj) - async def _load(self, plugins=()): try: async_tasks = [] diff --git a/app/service/file_svc.py b/app/service/file_svc.py index 927c1c246..b30d973ae 100644 --- a/app/service/file_svc.py +++ b/app/service/file_svc.py @@ -201,7 +201,6 @@ def add_xored_extension(filename): return filename return '%s.xored' % filename - def _save(self, filename, content, encrypt=True): if encrypt and (self.encryptor and self.encrypt_output): content = bytes(FILE_ENCRYPTION_FLAG, 'utf-8') + self.encryptor.encrypt(content) diff --git a/app/service/learning_svc.py b/app/service/learning_svc.py index 54c805a7c..4777c822d 100644 --- a/app/service/learning_svc.py +++ b/app/service/learning_svc.py @@ -50,7 +50,6 @@ async def learn(self, facts, link, blob, operation=None): await update_scores(operation=None, increment=len(found_facts), used=facts, facts=link.facts) await self._store_results(link, found_facts, operation) - @staticmethod async def _save_fact(link, facts, fact, operation=None): fact.origin_type = OriginType.LEARNED diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index 22e9baca5..e8ffef371 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -274,7 +274,6 @@ async def sort_links(links): """ return sorted(links, key=lambda k: (-k.score)) - async def _stop_bucket_exhaustion(self, planner, operation, condition_stop): """Determine whether to continue running the bucket. diff --git a/app/service/rest_svc.py b/app/service/rest_svc.py index be799316e..54af68897 100644 --- a/app/service/rest_svc.py +++ b/app/service/rest_svc.py @@ -341,7 +341,6 @@ async def build_potential_links(self, operation, agents, abilities): potential_links.append(pl) return await self.get_service('planning_svc').sort_links(potential_links) - async def _build_operation_object(self, access, data): name = data.pop('name') group = data.pop('group', '') diff --git a/app/utility/base_planning_svc.py b/app/utility/base_planning_svc.py index 25058c6fe..eb0a481e3 100644 --- a/app/utility/base_planning_svc.py +++ b/app/utility/base_planning_svc.py @@ -166,7 +166,6 @@ async def obfuscate_commands(self, agent, obfuscator, links): s_link.command = self.encode_string(mod.run(s_link)) return links - @staticmethod def _list_historic_duplicate_singletons(operation): """ From b478416d0f83ec244f42b5e7de9c822162ebfc8f Mon Sep 17 00:00:00 2001 From: Adam Gaudreau Date: Mon, 29 Nov 2021 15:30:15 -0500 Subject: [PATCH 7/9] Add plugin field to adversaries, abilities, and planners (#2345) * Add plugin field to adversaries, abilities, sources, and planners --- app/objects/c_ability.py | 13 +++++-------- app/objects/c_adversary.py | 13 +++++-------- app/objects/c_planner.py | 13 +++++-------- app/objects/c_source.py | 5 ++++- app/service/data_svc.py | 14 +++++++++++--- static/js/core.js | 2 -- templates/BLUE.html | 2 +- templates/RED.html | 2 +- tests/api/v2/handlers/test_abilities_api.py | 9 +++++---- tests/api/v2/handlers/test_adversaries_api.py | 6 ++++-- tests/api/v2/handlers/test_planners_api.py | 2 +- tests/api/v2/handlers/test_sources_api.py | 4 +++- tests/services/test_rest_svc.py | 8 ++++---- 13 files changed, 49 insertions(+), 44 deletions(-) diff --git a/app/objects/c_ability.py b/app/objects/c_ability.py index d4dd8cf1c..51283cda2 100644 --- a/app/objects/c_ability.py +++ b/app/objects/c_ability.py @@ -1,5 +1,4 @@ import collections -import os import uuid import marshmallow as ma @@ -8,7 +7,6 @@ from app.objects.secondclass.c_executor import ExecutorSchema from app.objects.secondclass.c_requirement import RequirementSchema from app.utility.base_object import BaseObject -from app.utility.base_service import BaseService from app.utility.base_world import AccessSchema @@ -27,6 +25,7 @@ class AbilitySchema(ma.Schema): additional_info = ma.fields.Dict(keys=ma.fields.String(), values=ma.fields.String()) access = ma.fields.Nested(AccessSchema, missing=None) singleton = ma.fields.Bool(missing=None) + plugin = ma.fields.String(missing=None) @ma.pre_load def fix_id(self, data, **_): @@ -58,7 +57,7 @@ def executors(self): def __init__(self, ability_id='', name=None, description=None, tactic=None, technique_id=None, technique_name=None, executors=(), requirements=None, privilege=None, repeatable=False, buckets=None, access=None, - additional_info=None, tags=None, singleton=False, **kwargs): + additional_info=None, tags=None, singleton=False, plugin='', **kwargs): super().__init__() self.ability_id = ability_id if ability_id else str(uuid.uuid4()) self.tactic = tactic.lower() if tactic else None @@ -80,6 +79,7 @@ def __init__(self, ability_id='', name=None, description=None, tactic=None, tech self.additional_info = additional_info or dict() self.additional_info.update(**kwargs) self.tags = set(tags) if tags else set() + self.plugin = plugin def __getattr__(self, item): try: @@ -103,14 +103,11 @@ def store(self, ram): existing.update('buckets', self.buckets) existing.update('tags', self.tags) existing.update('singleton', self.singleton) + existing.update('plugin', self.plugin) return existing async def which_plugin(self): - file_svc = BaseService.get_service('file_svc') - for plugin in os.listdir('plugins'): - if await file_svc.walk_file_path(os.path.join('plugins', plugin, 'data', ''), '%s.yml' % self.ability_id): - return plugin - return None + return self.plugin def find_executor(self, name, platform): return self._executor_map.get(self._make_executor_map_key(name, platform)) diff --git a/app/objects/c_adversary.py b/app/objects/c_adversary.py index de8dfab7a..32c5114b5 100644 --- a/app/objects/c_adversary.py +++ b/app/objects/c_adversary.py @@ -1,11 +1,9 @@ -import os import uuid import marshmallow as ma from app.objects.interfaces.i_object import FirstClassObjectInterface from app.utility.base_object import BaseObject -from app.utility.base_service import BaseService DEFAULT_OBJECTIVE_ID = '495a9828-cab1-44dd-a0ca-66e58177d8cc' @@ -20,6 +18,7 @@ class AdversarySchema(ma.Schema): objective = ma.fields.String() tags = ma.fields.List(ma.fields.String(), allow_none=True) has_repeatable_abilities = ma.fields.Boolean(dump_only=True) + plugin = ma.fields.String(missing=None) @ma.pre_load def fix_id(self, adversary, **_): @@ -57,7 +56,7 @@ class Adversary(FirstClassObjectInterface, BaseObject): def unique(self): return self.hash('%s' % self.adversary_id) - def __init__(self, name='', adversary_id='', description='', atomic_ordering=(), objective='', tags=None): + def __init__(self, name='', adversary_id='', description='', atomic_ordering=(), objective='', tags=None, plugin=''): super().__init__() self.adversary_id = adversary_id if adversary_id else str(uuid.uuid4()) self.name = name @@ -66,6 +65,7 @@ def __init__(self, name='', adversary_id='', description='', atomic_ordering=(), self.objective = objective or DEFAULT_OBJECTIVE_ID self.tags = set(tags) if tags else set() self.has_repeatable_abilities = False + self.plugin = plugin def store(self, ram): existing = self.retrieve(ram['adversaries'], self.unique) @@ -78,6 +78,7 @@ def store(self, ram): existing.update('objective', self.objective) existing.update('tags', self.tags) existing.update('has_repeatable_abilities', self.check_repeatable_abilities(ram['abilities'])) + existing.update('plugin', self.plugin) return existing def verify(self, log, abilities, objectives): @@ -101,11 +102,7 @@ def has_ability(self, ability): return False async def which_plugin(self): - file_svc = BaseService.get_service('file_svc') - for plugin in os.listdir('plugins'): - if await file_svc.walk_file_path(os.path.join('plugins', plugin, 'data', ''), '%s.yml' % self.adversary_id): - return plugin - return None + return self.plugin def check_repeatable_abilities(self, ability_list): return any(ab.repeatable for ab_id in self.atomic_ordering for ab in ability_list if ab.ability_id == ab_id) diff --git a/app/objects/c_planner.py b/app/objects/c_planner.py index 0d371f864..15d6e062f 100644 --- a/app/objects/c_planner.py +++ b/app/objects/c_planner.py @@ -1,11 +1,9 @@ -import os import uuid import marshmallow as ma from app.objects.interfaces.i_object import FirstClassObjectInterface from app.utility.base_object import BaseObject -from app.utility.base_service import BaseService from app.objects.secondclass.c_fact import Fact, FactSchema @@ -18,6 +16,7 @@ class PlannerSchema(ma.Schema): stopping_conditions = ma.fields.List(ma.fields.Nested(FactSchema())) ignore_enforcement_modules = ma.fields.List(ma.fields.String()) allow_repeatable_abilities = ma.fields.Boolean() + plugin = ma.fields.String(missing=None) @ma.post_load() def build_planner(self, data, **kwargs): @@ -34,7 +33,7 @@ def unique(self): return self.hash(self.name) def __init__(self, name='', planner_id='', module='', params=None, stopping_conditions=None, description=None, - ignore_enforcement_modules=(), allow_repeatable_abilities=False): + ignore_enforcement_modules=(), allow_repeatable_abilities=False, plugin=''): super().__init__() self.name = name self.planner_id = planner_id if planner_id else str(uuid.uuid4()) @@ -44,6 +43,7 @@ def __init__(self, name='', planner_id='', module='', params=None, stopping_cond self.stopping_conditions = self._set_stopping_conditions(stopping_conditions) self.ignore_enforcement_modules = ignore_enforcement_modules self.allow_repeatable_abilities = allow_repeatable_abilities + self.plugin = plugin def store(self, ram): existing = self.retrieve(ram['planners'], self.unique) @@ -53,14 +53,11 @@ def store(self, ram): else: existing.update('stopping_conditions', self.stopping_conditions) existing.update('params', self.params) + existing.update('plugin', self.plugin) return existing async def which_plugin(self): - file_svc = BaseService.get_service('file_svc') - for plugin in os.listdir('plugins'): - if await file_svc.walk_file_path(os.path.join('plugins', plugin, 'data', ''), '%s.yml' % self.planner_id): - return plugin - return None + return self.plugin @staticmethod def _set_stopping_conditions(conditions): diff --git a/app/objects/c_source.py b/app/objects/c_source.py index 755553074..f8dd33624 100644 --- a/app/objects/c_source.py +++ b/app/objects/c_source.py @@ -33,6 +33,7 @@ class SourceSchema(ma.Schema): rules = ma.fields.List(ma.fields.Nested(RuleSchema)) adjustments = ma.fields.List(ma.fields.Nested(AdjustmentSchema)) relationships = ma.fields.List(ma.fields.Nested(RelationshipSchema)) + plugin = ma.fields.String(missing=None) @ma.pre_load def fix_adjustments(self, in_data, **_): @@ -81,7 +82,7 @@ class Source(FirstClassObjectInterface, BaseObject): def unique(self): return self.hash('%s' % self.id) - def __init__(self, name='', id='', facts=(), relationships=(), rules=(), adjustments=()): + def __init__(self, name='', id='', facts=(), relationships=(), rules=(), adjustments=(), plugin=''): super().__init__() self.id = id if id else str(uuid.uuid4()) self.name = name @@ -89,6 +90,7 @@ def __init__(self, name='', id='', facts=(), relationships=(), rules=(), adjustm self.rules = rules self.adjustments = adjustments self.relationships = relationships + self.plugin = plugin def store(self, ram): existing = self.retrieve(ram['sources'], self.unique) @@ -99,4 +101,5 @@ def store(self, ram): existing.update('facts', self.facts) existing.update('rules', self.rules) existing.update('relationships', self.relationships) + existing.update('plugin', self.plugin) return existing diff --git a/app/service/data_svc.py b/app/service/data_svc.py index 6243dfafd..5434ed1cf 100644 --- a/app/service/data_svc.py +++ b/app/service/data_svc.py @@ -7,6 +7,7 @@ import tarfile import shutil import warnings +import pathlib from importlib import import_module from app.objects.c_ability import Ability @@ -160,6 +161,8 @@ async def load_ability_file(self, filename, access): requirements = await self._load_ability_requirements(ab.pop('requirements', [])) buckets = ab.pop('buckets', [tactic]) ab.pop('access', None) + plugin = self._get_plugin_name(filename) + ab.pop('plugin', plugin) if tactic and tactic not in filename: self.log.error('Ability=%s has wrong tactic' % id) @@ -167,7 +170,7 @@ async def load_ability_file(self, filename, access): await self._create_ability(ability_id=ability_id, name=name, description=description, tactic=tactic, technique_id=technique_id, technique_name=technique_name, executors=executors, requirements=requirements, privilege=privilege, - repeatable=repeatable, buckets=buckets, access=access, singleton=singleton, + repeatable=repeatable, buckets=buckets, access=access, singleton=singleton, plugin=plugin, **ab) async def convert_v0_ability_executor(self, ability_data: dict): @@ -239,6 +242,7 @@ async def load_yaml_file(self, object_class, filename, access): for src in self.strip_yml(filename): obj = object_class.load(src) obj.access = access + obj.plugin = self._get_plugin_name(filename) await self.store(obj) async def _load(self, plugins=()): @@ -336,11 +340,11 @@ async def _load_data_encoders(self, plugins): async def _create_ability(self, ability_id, name=None, description=None, tactic=None, technique_id=None, technique_name=None, executors=None, requirements=None, privilege=None, - repeatable=False, buckets=None, access=None, singleton=False, **kwargs): + repeatable=False, buckets=None, access=None, singleton=False, plugin='', **kwargs): ability = Ability(ability_id=ability_id, name=name, description=description, tactic=tactic, technique_id=technique_id, technique_name=technique_name, executors=executors, requirements=requirements, privilege=privilege, repeatable=repeatable, buckets=buckets, - access=access, singleton=singleton, **kwargs) + access=access, singleton=singleton, plugin=plugin, **kwargs) return await self.store(ability) async def _prune_non_critical_data(self): @@ -410,3 +414,7 @@ async def _verify_default_objective_exists(self): async def _verify_adversary_profiles(self): for adv in await self.locate('adversaries'): adv.verify(log=self.log, abilities=self.ram['abilities'], objectives=self.ram['objectives']) + + def _get_plugin_name(self, filename): + plugin_path = pathlib.PurePath(filename).parts + return plugin_path[1] if 'plugins' in plugin_path else '' diff --git a/static/js/core.js b/static/js/core.js index aeb8a6d15..559fd2377 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -28,8 +28,6 @@ function alpineCore() { if (tabName === 'fieldmanual') { restRequest('GET', null, (data) => { this.setTabContent({ name: tabName, contentID: `tab-${tabName}`, address: address }, data); }, address); return; - } else if (tabName === 'stockpile' || tabName === 'atomic') { - return; } // If tab is already open, jump to it diff --git a/templates/BLUE.html b/templates/BLUE.html index d0febd1bd..80cb01ce0 100644 --- a/templates/BLUE.html +++ b/templates/BLUE.html @@ -46,7 +46,7 @@