diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 70d7508..9de8342 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/phantomcyber/dev-cicd-tools - rev: v1.16 + rev: v1.23 hooks: - id: org-hook - id: package-app-dependencies - repo: https://github.com/Yelp/detect-secrets - rev: v1.4.0 + rev: v1.5.0 hooks: - id: detect-secrets args: ['--no-verify'] diff --git a/LICENSE b/LICENSE index a901ecd..5c6d44e 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (c) Trend Micro, 2022-2023 + Copyright (c) Trend Micro, 2022-2024 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 00905a0..73fc834 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ # Trend Vision One for Splunk SOAR Publisher: Trend Micro -Connector Version: 2.2.1 +Connector Version: 2.3.0 Product Vendor: Trend Micro Product Name: VisionOne Product Version Supported (regex): ".\*" -Minimum Product Version: 6.1.1 +Minimum Product Version: 6.2.2 Trend Vision One is a purpose-built threat defense platform that provides added value and new benefits beyond XDR solutions, allowing you to see more and respond faster. Providing deep and broad extended detection and response (XDR) capabilities that collect and automatically correlate data across multiple security layers—email, endpoints, servers, cloud workloads, and networks—Trend Vision One prevents the majority of attacks with automated protection @@ -97,6 +97,7 @@ Configure Trend Vision One on Splunk SOAR [Collect Forensic File](#action-collect-forensic-file) \- Collect forensic file [Forensic File Info](#action-forensic-file-info) \- Get the download information for collected forensic file [Start Analysis](#action-start-analysis) \- Submit file to sandbox for analysis. For supported file types, check [here](https://docs.trendmicro.com/en-us/enterprise/trend-vision-one-olh/threat-intelligence-/sandbox-analysis/sandbox-supported-fi.aspx) +[Vault Sandbox Analysis](#action-vault-sandbox-analysis) \- Submit file from Splunk vault to sandbox for analysis. For supported file types, check [here](https://docs.trendmicro.com/en-us/enterprise/trend-vision-one-olh/threat-intelligence-/sandbox-analysis/sandbox-supported-fi.aspx) [Add Note](#action-add-note) \- Adds a note to an existing workbench alert [Update Status](#action-update-status) \- Updates the status of an existing workbench alert [Get Alert Details](#action-get-alert-details) \- Displays information about the specified alert @@ -1428,6 +1429,49 @@ Authentication Information The app uses HTTPS protocol for communicating with the Trend Vision One server. For authentication a Vision One API Token is used by the Splunk SOAR Connector. +Action: Vault Sandbox Analysis +---------------------- + +Submit file from vault to sandbox for analysis. + +**API key role permissions required: Sandbox Analysis** + +* View, filter, and search +* Submit objects + +Type: **investigate** +Read only: **False** + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| vault_id | ID of the vault where the file is located | Required | +| file_name | Name of the file to be analyzed | Required | +| document_pass | The password for decrypting the submitted document. The value must be Base64-encoded. The maximum password length is 128 bytes prior to encoding | Optional | +| archive_pass | The password for decrypting the submitted archive. The value must be Base64-encoded. The maximum password length is 128 bytes prior to encoding | Optional | +| arguments | Parameter that allows you to specify Base64-encoded command line arguments to run the submitted file. The maximum argument length before encoding is 1024 bytes. Arguments are only available for Portable Executable (PE) files and script files | Optional | + +Example input: + + Vault ID + 984afc7aaa2718984e15e3b5ab095b519a081321 + File Name + some_file.bat + Document Password + cGFzc3dvcmQK + Archive Password + cGFzc3dvcmQK + Arguments + IFMlYztbQA== + +#### Context Output + + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| action_result.data.*.id | String | Unique alphanumeric string that identifies a submission | +| action_result.data.*.digest | String | object (sandbox-digest) | +| action_result.data.*.arguments | String | Command line arguments encoded in Base64 of the submitted file | + * * * ### Configuration Variables @@ -1473,6 +1517,7 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION [sandbox investigation package](#action-sandbox-investigation-package) - Downloads the Investigation Package of the specified object [get suspicious list](#action-get-suspicious-list) - Retrieves information about domains, file SHA-1, file SHA-256, IP addresses, email addresses, or URLs in the Suspicious Object List and displays the information in a paginated list [get exception list](#action-get-exception-list) - Retrieves information about domains, file SHA-1, file SHA-256, IP addresses, sender addresses, or URLs in the Exception List and displays it in a paginated list +[vault sandbox analysis](#action-vault-sandbox-analysis) - Send vault item to sandbox for analysis ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -2410,4 +2455,37 @@ action_result.data.\*.value | string | | action_result.summary | string | | action_result.message | string | | summary.total_objects | numeric | | +summary.total_objects_successful | numeric | | + +## action: 'vault sandbox analysis' +Send vault item to sandbox for analysis + +Type: **investigate** +Read only: **True** + +Sends vault item to sandbox for analysis. Provide file name and vault id to perform the action. For the 'arguments' parameter, the maximum argument length before encoding is 1024 bytes. Arguments are only available for Portable Executable (PE) files and script files. + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**vault_id** | required | ID of item in vault | string | `vault id` +**file_name** | required | File name of vault item | string | +**document_pass** | optional | Password for the document | string | +**archive_pass** | optional | Password for the archive | string | +**arguments** | optional | Allows you to specify Base64-encoded command line arguments to run the submitted file | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.parameter.vault_id | string | `vault id` | +action_result.parameter.file_name | string | | +action_result.parameter.document_pass | string | | +action_result.parameter.archive_pass | string | | +action_result.parameter.arguments | string | | +action_result.status | string | | success failed +action_result.data.\*.arguments | string | | +action_result.data.\*.digest | string | | +action_result.data.\*.id | string | `task id` | +action_result.message | string | | +summary.total_objects | numeric | | summary.total_objects_successful | numeric | | \ No newline at end of file diff --git a/__init__.py b/__init__.py index 4e29268..596c98c 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ # File: __init__.py -# Copyright (c) Trend Micro, 2022-2023 +# Copyright (c) Trend Micro, 2022-2024 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/manual_readme_content.md b/manual_readme_content.md index 89983e2..19c5da3 100644 --- a/manual_readme_content.md +++ b/manual_readme_content.md @@ -85,6 +85,7 @@ Configure Trend Vision One on Splunk SOAR [Collect Forensic File](#action-collect-forensic-file) \- Collect forensic file [Forensic File Info](#action-forensic-file-info) \- Get the download information for collected forensic file [Start Analysis](#action-start-analysis) \- Submit file to sandbox for analysis. For supported file types, check [here](https://docs.trendmicro.com/en-us/enterprise/trend-vision-one-olh/threat-intelligence-/sandbox-analysis/sandbox-supported-fi.aspx) +[Vault Sandbox Analysis](#action-vault-sandbox-analysis) \- Submit file from Splunk vault to sandbox for analysis. For supported file types, check [here](https://docs.trendmicro.com/en-us/enterprise/trend-vision-one-olh/threat-intelligence-/sandbox-analysis/sandbox-supported-fi.aspx) [Add Note](#action-add-note) \- Adds a note to an existing workbench alert [Update Status](#action-update-status) \- Updates the status of an existing workbench alert [Get Alert Details](#action-get-alert-details) \- Displays information about the specified alert @@ -1416,4 +1417,47 @@ Authentication Information The app uses HTTPS protocol for communicating with the Trend Vision One server. For authentication a Vision One API Token is used by the Splunk SOAR Connector. +Action: Vault Sandbox Analysis +---------------------- + +Submit file from vault to sandbox for analysis. + +**API key role permissions required: Sandbox Analysis** + +* View, filter, and search +* Submit objects + +Type: **investigate** +Read only: **False** + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| vault_id | ID of the vault where the file is located | Required | +| file_name | Name of the file to be analyzed | Required | +| document_pass | The password for decrypting the submitted document. The value must be Base64-encoded. The maximum password length is 128 bytes prior to encoding | Optional | +| archive_pass | The password for decrypting the submitted archive. The value must be Base64-encoded. The maximum password length is 128 bytes prior to encoding | Optional | +| arguments | Parameter that allows you to specify Base64-encoded command line arguments to run the submitted file. The maximum argument length before encoding is 1024 bytes. Arguments are only available for Portable Executable (PE) files and script files | Optional | + +Example input: + + Vault ID + 984afc7aaa2718984e15e3b5ab095b519a081321 + File Name + some_file.bat + Document Password + cGFzc3dvcmQK + Archive Password + cGFzc3dvcmQK + Arguments + IFMlYztbQA== + +#### Context Output + + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| action_result.data.*.id | String | Unique alphanumeric string that identifies a submission | +| action_result.data.*.digest | String | object (sandbox-digest) | +| action_result.data.*.arguments | String | Command line arguments encoded in Base64 of the submitted file | + * * * \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4c594fc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[tool.black] +line-length = 145 +target-version = ['py39'] +verbose = true + +[tool.isort] +line_length = 145 +profile = "black" diff --git a/release_notes/2.3.0.md b/release_notes/2.3.0.md new file mode 100644 index 0000000..e19e907 --- /dev/null +++ b/release_notes/2.3.0.md @@ -0,0 +1 @@ +* Added new action 'vault sandbox analysis' to enable user to submit item from Splunk Vault to XDR portal for analysis. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8e04238..c04284b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -beautifulsoup4==4.11.2 pytmv1==0.6.2 diff --git a/tox.ini b/tox.ini index c4644ad..720a141 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,4 @@ [flake8] max-line-length = 145 max-complexity = 28 -extend-ignore = F403,E128,E126,E111,E121,E127,E731,E201,E202,F405,E722,D,W292 - -[isort] -line_length = 145 +extend-ignore = F403,E128,E126,E121,E127,E731,E201,E202,E203,E701,F405,E722,D,W503 diff --git a/trendmicrovisionone.json b/trendmicrovisionone.json index e442836..2c39401 100644 --- a/trendmicrovisionone.json +++ b/trendmicrovisionone.json @@ -10,12 +10,17 @@ "python_version": "3", "product_version_regex": ".*", "publisher": "Trend Micro", - "license": "Copyright (c) Trend Micro, 2022-2023", - "app_version": "2.2.1", + "contributors": [ + { + "name": "Shakti Shah" + } + ], + "license": "Copyright (c) Trend Micro, 2022-2024", + "app_version": "2.3.0", "utctime_updated": "2023-12-11T11:50:25.000000Z", "package_name": "phantom_trendmicrovisionone", "main_module": "trendmicrovisionone_connector.py", - "min_phantom_version": "6.1.1", + "min_phantom_version": "6.2.2", "app_wizard_version": "1.0.0", "fips_compliant": false, "configuration": { @@ -153,7 +158,7 @@ "contains": [ "mac address" ], - "column_name": "Mac Address", + "column_name": "MAC Address", "column_order": 5 }, { @@ -2895,22 +2900,137 @@ "type": "table" }, "versions": "EQ(*)" + }, + { + "action": "vault sandbox analysis", + "identifier": "vault_sandbox_analysis", + "description": "Send vault item to sandbox for analysis", + "verbose": "Sends vault item to sandbox for analysis. Provide file name and vault id to perform the action. For the 'arguments' parameter, the maximum argument length before encoding is 1024 bytes. Arguments are only available for Portable Executable (PE) files and script files.", + "type": "investigate", + "read_only": true, + "parameters": { + "vault_id": { + "description": "ID of item in vault", + "data_type": "string", + "required": true, + "primary": true, + "contains": [ + "vault id" + ], + "order": 0 + }, + "file_name": { + "description": "File name of vault item", + "data_type": "string", + "required": true, + "order": 1 + }, + "document_pass": { + "description": "Password for the document", + "data_type": "string", + "order": 2 + }, + "archive_pass": { + "description": "Password for the archive", + "data_type": "string", + "order": 3 + }, + "arguments": { + "description": "Allows you to specify Base64-encoded command line arguments to run the submitted file", + "data_type": "string", + "order": 4 + } + }, + "output": [ + { + "data_path": "action_result.parameter.vault_id", + "data_type": "string", + "contains": [ + "vault id" + ], + "column_name": "Vault ID", + "column_order": 0 + }, + { + "data_path": "action_result.parameter.file_name", + "data_type": "string", + "column_name": "File Name", + "column_order": 1 + }, + { + "data_path": "action_result.parameter.document_pass", + "data_type": "string", + "column_name": "Document Pass", + "column_order": 2 + }, + { + "data_path": "action_result.parameter.archive_pass", + "data_type": "string", + "column_name": "Archive Pass", + "column_order": 3 + }, + { + "data_path": "action_result.parameter.arguments", + "data_type": "string", + "column_name": "Arguments", + "column_order": 4 + }, + { + "data_path": "action_result.status", + "data_type": "string", + "column_name": "Status", + "column_order": 5, + "example_values": [ + "success", + "failed" + ] + }, + { + "data_path": "action_result.data.*.arguments", + "data_type": "string", + "column_name": "Arguments", + "column_order": 6 + }, + { + "data_path": "action_result.data.*.digest", + "data_type": "string", + "column_name": "Digest", + "column_order": 7 + }, + { + "data_path": "action_result.data.*.id", + "contains": [ + "task id" + ], + "data_type": "string", + "column_name": "ID", + "column_order": 8 + }, + { + "data_path": "action_result.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric" + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric" + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" } ], "pip_dependencies": { "wheel": [ - { - "module": "beautifulsoup4", - "input_file": "wheels/py3/beautifulsoup4-4.10.0-py3-none-any.whl" - }, { "module": "chardet", "input_file": "wheels/shared/chardet-3.0.4-py2.py3-none-any.whl" }, - { - "module": "soupsieve", - "input_file": "wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl" - }, { "module": "pytmv1", "input_file": "wheels/py3/pytmv1-0.6.2-py3-none-any.whl" @@ -2919,30 +3039,18 @@ }, "pip39_dependencies": { "wheel": [ - { - "module": "beautifulsoup4", - "input_file": "wheels/py3/beautifulsoup4-4.11.2-py3-none-any.whl" - }, - { - "module": "charset_normalizer", - "input_file": "wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl" - }, { "module": "pydantic", - "input_file": "wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "input_file": "wheels/py39/pydantic-1.10.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl" }, { "module": "pytmv1", "input_file": "wheels/py3/pytmv1-0.6.2-py3-none-any.whl" }, - { - "module": "soupsieve", - "input_file": "wheels/py3/soupsieve-2.5-py3-none-any.whl" - }, { "module": "typing_extensions", - "input_file": "wheels/py3/typing_extensions-4.8.0-py3-none-any.whl" + "input_file": "wheels/py3/typing_extensions-4.12.2-py3-none-any.whl" } ] } -} \ No newline at end of file +} diff --git a/trendmicrovisionone_connector.py b/trendmicrovisionone_connector.py index bc50b6e..4d35bc1 100644 --- a/trendmicrovisionone_connector.py +++ b/trendmicrovisionone_connector.py @@ -1,6 +1,6 @@ # File: trendmicrovisionone_connector.py -# Copyright (c) Trend Micro, 2022-2023 +# Copyright (c) Trend Micro, 2022-2024 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,10 +33,31 @@ from phantom.action_result import ActionResult from phantom.base_connector import BaseConnector from phantom.vault import Vault - -from pytmv1 import (AccountTask, AccountTaskResp, BlockListTaskResp, CollectFileTaskResp, EmailMessageIdTask, EmailMessageTaskResp, - EmailMessageUIdTask, EndpointTask, EndpointTaskResp, ExceptionObject, FileTask, InvestigationStatus, ObjectTask, ObjectType, - ProcessTask, ResultCode, SaeAlert, SuspiciousObject, SuspiciousObjectTask, TerminateProcessTaskResp, TiAlert) + from phantom import vault + +from pytmv1 import ( + AccountTask, + AccountTaskResp, + BlockListTaskResp, + CollectFileTaskResp, + EmailMessageIdTask, + EmailMessageTaskResp, + EmailMessageUIdTask, + EndpointTask, + EndpointTaskResp, + ExceptionObject, + FileTask, + InvestigationStatus, + ObjectTask, + ObjectType, + ProcessTask, + ResultCode, + SaeAlert, + SuspiciousObject, + SuspiciousObjectTask, + TerminateProcessTaskResp, + TiAlert, +) class RetVal(tuple): @@ -86,12 +107,13 @@ def __init__(self): "remove_from_blocklist": self._handle_remove_from_blocklist, "collect_forensic_file": self._handle_collect_forensic_file, "restore_email_message": self._handle_restore_email_message, + "check_analysis_status": self._handle_check_analysis_status, "delete_from_suspicious": self._handle_delete_from_suspicious, + "vault_sandbox_analysis": self._handle_vault_sandbox_analysis, "sandbox_analysis_result": self._handle_sandbox_analysis_result, "sandbox_suspicious_list": self._handle_sandbox_suspicious_list, "download_analysis_report": self._handle_download_analysis_report, "quarantine_email_message": self._handle_quarantine_email_message, - "check_analysis_status": self._handle_check_analysis_status, "sandbox_investigation_package": self._handle_sandbox_investigation_package, } @@ -117,9 +139,18 @@ def _get_ot_enum(obj_type: str) -> ObjectType: @staticmethod def get_task_type(action: str) -> Any: task_dict: Dict[Any, List[str]] = { - AccountTaskResp: ["enableAccount", "disableAccount", "forceSignOut", "resetPassword"], + AccountTaskResp: [ + "enableAccount", + "disableAccount", + "forceSignOut", + "resetPassword", + ], BlockListTaskResp: ["block", "restoreBlock"], - EmailMessageTaskResp: ["quarantineMessage", "restoreMessage", "deleteMessage"], + EmailMessageTaskResp: [ + "quarantineMessage", + "restoreMessage", + "deleteMessage", + ], EndpointTaskResp: ["isolate", "restoreIsolate"], TerminateProcessTaskResp: ["terminateProcess"], } @@ -147,9 +178,7 @@ def _handle_test_connectivity(self, param): if self._is_pytmv1_error(response.result_code): self.debug_print("Please check your environment variables.") - self.save_progress( - "Test Connectivity Failed. Please check your environment variables." - ) + self.save_progress("Test Connectivity Failed. Please check your environment variables.") return action_result.get_status() # Return success @@ -165,9 +194,7 @@ def _handle_get_endpoint_info(self, param): Returns: List[Any]: Returns a list of objects containing information about an endpoint """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -199,13 +226,9 @@ def _handle_get_endpoint_info(self, param): *endpoint_list, ) except Exception as e: - raise RuntimeError( - f"Something went wrong while fetching endpoint data: {e}" - ) + raise RuntimeError(f"Something went wrong while fetching endpoint data: {e}") if len(new_endpoint_data) == 0: - self.save_progress( - f"Endpoint lookup failed, please check endpoint name: {endpoint}" - ) + self.save_progress(f"Endpoint lookup failed, please check endpoint name: {endpoint}") return action_result.get_status() # Load json objects to list for endpoint in new_endpoint_data: @@ -223,17 +246,13 @@ def _handle_quarantine_device(self, param): Dict[str, List[Any]]: Returns a list of objects containing task_id and HTTP status code """ # send progress messages back to the platform - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - endpoint_identifiers: List[Dict[str, str]] = json.loads( - param["endpoint_identifiers"] - ) + endpoint_identifiers: List[Dict[str, str]] = json.loads(param["endpoint_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -280,17 +299,13 @@ def _handle_unquarantine_device(self, param): multi_resp(Dict[str,Any]): Object containing task_id and HTTP status code. """ # send progress messages back to the platform - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - endpoint_identifiers: List[Dict[str, str]] = json.loads( - param["endpoint_identifiers"] - ) + endpoint_identifiers: List[Dict[str, str]] = json.loads(param["endpoint_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -328,9 +343,7 @@ def _handle_unquarantine_device(self, param): # Return success return action_result.set_status(phantom.APP_SUCCESS) - def _create_new_artifact_from_alert( - self, container_id: int, alert: Union[SaeAlert, TiAlert] - ): + def _create_new_artifact_from_alert(self, container_id: int, alert: Union[SaeAlert, TiAlert]): """ Create a new artifact from alert. Args: @@ -361,9 +374,7 @@ def create_artifact_identifier(alert_id: str) -> str: """ return f"TM-{alert_id}" - def _create_artifact_content( - self, container_id: int, alert: Union[SaeAlert, TiAlert] - ): + def _create_artifact_content(self, container_id: int, alert: Union[SaeAlert, TiAlert]): """ Gathers information and adds to artifact. Args: @@ -387,9 +398,7 @@ def _create_artifact_content( "cef": art_cef, } - def _update_container_metadata( - self, container_id: int, alert: Union[SaeAlert, TiAlert] - ): + def _update_container_metadata(self, container_id: int, alert: Union[SaeAlert, TiAlert]): """ Updates an Alert container. Args: @@ -411,9 +420,7 @@ def _update_container_metadata( timeout=30, ) # nosemgrep except Exception as e: - raise RuntimeError( - "Encountered an error updateding container alert." - ) from e + raise RuntimeError("Encountered an error updateding container alert.") from e def artifact_exists(self, container_id: int, alert_id: str): """ @@ -456,9 +463,7 @@ def _get_existing_container_id_for_sdi(self, sdi: str) -> Optional[int]: try: response = requests.get(url, verify=False, timeout=30) # nosemgrep except Exception as e: - raise RuntimeError( - "Encountered an error getting the existing container ID from Phantom." - ) from e + raise RuntimeError("Encountered an error getting the existing container ID from Phantom.") from e # return id or None container_data: dict[str, Any] = response.json() @@ -467,9 +472,7 @@ def _get_existing_container_id_for_sdi(self, sdi: str) -> Optional[int]: # This direct access is okay because the values MUST exist otherwise the problem is out of scope. return container_data["data"][0]["id"] - def _get_existing_container_id_for_alert( - self, alert: Union[SaeAlert, TiAlert] - ) -> Optional[int]: + def _get_existing_container_id_for_alert(self, alert: Union[SaeAlert, TiAlert]) -> Optional[int]: """ Fetch container ID if it exists. Args: @@ -481,9 +484,7 @@ def _get_existing_container_id_for_alert( """ return self._get_existing_container_id_for_sdi(alert.id) - def _create_new_container_payload( - self, alert: Union[SaeAlert, TiAlert] - ) -> Dict[str, Any]: + def _create_new_container_payload(self, alert: Union[SaeAlert, TiAlert]) -> Dict[str, Any]: """ Returns information for an Alert Args: @@ -511,23 +512,17 @@ def _create_or_update_container(self, alert: Union[SaeAlert, TiAlert]) -> int: Returns: int: The ID for the created container. """ - existing_container_id: Optional[ - int - ] = self._get_existing_container_id_for_alert(alert) + existing_container_id: Optional[int] = self._get_existing_container_id_for_alert(alert) # If a container ID does not already exist, create a new one first, because the update operation # runs regardless of whether the container is new or existing. if existing_container_id is None: # save new container to Splunk using the alert - ret_val, msg, cid = self.save_container( - self._create_new_container_payload(alert) - ) + ret_val, msg, cid = self.save_container(self._create_new_container_payload(alert)) if phantom.is_fail(ret_val): self.save_progress("Error saving container: {}".format(msg)) - raise RuntimeError( - "Error saving container: {} -- CID: {}".format(msg, cid) - ) + raise RuntimeError("Error saving container: {} -- CID: {}".format(msg, cid)) existing_container_id = self._get_existing_container_id_for_alert(alert) @@ -536,9 +531,7 @@ def _create_or_update_container(self, alert: Union[SaeAlert, TiAlert]) -> int: assert existing_container_id is not None return existing_container_id - def _create_container_artifacts( - self, container_id: int, alert: Union[SaeAlert, TiAlert] - ): + def _create_container_artifacts(self, container_id: int, alert: Union[SaeAlert, TiAlert]): """ Create an artifact for a container. Args: @@ -558,12 +551,8 @@ def _get_poll_interval(self, param) -> Tuple[str, str]: Tuple[datetime, datetime]: start and end datetime. """ # standard time frame for poll interval - default_end_time = ( - datetime.fromtimestamp(int(datetime.utcnow().timestamp())).isoformat() + "Z" - ) - start_time: str = param.get( - "starttime", self._state.get("last_ingestion_time", "2020-06-15T10:00:00Z") - ) + default_end_time = datetime.fromtimestamp(int(datetime.utcnow().timestamp())).isoformat() + "Z" + start_time: str = param.get("starttime", self._state.get("last_ingestion_time", "2020-06-15T10:00:00Z")) end_time: str = param.get("endtime", default_end_time) return start_time, end_time @@ -580,9 +569,7 @@ def _handle_on_poll(self, param): """ # Log current action - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) action_result = self.add_action_result(ActionResult(param)) @@ -617,13 +604,9 @@ def _handle_on_poll(self, param): # Log results serialized_alerts: List[Dict] = [item.dict() for item in new_alerts] action_result.update_data(serialized_alerts) - action_result.set_summary( - {"Number of Events Found": len(serialized_alerts)} - ) + action_result.set_summary({"Number of Events Found": len(serialized_alerts)}) - self.save_progress( - "Phantom imported {0} events".format(len(serialized_alerts)) - ) + self.save_progress("Phantom imported {0} events".format(len(serialized_alerts))) # remember current timestamp for next run self._state["last_ingestion_time"] = end_time @@ -644,9 +627,7 @@ def _handle_status_check(self, param): Dict[str, int]: object containing task_id and HTTP status code """ # send progress messages back to the platform - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -677,9 +658,7 @@ def _handle_status_check(self, param): if self._is_pytmv1_error(resp.result_code): self.debug_print("Something went wrong, please check task_id.") - raise RuntimeError( - f"Error fetching task status for task {task_id}. Result Code: {resp.error}" - ) + raise RuntimeError(f"Error fetching task status for task {task_id}. Result Code: {resp.error}") assert resp.response is not None action_result.add_data(resp.response.dict()) @@ -695,9 +674,7 @@ def _handle_add_to_blocklist(self, param): multi_resp: Object containing task_id and https status code. """ # send progress messages back to the platform - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -743,9 +720,7 @@ def _handle_remove_from_blocklist(self, param): multi_resp: Object containing task_id and https status code. """ # send progress messages back to the platform - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -771,9 +746,7 @@ def _handle_remove_from_blocklist(self, param): response = client.remove_from_block_list(*unblock_tasks) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please input params.") - raise RuntimeError( - f"Error while removing items from block list: {response.errors}" - ) + raise RuntimeError(f"Error while removing items from block list: {response.errors}") assert response.response is not None # Add the response into the data section @@ -793,9 +766,7 @@ def _handle_quarantine_email_message(self, param): multi_resp(List[Dict[str, Any]]): Object containing task_id and HTTP status code. """ # send progress messages back to the platform - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -814,9 +785,7 @@ def _handle_quarantine_email_message(self, param): email_tasks.append( EmailMessageIdTask( message_id=email["message_id"], - description=email.get( - "description", "Quarantine Email Message." - ), + description=email.get("description", "Quarantine Email Message."), mail_box=email.get("mailbox", ""), ) ) @@ -824,9 +793,7 @@ def _handle_quarantine_email_message(self, param): email_tasks.append( EmailMessageUIdTask( unique_id=email["unique_id"], - description=email.get( - "description", "Quarantine Email Message." - ), + description=email.get("description", "Quarantine Email Message."), ) ) @@ -853,9 +820,7 @@ def _handle_delete_email_message(self, param): Returns: multi_resp(Dict[str, List]): Object containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -910,17 +875,13 @@ def _handle_terminate_process(self, param): Returns: multi_resp(Dict[str, List]): Object containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - process_identifiers: List[Dict[str, str]] = json.loads( - param["process_identifiers"] - ) + process_identifiers: List[Dict[str, str]] = json.loads(param["process_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -970,9 +931,7 @@ def get_exception_count(self) -> int: new_exceptions: List[ExceptionObject] = [] try: - client.consume_exception_list( - lambda exception: new_exceptions.append(exception) - ) + client.consume_exception_list(lambda exception: new_exceptions.append(exception)) except Exception as e: self.debug_print("Consume Exception List failed with following exception:") raise RuntimeError("Error while adding to exception list.") from e @@ -987,9 +946,7 @@ def _handle_add_to_exception(self, param: Dict[str, Any]): Returns: multi_resp(Dict[str, List]): Object containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1016,9 +973,7 @@ def _handle_add_to_exception(self, param: Dict[str, Any]): response = client.add_to_exception_list(*excp_tasks) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check block objects.") - raise RuntimeError( - f"Error while adding object to exception list: {response.errors}" - ) + raise RuntimeError(f"Error while adding object to exception list: {response.errors}") assert response.response is not None # Get total exception list count @@ -1042,9 +997,7 @@ def _handle_delete_from_exception(self, param): Returns: multi_resp(Dict[str, List]): Object containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1070,9 +1023,7 @@ def _handle_delete_from_exception(self, param): response = client.remove_from_exception_list(*excp_tasks) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check block objects.") - raise RuntimeError( - f"Error while removing object from exception list: {response.errors}" - ) + raise RuntimeError(f"Error while removing object from exception list: {response.errors}") assert response.response is not None # Get total exception list count @@ -1096,9 +1047,7 @@ def get_suspicious_count(self) -> int: new_suspicious: List[SuspiciousObject] = [] try: - client.consume_suspicious_list( - lambda suspicious: new_suspicious.append(suspicious) - ) + client.consume_suspicious_list(lambda suspicious: new_suspicious.append(suspicious)) except Exception as e: self.debug_print("Consume Suspicious List failed with following exception:") raise RuntimeError("Error while fetching suspicious list count.") from e @@ -1117,9 +1066,7 @@ def _handle_add_to_suspicious(self, param): Returns: multi_resp(Dict[str, List]): Object containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1146,9 +1093,7 @@ def _handle_add_to_suspicious(self, param): # Make rest call response = client.add_to_suspicious_list(*suspicious_tasks) if self._is_pytmv1_error(response.result_code): - raise RuntimeError( - f"Error while adding to suspicious list: {response.errors}" - ) + raise RuntimeError(f"Error while adding to suspicious list: {response.errors}") assert response.response is not None # Get suspicious list count @@ -1173,9 +1118,7 @@ def _handle_delete_from_suspicious(self, param): Returns: multi_resp(Dict[str, List]): Object containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1200,9 +1143,7 @@ def _handle_delete_from_suspicious(self, param): # Make rest call response = client.remove_from_suspicious_list(*suspicious_tasks) if self._is_pytmv1_error(response.result_code): - raise RuntimeError( - f"Error while removing from suspicious list: {response.errors}" - ) + raise RuntimeError(f"Error while removing from suspicious list: {response.errors}") assert response.response is not None # Get suspicious list count @@ -1226,9 +1167,7 @@ def _handle_check_analysis_status(self, param): Returns: Dict: Object containing response regarding submission status. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1243,9 +1182,7 @@ def _handle_check_analysis_status(self, param): response = client.get_sandbox_submission_status(submit_id=task_id) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check task_id.") - raise RuntimeError( - f"Error while fetching sandbox submission status: {response.error}" - ) + raise RuntimeError(f"Error while fetching sandbox submission status: {response.error}") assert response.response is not None # Add the response into the data section action_result.add_data(response.response.dict()) @@ -1262,9 +1199,7 @@ def _handle_download_analysis_report(self, param): Returns: file(.pdf): A PDF document containing analysis result for specified object. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1280,14 +1215,10 @@ def _handle_download_analysis_report(self, param): client = self._get_client() # Make rest call - response = client.download_sandbox_analysis_result( - submit_id=submit_id, poll=poll, poll_time_sec=poll_time_sec - ) + response = client.download_sandbox_analysis_result(submit_id=submit_id, poll=poll, poll_time_sec=poll_time_sec) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check submit_id.") - raise RuntimeError( - f"Error while downloading sandbox analysis report: {response.error}" - ) + raise RuntimeError(f"Error while downloading sandbox analysis report: {response.error}") assert response.response is not None # Default filename name = "Trend_Micro_Sandbox_Analysis_Report" @@ -1313,9 +1244,7 @@ def _handle_collect_forensic_file(self, param): Returns: Dict[str, List]: List consisting of dict objects containing task_id and HTTP status code. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1371,9 +1300,7 @@ def _handle_forensic_file_info(self, param): Returns: file_info(Dict[str, Any]): Dict object containing response data for file collected. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1398,9 +1325,7 @@ def _handle_forensic_file_info(self, param): if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check task_id.") - raise RuntimeError( - f"Error fetching forensic file info for task {task_id}. Result Code: {response.error}" - ) + raise RuntimeError(f"Error fetching forensic file info for task {task_id}. Result Code: {response.error}") assert response.response is not None # Add the response into the data section @@ -1425,9 +1350,7 @@ def _handle_start_analysis(self, param): Returns: response(Dict[str, Any]): Response object containing ID for submitted object along with digest values. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1458,9 +1381,7 @@ def _handle_start_analysis(self, param): if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check file_url.") - raise RuntimeError( - f"Error submitting file to sandbox for analysis. Result Code: {response.error}" - ) + raise RuntimeError(f"Error submitting file to sandbox for analysis. Result Code: {response.error}") assert response.response is not None # Add the response into the data section @@ -1478,9 +1399,7 @@ def _handle_add_note(self, param): Returns: result(Dict[str, str]): Contains the ID for newly created not and success message. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1496,12 +1415,8 @@ def _handle_add_note(self, param): response = client.add_alert_note(alert_id=workbench_id, note=content) if self._is_pytmv1_error(response.result_code): - self.debug_print( - "Something went wrong, please check workbench_id and content." - ) - raise RuntimeError( - f"Error adding note to workbench {workbench_id}. Result Code: {response.error}" - ) + self.debug_print("Something went wrong, please check workbench_id and content.") + raise RuntimeError(f"Error adding note to workbench {workbench_id}. Result Code: {response.error}") assert response.response is not None note_id = response.response.note_id() @@ -1526,9 +1441,7 @@ def _handle_update_status(self, param): Returns: message(str): Success or Failure. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1550,20 +1463,14 @@ def _handle_update_status(self, param): status = InvestigationStatus[sts] # Make rest call - response = client.edit_alert_status( - alert_id=workbench_id, status=status, if_match=if_match - ) + response = client.edit_alert_status(alert_id=workbench_id, status=status, if_match=if_match) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong while updating alert status.") - raise RuntimeError( - f"Error updating alert status for {workbench_id}. Result Code: {response.error}" - ) + raise RuntimeError(f"Error updating alert status for {workbench_id}. Result Code: {response.error}") # Add the response into the data section - action_result.add_data( - {"message": f"Successfully updated status for {workbench_id} to {status}."} - ) + action_result.add_data({"message": f"Successfully updated status for {workbench_id} to {status}."}) # Return success return action_result.set_status(phantom.APP_SUCCESS) @@ -1577,9 +1484,7 @@ def _handle_get_alert_details(self, param): alert_details (Dict[str, Any]): Returns an Alert (SaeAlert or TiAlert) and ETag (an identifier for a specific version of a Workbench alert resource). """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1595,9 +1500,7 @@ def _handle_get_alert_details(self, param): if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check workbench_id.") - raise RuntimeError( - f"Error fetching alert details for {workbench_id}. Result Code: {response.error}" - ) + raise RuntimeError(f"Error fetching alert details for {workbench_id}. Result Code: {response.error}") assert response.response is not None etag = response.response.etag.replace('"', "") @@ -1619,9 +1522,7 @@ def _handle_urls_to_sandbox(self, param): Returns: submit_urls_resp (List[Dict]): Object containing task_id and http status code for the action call. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1636,9 +1537,7 @@ def _handle_urls_to_sandbox(self, param): response = client.submit_urls_to_sandbox(*urls) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check urls.") - raise RuntimeError( - f"Error while submitting URLs to sandbox: {response.errors}" - ) + raise RuntimeError(f"Error while submitting URLs to sandbox: {response.errors}") assert response.response is not None for item in response.response.items: action_result.add_data(item.dict()) @@ -1654,17 +1553,13 @@ def _handle_enable_account(self, param): Returns: multi_response(List[Dict]): Object containing task_id and http status code for the action call. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - account_identifiers: List[Dict[str, str]] = json.loads( - param["account_identifiers"] - ) + account_identifiers: List[Dict[str, str]] = json.loads(param["account_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -1701,17 +1596,13 @@ def _handle_disable_account(self, param): Returns: multi_response(List[Dict]): Object containing task_id and http status code for the action call. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - account_identifiers: List[Dict[str, str]] = json.loads( - param["account_identifiers"] - ) + account_identifiers: List[Dict[str, str]] = json.loads(param["account_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -1750,9 +1641,7 @@ def _handle_restore_email_message(self, param): Returns: multi_response(List[Dict]): Object containing task_id and http status code for the action call. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1805,17 +1694,13 @@ def _handle_sign_out_account(self, param): Returns: multi_response(List[Dict]): Object containing task_id and http status code for the action call. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - account_identifiers: List[Dict[str, str]] = json.loads( - param["account_identifiers"] - ) + account_identifiers: List[Dict[str, str]] = json.loads(param["account_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -1834,9 +1719,7 @@ def _handle_sign_out_account(self, param): response = client.sign_out_account(*account_tasks) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check account identifiers.") - raise RuntimeError( - f"Error while signing out user account: {response.errors}" - ) + raise RuntimeError(f"Error while signing out user account: {response.errors}") assert response.response is not None # Add the response into the data section @@ -1855,17 +1738,13 @@ def _handle_force_password_reset(self, param): Returns: multi_response(List[Dict]): Object containing task_id and http status code for the action call. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) # Required Params - account_identifiers: List[Dict[str, str]] = json.loads( - param["account_identifiers"] - ) + account_identifiers: List[Dict[str, str]] = json.loads(param["account_identifiers"]) # Initialize Pytmv1 client = self._get_client() @@ -1885,9 +1764,7 @@ def _handle_force_password_reset(self, param): response = client.reset_password_account(*account_tasks) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check account identifiers.") - raise RuntimeError( - f"Error while resetting user account password: {response.errors}" - ) + raise RuntimeError(f"Error while resetting user account password: {response.errors}") assert response.response is not None # Add the response into the data section @@ -1907,9 +1784,7 @@ def _handle_sandbox_suspicious_list(self, param): Returns: sandbox_suspicious_list_resp(List[Dict]): Array response for suspicious object found. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -1926,14 +1801,10 @@ def _handle_sandbox_suspicious_list(self, param): sandbox_suspicious_list_resp: List[Dict[str, Any]] = [] # Make rest call - response = client.get_sandbox_suspicious_list( - submit_id=submit_id, poll=poll, poll_time_sec=poll_time_sec - ) + response = client.get_sandbox_suspicious_list(submit_id=submit_id, poll=poll, poll_time_sec=poll_time_sec) if self._is_pytmv1_error(response.result_code): - raise RuntimeError( - f"Error while fetching sandbox suspicious list: {response.error}" - ) + raise RuntimeError(f"Error while fetching sandbox suspicious list: {response.error}") assert response.response is not None for item in response.response.items: sandbox_suspicious_list_resp.append(item.dict()) @@ -1977,9 +1848,7 @@ def _handle_sandbox_analysis_result(self, param): Returns: Dict: Object containing analysis results for specified ID. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -2003,9 +1872,7 @@ def _handle_sandbox_analysis_result(self, param): if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check report_id.") - raise RuntimeError( - f"Error fetching sandbox analysis result: {response.error}" - ) + raise RuntimeError(f"Error fetching sandbox analysis result: {response.error}") assert response.response is not None # Add the response into the data section @@ -2023,9 +1890,7 @@ def _handle_sandbox_investigation_package(self, param): Returns: file(.zip): Investigation package for the specified object. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -2041,15 +1906,11 @@ def _handle_sandbox_investigation_package(self, param): client = self._get_client() # Make rest call - response = client.download_sandbox_investigation_package( - submit_id=submit_id, poll=poll, poll_time_sec=poll_time_sec - ) + response = client.download_sandbox_investigation_package(submit_id=submit_id, poll=poll, poll_time_sec=poll_time_sec) if self._is_pytmv1_error(response.result_code): self.debug_print("Something went wrong, please check submit_id.") - raise RuntimeError( - f"Error while downloading investigation package: {response.error}" - ) + raise RuntimeError(f"Error while downloading investigation package: {response.error}") assert response.response is not None # Make filename with timestamp name = "Trend_Micro_Sandbox_Investigation_Package" @@ -2072,9 +1933,7 @@ def _handle_get_suspicious_list(self, param): Returns: List: List of suspicious items. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -2086,13 +1945,9 @@ def _handle_get_suspicious_list(self, param): # Make rest call try: - client.consume_suspicious_list( - lambda suspicion: new_suspicions.append(suspicion) - ) + client.consume_suspicious_list(lambda suspicion: new_suspicions.append(suspicion)) except Exception as e: - self.debug_print( - f"Consume Suspicious List failed with following exception: {e}" - ) + self.debug_print(f"Consume Suspicious List failed with following exception: {e}") raise e # Add the response into the data section @@ -2108,9 +1963,7 @@ def _handle_get_exception_list(self, param): Returns: List: Items in exceptions list. """ - self.save_progress( - "In action handler for: {0}".format(self.get_action_identifier()) - ) + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) # Add an action result object to self (BaseConnector) to represent the action for this param action_result = self.add_action_result(ActionResult(param)) @@ -2122,13 +1975,9 @@ def _handle_get_exception_list(self, param): # Make rest call try: - client.consume_exception_list( - lambda exception: new_exceptions.append(exception) - ) + client.consume_exception_list(lambda exception: new_exceptions.append(exception)) except Exception as e: - self.debug_print( - f"Consume Suspicious List failed with following exception: {e}" - ) + self.debug_print(f"Consume Suspicious List failed with following exception: {e}") raise e # Add the response into the data section @@ -2138,6 +1987,61 @@ def _handle_get_exception_list(self, param): # Return success return action_result.set_status(phantom.APP_SUCCESS) + def _handle_vault_sandbox_analysis(self, param): + # use self.save_progress(...) to send progress messages back to the platform + self.save_progress("In action handler for: {0}".format(self.get_action_identifier())) + + # Add an action result object to self (BaseConnector) to represent the action for this param + action_result = self.add_action_result(ActionResult(dict(param))) + + # Required Params + vault_id = param["vault_id"] + file_name = param["file_name"] + # Optional Params + doc_pass = param.get("document_pass", "") + arc_pass = param.get("archive_pass", "") + arguments = param.get("arguments", "None") + + # Initialize Pytmv1 + client = self._get_client() + + # Get file contents + vault_info = vault.vault_info(vault_id=vault_id, file_name=file_name) + file_found: bool = vault_info[0] + if file_found is False: + raise RuntimeError(f"VAULT RESPONSE: {vault_info[1]}. Please check arguments.") + file_contents = b"" + file_path = "" + try: + file_path = vault_info[2][0]["path"] + except FileNotFoundError: + return f"Error: No valid file path returned. '{file_path}' does not exist." + try: + with open(file_path, "rb") as f: + file_contents = f.read() + except IOError: + return f"Error: Could not read the file '{file_path}'." + + # Make rest call + response = client.submit_file_to_sandbox( + file=file_contents, + file_name=file_name, + document_password=doc_pass, + archive_password=arc_pass, + arguments=arguments, + ) + + if self._is_pytmv1_error(response.result_code): + self.debug_print(f"Something went wrong, please check vault_id: {vault_id} and file_name: {file_name}.") + raise RuntimeError(f"Error submitting file to sandbox for analysis. Result Code: {response.error}") + assert response.response is not None + + # Add the response into the data section + action_result.add_data(response.response.dict()) + + # Return success + return action_result.set_status(phantom.APP_SUCCESS) + def handle_action(self, param): # Get the action that we are supposed to execute for this App Run action_id = self.get_action_identifier() @@ -2227,9 +2131,7 @@ def main(): headers["Referer"] = login_url print("Logging into Platform to get the session id") - r2 = requests.post( - login_url, verify=verify, data=data, headers=headers, timeout=30 - ) # nosemgrep + r2 = requests.post(login_url, verify=verify, data=data, headers=headers, timeout=30) # nosemgrep # the above requests to create artefacts only work with verify=False session_id = r2.cookies["sessionid"] except Exception as e: diff --git a/trendmicrovisionone_consts.py b/trendmicrovisionone_consts.py index 8b44d4b..7a0e41c 100644 --- a/trendmicrovisionone_consts.py +++ b/trendmicrovisionone_consts.py @@ -1,6 +1,6 @@ # File: trendmicrovisionone_consts.py -# Copyright (c) Trend Micro, 2022-2023 +# Copyright (c) Trend Micro, 2022-2024 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ REMOVE_BLOCKLIST_COMMAND = "trendmicro-visionone-remove-from-block-list" FORCE_PASSWORD_RESET_COMMAND = "trendmicro-visionone-force-password-reset" # pragma: allowlist secret QUARANTINE_EMAIL_COMMAND = "trendmicro-visionone-quarantine-email-message" +VAULT_SANDBOX_ANALYSIS_COMMAND = "trendmicro-visionone-vault-sandbox-analysis" ADD_EXCEPTION_LIST_COMMAND = "trendmicro-visionone-add-objects-to-exception-list" ADD_SUSPICIOUS_LIST_COMMAND = "trendmicro-visionone-add-objects-to-suspicious-list" DELETE_EXCEPTION_LIST_COMMAND = "trendmicro-visionone-delete-objects-from-exception-list" diff --git a/wheels/py3/beautifulsoup4-4.10.0-py3-none-any.whl b/wheels/py3/beautifulsoup4-4.10.0-py3-none-any.whl deleted file mode 100644 index 85338c2..0000000 Binary files a/wheels/py3/beautifulsoup4-4.10.0-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/beautifulsoup4-4.11.2-py3-none-any.whl b/wheels/py3/beautifulsoup4-4.11.2-py3-none-any.whl deleted file mode 100644 index a09c947..0000000 Binary files a/wheels/py3/beautifulsoup4-4.11.2-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/certifi-2023.7.22-py3-none-any.whl b/wheels/py3/certifi-2023.7.22-py3-none-any.whl deleted file mode 100644 index 78dfe27..0000000 Binary files a/wheels/py3/certifi-2023.7.22-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl b/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl deleted file mode 100644 index 17a2dfb..0000000 Binary files a/wheels/py3/charset_normalizer-2.0.12-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/idna-3.4-py3-none-any.whl b/wheels/py3/idna-3.4-py3-none-any.whl deleted file mode 100644 index 7343c68..0000000 Binary files a/wheels/py3/idna-3.4-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl b/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl deleted file mode 100644 index b363a9b..0000000 Binary files a/wheels/py3/soupsieve-2.3.2.post1-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/soupsieve-2.5-py3-none-any.whl b/wheels/py3/soupsieve-2.5-py3-none-any.whl deleted file mode 100644 index e1be128..0000000 Binary files a/wheels/py3/soupsieve-2.5-py3-none-any.whl and /dev/null differ diff --git a/wheels/py3/typing_extensions-4.12.2-py3-none-any.whl b/wheels/py3/typing_extensions-4.12.2-py3-none-any.whl new file mode 100644 index 0000000..f6cc799 Binary files /dev/null and b/wheels/py3/typing_extensions-4.12.2-py3-none-any.whl differ diff --git a/wheels/py3/typing_extensions-4.8.0-py3-none-any.whl b/wheels/py3/typing_extensions-4.8.0-py3-none-any.whl deleted file mode 100644 index 3e28f86..0000000 Binary files a/wheels/py3/typing_extensions-4.8.0-py3-none-any.whl and /dev/null differ diff --git a/wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl deleted file mode 100644 index d89f0b1..0000000 Binary files a/wheels/py39/pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ diff --git a/wheels/py39/pydantic-1.10.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl b/wheels/py39/pydantic-1.10.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..4f4db6b Binary files /dev/null and b/wheels/py39/pydantic-1.10.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/wheels/shared/requests-2.27.1-py2.py3-none-any.whl b/wheels/shared/requests-2.27.1-py2.py3-none-any.whl deleted file mode 100644 index 807fc61..0000000 Binary files a/wheels/shared/requests-2.27.1-py2.py3-none-any.whl and /dev/null differ diff --git a/wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl b/wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl deleted file mode 100644 index c7337c7..0000000 Binary files a/wheels/shared/urllib3-1.26.18-py2.py3-none-any.whl and /dev/null differ