From 6da8bc32c17bc3d20fff7b12d36344b8c8ed9f18 Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:00:02 -0400 Subject: [PATCH 01/10] INTENTIONALLY INTRODUCES AN ERROR TO TEST UPDATED VALIDATION AND PYTEST WORKFLOWS. REMOVE THIS CHANGE BEFORE MERGING THIS PR. --- detections/endpoint/7zip_commandline_to_smb_share_path.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detections/endpoint/7zip_commandline_to_smb_share_path.yml b/detections/endpoint/7zip_commandline_to_smb_share_path.yml index 59f9728be9..8d7b82129c 100644 --- a/detections/endpoint/7zip_commandline_to_smb_share_path.yml +++ b/detections/endpoint/7zip_commandline_to_smb_share_path.yml @@ -67,6 +67,6 @@ tags: - Processes.process - Processes.process_id - Processes.parent_process_id - risk_score: 25 + risk_score: 1 security_domain: endpoint asset_type: Endpoint From 640f93241903175b08b29946e8852b0574e7cb85 Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:47:29 -0400 Subject: [PATCH 02/10] Added bubbling up errors so that they can be handled by higher level functions. --- .../application/factory/factory.py | 57 +++++++++++-------- .../domain/entities/detection_tags.py | 5 +- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/bin/contentctl_project/contentctl_core/application/factory/factory.py b/bin/contentctl_project/contentctl_core/application/factory/factory.py index a074bb6cca..1122a88b63 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/factory.py +++ b/bin/contentctl_project/contentctl_core/application/factory/factory.py @@ -3,7 +3,8 @@ import sys from pydantic import ValidationError from dataclasses import dataclass - +import pathlib +from typing import Tuple from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType from bin.contentctl_project.contentctl_core.application.builder.basic_builder import BasicBuilder @@ -59,20 +60,29 @@ class Factory(): def execute(self, input_dto: FactoryInputDto) -> None: self.input_dto = input_dto print("Creating Security Content - ESCU. This may take some time...") + #Accumulate any validation errors that may occur while creating security_contnet + validation_errors = [] # order matters to load and enrich security content types - self.createSecurityContent(SecurityContentType.unit_tests) - self.createSecurityContent(SecurityContentType.lookups) - self.createSecurityContent(SecurityContentType.macros) - self.createSecurityContent(SecurityContentType.deployments) - self.createSecurityContent(SecurityContentType.baselines) - self.createSecurityContent(SecurityContentType.investigations) - self.createSecurityContent(SecurityContentType.playbooks) - self.createSecurityContent(SecurityContentType.detections) - self.createSecurityContent(SecurityContentType.stories) + validation_errors.extend(self.createSecurityContent(SecurityContentType.unit_tests)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.lookups)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.macros)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.deployments)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.baselines)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.investigations)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.playbooks)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.detections)) + validation_errors.extend(self.createSecurityContent(SecurityContentType.stories)) LinkValidator.print_link_validation_errors() + if len(validation_errors) != 0: + print(f"There were [{len(validation_errors)}] error(s) found while parsing security_content") + for ve in validation_errors: + file_path = ve[0] + error = ve[1] + print(f'\nValidation Error for file [{file_path}]:\n{str(error)}') + raise(Exception("Error(s) validating Security Content")) - def createSecurityContent(self, type: SecurityContentType) -> list: + def createSecurityContent(self, type: SecurityContentType) -> list[Tuple[pathlib.Path, ValidationError]]: objects = [] if type == SecurityContentType.deployments: files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name), 'ESCU')) @@ -82,10 +92,10 @@ class Factory(): files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name))) # Instead of failing on the first error, just keep track of - # whether or not an error was found. This way, we can - # report all of the errors on a single run so that the - # user can see all the errors they need to fix. - validation_error_found = False + # of all the exceptions that we generate. These exceptions + # will be returned from the function and should be printed + # by the caller. + validation_errors = [] already_ran = False progress_percent = 0 @@ -175,19 +185,16 @@ class Factory(): print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) except ValidationError as e: - print('\nValidation Error for file ' + file) - print(e) - validation_error_found = True + validation_errors.append((pathlib.Path(file), e)) + except Exception as e: + print(f"Unknown exception caught while Creating Security Content: {str(e)}") + sys.exit(1) + - #Check for any duplicate IDs. The structure is uses - # to track them, self.ids, is populated previously in this - # function every time content is adde. - # This will also print out the duplicates if they exist. - validation_error_found |= Utils.check_ids_for_duplicates(self.ids) + print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) print("Done!") - if validation_error_found: - sys.exit(1) \ No newline at end of file + return validation_errors \ No newline at end of file diff --git a/bin/contentctl_project/contentctl_core/domain/entities/detection_tags.py b/bin/contentctl_project/contentctl_core/domain/entities/detection_tags.py index 6b107836ed..4afa6702cc 100644 --- a/bin/contentctl_project/contentctl_core/domain/entities/detection_tags.py +++ b/bin/contentctl_project/contentctl_core/domain/entities/detection_tags.py @@ -115,8 +115,9 @@ class DetectionTags(BaseModel): @validator('risk_score') def tags_calculate_risk_score(cls, v, values): - calculated_risk_score = (int(values['impact']))*(int(values['confidence']))/100 + calculated_risk_score = round(values['impact'] * values['confidence'] / 100) if calculated_risk_score != int(v): - raise ValueError('risk_score is calculated wrong: ' + values["name"]) + raise ValueError(f"Risk Score must be calculated as round(confidence * impact / 100)" + f"\n Expected risk_score={calculated_risk_score}, found risk_score={int(v)}: {values['name']}") return v From fc1db0c16161108703ace473594aaf5f22d91f3e Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:25:53 -0400 Subject: [PATCH 03/10] Fixing modified detection so that it has its original value. --- detections/endpoint/7zip_commandline_to_smb_share_path.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detections/endpoint/7zip_commandline_to_smb_share_path.yml b/detections/endpoint/7zip_commandline_to_smb_share_path.yml index 8d7b82129c..59f9728be9 100644 --- a/detections/endpoint/7zip_commandline_to_smb_share_path.yml +++ b/detections/endpoint/7zip_commandline_to_smb_share_path.yml @@ -67,6 +67,6 @@ tags: - Processes.process - Processes.process_id - Processes.parent_process_id - risk_score: 1 + risk_score: 25 security_domain: endpoint asset_type: Endpoint From 31cd39763bc34d1652480b679359acb710944396 Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:34:12 -0400 Subject: [PATCH 04/10] Remove a number of updated files which don't yet exist in develop and should not have been in this branch to begin with. They will be merged as part of a separate PR. --- ...lation_winlogon_duplicate_token_handle.yml | 73 ------------------ ...ogon_duplicate_handle_in_uncommon_path.yml | 73 ------------------ .../windows_service_deletion_in_registry.yml | 74 ------------------- ...n_winlogon_duplicate_token_handle.test.yml | 13 ---- ...duplicate_handle_in_uncommon_path.test.yml | 13 ---- ...dows_service_deletion_in_registry.test.yml | 14 ---- 6 files changed, 260 deletions(-) delete mode 100644 detections/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.yml delete mode 100644 detections/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.yml delete mode 100644 detections/endpoint/windows_service_deletion_in_registry.yml delete mode 100644 tests/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.test.yml delete mode 100644 tests/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.test.yml delete mode 100644 tests/endpoint/windows_service_deletion_in_registry.test.yml diff --git a/detections/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.yml b/detections/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.yml deleted file mode 100644 index d275c9aba9..0000000000 --- a/detections/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Windows Access Token Manipulation Winlogon Duplicate Token Handle -id: dda126d7-1d99-4f0b-b72a-4c14031f9398 -version: 1 -date: '2022-08-24' -author: Teoderick Contreras, Splunk -type: Hunting -datamodel: -- Endpoint -description: The following analytic identifies a process access in winlogon.exe to duplicate its handle. - This technique was seen in several adversaries, threat actors and even red teams to gain privileges to their process. - This duplicate handle access technique, may refer to a malicious process duplicating the process token of winlogon.exe and used it to a new process instance. - Winlogon.exe is the common targeted process of this technique because it contains high privileges and security tokens. -search: '`sysmon` EventCode=10 TargetImage IN("*\\system32\\winlogon.exe*", "*\\SysWOW64\\winlogon.exe*") GrantedAccess = 0x1040 - | stats count min(_time) as firstTime max(_time) as lastTime - by SourceImage TargetImage SourceProcessGUID TargetProcessGUID SourceProcessId TargetProcessId GrantedAccess CallTrace Computer user_id - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `windows_access_token_manipulation_winlogon_duplicate_token_handle_filter`' -how_to_implement: To successfully implement this search you need to be ingesting information - on process that include the name of the process responsible for the changes from - your endpoints into the `Endpoint` datamodel in the `Processes` node. In addition, - confirm the latest CIM App 4.20 or higher is installed and the latest TA for the - endpoint product. -known_false_positives: third party software application may do this technique. -references: - - https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle - - https://attack.mitre.org/techniques/T1134/001/ -tags: - analytic_story: - - Brute Ratel C4 - asset_type: Endpoint - cis20: - - CIS 3 - - CIS 5 - - CIS 16 - confidence: 60 - context: - - Source:Endpoint - - Stage:Defense Evasion - dataset: - - https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/brute_ratel/brute_duplicate_token/sysmon.log - impact: 60 - kill_chain_phases: - - Exploitation - message: a process $SourceImage$ is duplicating the handle token of winlogon.exe in $Computer$ - mitre_attack_id: - - T1134.001 - - T1134 - nist: - - DE.CM - observable: - - name: Computer - type: Endpoint - role: - - Victim - product: - - Splunk Enterprise - - Splunk Enterprise Security - - Splunk Cloud - required_fields: - - _time - - SourceImage - - TargetImage - - SourceProcessGUID - - TargetProcessGUID - - SourceProcessId - - TargetProcessId - - GrantedAccess - - CallTrace - - Computer - - user_id - risk_score: 36 - security_domain: endpoint diff --git a/detections/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.yml b/detections/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.yml deleted file mode 100644 index 30b96f5a91..0000000000 --- a/detections/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Windows Access Token Winlogon Duplicate Handle In Uncommon Path -id: b8f7ed6b-0556-4c84-bffd-839c262b0278 -version: 1 -date: '2022-08-24' -author: Teoderick Contreras, Splunk -type: Anomaly -datamodel: -- Endpoint -description: The following analytic identifies a process access in winlogon.exe to duplicate its handle with a non-common or public process source path. - This technique was seen in several adversaries, threat actors and even red teams to gain privileges to their process. - This duplicate handle access technique, may refer to a malicious process duplicating the process token of winlogon.exe and using it to a new process instance. - Winlogon.exe is the common targeted process of this technique because it contains high privileges and security tokens. -search: '`sysmon` EventCode=10 TargetImage IN("*\\system32\\winlogon.exe*", "*\\SysWOW64\\winlogon.exe*") AND GrantedAccess = 0x1040 - AND NOT (SourceImage IN("C:\\Windows\\*", "C:\\Program File*", "%systemroot%\\*")) - | stats count min(_time) as firstTime max(_time) as lastTime by SourceImage TargetImage SourceProcessGUID TargetProcessGUID SourceProcessId TargetProcessId GrantedAccess CallTrace - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `windows_access_token_winlogon_duplicate_handle_in_uncommon_path_filter`' -how_to_implement: To successfully implement this search you need to be ingesting information - on process that include the name of the process responsible for the changes from - your endpoints into the `Endpoint` datamodel in the `Processes` node. In addition, - confirm the latest CIM App 4.20 or higher is installed and the latest TA for the - endpoint product. -known_false_positives: 3rd party software application may do this technique. -references: - - https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle - - https://attack.mitre.org/techniques/T1134/001/ -tags: - analytic_story: - - Brute Ratel C4 - asset_type: Endpoint - cis20: - - CIS 3 - - CIS 5 - - CIS 16 - confidence: 70 - context: - - Source:Endpoint - - Stage:Defense Evasion - dataset: - - https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/brute_ratel/brute_duplicate_token/sysmon.log - impact: 70 - kill_chain_phases: - - Exploitation - message: a process $SourceImage$ is duplicating the handle token of winlogon.exe in $Computer$ - mitre_attack_id: - - T1134.001 - - T1134 - nist: - - DE.CM - observable: - - name: Computer - type: Endpoint - role: - - Victim - product: - - Splunk Enterprise - - Splunk Enterprise Security - - Splunk Cloud - required_fields: - - _time - - SourceImage - - TargetImage - - SourceProcessGUID - - TargetProcessGUID - - SourceProcessId - - TargetProcessId - - GrantedAccess - - CallTrace - - Computer - - user_id - risk_score: 49 - security_domain: endpoint \ No newline at end of file diff --git a/detections/endpoint/windows_service_deletion_in_registry.yml b/detections/endpoint/windows_service_deletion_in_registry.yml deleted file mode 100644 index 3488fefd77..0000000000 --- a/detections/endpoint/windows_service_deletion_in_registry.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Windows Service Deletion In Registry -id: daed6823-b51c-4843-a6ad-169708f1323e -version: 1 -date: '2022-08-24' -author: Teoderick Contreras, Splunk -type: TTP -datamodel: -- Endpoint -description: The following analytic identifies a registry modification due to deleted services. - Red Teams, malicious actors and adversaries may delete a security service as part of its defense evasion. - The BRC4 red teaming tool is capable of deleting a services using native windows API that leave lesser noise - and footprint in terms of process command-line detections. -search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime FROM datamodel=Endpoint.Registry - where Registry.registry_path= "*\\SYSTEM\\CurrentControlSet\\Services*" AND (Registry.action = deleted OR (Registry.registry_value_name = DeleteFlag AND Registry.registry_value_data = 0x00000001 AND Registry.action=modified)) - by Registry.registry_key_name Registry.user Registry.registry_path Registry.registry_value_data Registry.registry_value_name Registry.action Registry.dest - | `drop_dm_object_name(Registry)` - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `windows_service_deletion_in_registry_filter`' -how_to_implement: To successfully implement this search you need to be ingesting information - on process that include the name of the process responsible for the changes from - your endpoints into the `Endpoint` datamodel in the `Registry` node. Also make sure - that this registry was included in your config files ex. sysmon config to be monitored. -known_false_positives: this event can be seen when administrator delete a service or uninstall/reinstall a software that create service entry, - But it is still recommended to check this alert with high priority. -references: -- https://unit42.paloaltonetworks.com/brute-ratel-c4-tool/ -tags: - analytic_story: - - Brute Ratel C4 - asset_type: Endpoint - cis20: - - CIS 3 - - CIS 5 - - CIS 16 - confidence: 80 - context: - - Source:Endpoint - - Stage:Defense Evasion - dataset: - - https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/brute_ratel/service_deletion/sysmon.log - impact: 80 - kill_chain_phases: - - Exploitation - message: a service registry $Registry.registry_path$ was deleted in $Registry.dest$ - mitre_attack_id: - - T1489 - nist: - - DE.CM - observable: - - name: dest - type: Endpoint - role: - - Victim - product: - - Splunk Enterprise - - Splunk Enterprise Security - - Splunk Cloud - required_fields: - - _time - - Registry.registry_key_name - - Registry.registry_path - - Registry.user - - Registry.dest - - Registry.registry_value_name - - Processes.process_id - - Processes.process_name - - Processes.process - - Processes.dest - - Processes.parent_process_name - - Processes.parent_process - - Processes.process_guid - risk_score: 64 - security_domain: endpoint diff --git a/tests/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.test.yml b/tests/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.test.yml deleted file mode 100644 index d9a4893617..0000000000 --- a/tests/endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.test.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Windows Access Token Manipulation Winlogon Duplicate Token Handle Unit Test -tests: -- name: Windows Access Token Manipulation Winlogon Duplicate Token Handle - file: endpoint/windows_access_token_manipulation_winlogon_duplicate_token_handle.yml - pass_condition: '| stats count | where count > 0' - earliest_time: -24h - latest_time: now - attack_data: - - file_name: sysmon.log - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/brute_ratel/brute_duplicate_token/sysmon.log - source: XmlWinEventLog:Microsoft-Windows-Sysmon/Operational - sourcetype: xmlwineventlog - update_timestamp: true diff --git a/tests/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.test.yml b/tests/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.test.yml deleted file mode 100644 index ce8a6ecca5..0000000000 --- a/tests/endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.test.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Windows Access Token Winlogon Duplicate Handle In Uncommon Path Unit Test -tests: -- name: Windows Access Token Winlogon Duplicate Handle In Uncommon Path - file: endpoint/windows_access_token_winlogon_duplicate_handle_in_uncommon_path.yml - pass_condition: '| stats count | where count > 0' - earliest_time: -24h - latest_time: now - attack_data: - - file_name: sysmon.log - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/brute_ratel/brute_duplicate_token/sysmon.log - source: XmlWinEventLog:Microsoft-Windows-Sysmon/Operational - sourcetype: xmlwineventlog - update_timestamp: true diff --git a/tests/endpoint/windows_service_deletion_in_registry.test.yml b/tests/endpoint/windows_service_deletion_in_registry.test.yml deleted file mode 100644 index 54a7ad09e3..0000000000 --- a/tests/endpoint/windows_service_deletion_in_registry.test.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Windows Service Deletion In Registry Unit Test -tests: -- name: Windows Service Deletion In Registry - file: endpoint/windows_service_deletion_in_registry.yml - pass_condition: '| stats count | where count > 0' - earliest_time: -24h - latest_time: now - attack_data: - - file_name: sysmon.log - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/brute_ratel/service_deletion/sysmon.log - source: XmlWinEventLog:Microsoft-Windows-Sysmon/Operational - sourcetype: xmlwineventlog - - update_timestamp: true From 9657101fc3b300c97ba71d29606e431c7afbb250 Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:46:52 -0700 Subject: [PATCH 05/10] Fixes for more robust catching of duplicate ids. --- .../application/factory/ba_factory.py | 80 +++++++++++-------- .../application/factory/factory.py | 22 ++--- .../application/factory/object_factory.py | 2 +- .../application/factory/utils/utils.py | 27 ++++--- 4 files changed, 74 insertions(+), 57 deletions(-) diff --git a/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py b/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py index a52bb26d63..4cd8fc529c 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py +++ b/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py @@ -3,6 +3,8 @@ import sys from pydantic import ValidationError from dataclasses import dataclass +from typing import Tuple +import pathlib from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType from bin.contentctl_project.contentctl_core.application.builder.basic_builder import BasicBuilder @@ -27,7 +29,7 @@ class BAFactoryOutputDto: class BAFactory(): input_dto: BAFactoryInputDto output_dto: BAFactoryOutputDto - ids: dict[str,list[str]] = {} + ids: dict[str,list[pathlib.Path]] = {} def __init__(self, output_dto: BAFactoryOutputDto) -> None: self.output_dto = output_dto @@ -35,22 +37,30 @@ class BAFactory(): def execute(self, input_dto: BAFactoryInputDto) -> None: self.input_dto = input_dto print("Creating Security Content - SSA. This may take some time...") - self.createSecurityContent(SecurityContentType.unit_tests) - self.createSecurityContent(SecurityContentType.detections) + validation_errors = self.createSecurityContent(SecurityContentType.unit_tests) + validation_errors.extend(self.createSecurityContent(SecurityContentType.detections)) + if len(validation_errors) != 0: + print(f"There were [{len(validation_errors)}] error(s) found while parsing security_content") + for ve in validation_errors: + file_path = ve[0] + error = ve[1] + print(f'\nValidation Error for file [{file_path}]:\n{str(error)}') + raise(Exception("Error(s) validating Security Content")) + - def createSecurityContent(self, type: SecurityContentType) -> list: + def createSecurityContent(self, type: SecurityContentType) -> list[Tuple[pathlib.Path, ValidationError]]: objects = [] if type == SecurityContentType.unit_tests: files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, 'tests')) else: files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name))) - validation_error_found = False + validation_errors:list[Tuple[pathlib.Path, ValidationError]] = [] - files_with_ssa = [f for f in files if 'ssa___' in f] + files_with_ssa = [f for f in files if f.name.startswith('ssa___')] already_ran = False progress_percent = 0 @@ -61,43 +71,43 @@ class BAFactory(): # that printouts end at 100%, not some other number progress_percent = ((index+1)/len(files_with_ssa)) * 100 - if 'ssa__' in file: - progress_percent = ((index+1)/len(files_with_ssa)) * 100 - try: - type_string = "UNKNOWN TYPE" - if type == SecurityContentType.detections: - type_string = "Detections" - self.input_dto.director.constructDetection(self.input_dto.detection_builder, file, [], [], [], self.output_dto.tests, {}, [], []) - detection = self.input_dto.detection_builder.getObject() - Utils.add_id(self.ids, detection, file) - if not detection.deprecated and not detection.experimental: - self.output_dto.detections.append(detection) - elif type == SecurityContentType.unit_tests: - type_string = "Unit Tests" - self.input_dto.director.constructTest(self.input_dto.basic_builder, file) - test = self.input_dto.basic_builder.getObject() - Utils.add_id(self.ids, test, file) - self.output_dto.tests.append(test) - else: - raise(Exception(f"Unsupported content type: [{type}]")) + + progress_percent = ((index+1)/len(files_with_ssa)) * 100 + try: + type_string = "UNKNOWN TYPE" + if type == SecurityContentType.detections: + type_string = "Detections" + self.input_dto.director.constructDetection(self.input_dto.detection_builder, file, [], [], [], self.output_dto.tests, {}, [], []) + detection = self.input_dto.detection_builder.getObject() + Utils.add_id(self.ids, detection, file) + if not detection.deprecated and not detection.experimental: + self.output_dto.detections.append(detection) + elif type == SecurityContentType.unit_tests: + type_string = "Unit Tests" + self.input_dto.director.constructTest(self.input_dto.basic_builder, str(file)) + test = self.input_dto.basic_builder.getObject() + Utils.add_id(self.ids, test, file) + self.output_dto.tests.append(test) + else: + raise(Exception(f"Unsupported content type: [{type}]")) - if (sys.stdout.isatty() and sys.stdin.isatty() and sys.stderr.isatty()) or not already_ran: - already_ran = True - print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) + if (sys.stdout.isatty() and sys.stdin.isatty() and sys.stderr.isatty()) or not already_ran: + already_ran = True + print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) - except ValidationError as e: - print('\nValidation Error for file ' + file) - print(e) - validation_error_found = True + except ValidationError as e: + validation_errors.append((pathlib.Path(file), e)) + except Exception as e: + print(f"Unknown exception caught while Creating BA Security Content: {str(e)}") + sys.exit(1) #Check for any duplicate IDs. The structure is uses # to track them, self.ids, is populated previously in this # function every time content is adde. # This will also print out the duplicates if they exist. - validation_error_found |= Utils.check_ids_for_duplicates(self.ids) + validation_errors.extend(Utils.check_ids_for_duplicates(self.ids)) print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) print("Done!") - if validation_error_found: - sys.exit(1) \ No newline at end of file + return validation_errors \ No newline at end of file diff --git a/bin/contentctl_project/contentctl_core/application/factory/factory.py b/bin/contentctl_project/contentctl_core/application/factory/factory.py index 1122a88b63..f7720da5cf 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/factory.py +++ b/bin/contentctl_project/contentctl_core/application/factory/factory.py @@ -49,7 +49,7 @@ class FactoryOutputDto: class Factory(): input_dto: FactoryInputDto output_dto: FactoryOutputDto - ids: dict[str,list[str]] = {} + ids: dict[str,list[pathlib.Path]] = {} def __init__(self, output_dto: FactoryOutputDto) -> None: self.output_dto = output_dto @@ -95,14 +95,14 @@ class Factory(): # of all the exceptions that we generate. These exceptions # will be returned from the function and should be printed # by the caller. - validation_errors = [] + validation_errors:list[Tuple[pathlib.Path, ValidationError]] = [] already_ran = False progress_percent = 0 type_string = "UNKNOWN TYPE" #Non threaded, production version of the construction code - files_without_ssa = [f for f in files if 'ssa___' not in f] + files_without_ssa = [f for f in files if not f.name.startswith('ssa___')] for index,file in enumerate(files_without_ssa): #Index + 1 because we are zero indexed, not 1 indexed. This ensures @@ -112,35 +112,35 @@ class Factory(): type_string = "UNKNOWN TYPE" if type == SecurityContentType.lookups: type_string = "Lookups" - self.input_dto.director.constructLookup(self.input_dto.basic_builder, file) + self.input_dto.director.constructLookup(self.input_dto.basic_builder, str(file)) lookup = self.input_dto.basic_builder.getObject() Utils.add_id(self.ids, lookup, file) self.output_dto.lookups.append(lookup) elif type == SecurityContentType.macros: type_string = "Macros" - self.input_dto.director.constructMacro(self.input_dto.basic_builder, file) + self.input_dto.director.constructMacro(self.input_dto.basic_builder, str(file)) macro = self.input_dto.basic_builder.getObject() Utils.add_id(self.ids, macro, file) self.output_dto.macros.append(macro) elif type == SecurityContentType.deployments: type_string = "Deployments" - self.input_dto.director.constructDeployment(self.input_dto.basic_builder, file) + self.input_dto.director.constructDeployment(self.input_dto.basic_builder, str(file)) deployment = self.input_dto.basic_builder.getObject() Utils.add_id(self.ids, deployment, file) self.output_dto.deployments.append(deployment) elif type == SecurityContentType.playbooks: type_string = "Playbooks" - self.input_dto.director.constructPlaybook(self.input_dto.playbook_builder, file) + self.input_dto.director.constructPlaybook(self.input_dto.playbook_builder, str(file)) playbook = self.input_dto.playbook_builder.getObject() Utils.add_id(self.ids, playbook, file) self.output_dto.playbooks.append(playbook) elif type == SecurityContentType.baselines: type_string = "Baselines" - self.input_dto.director.constructBaseline(self.input_dto.baseline_builder, file, self.output_dto.deployments) + self.input_dto.director.constructBaseline(self.input_dto.baseline_builder, str(file), self.output_dto.deployments) baseline = self.input_dto.baseline_builder.getObject() Utils.add_id(self.ids, baseline, file) self.output_dto.baselines.append(baseline) @@ -154,7 +154,7 @@ class Factory(): elif type == SecurityContentType.stories: type_string = "Stories" - self.input_dto.director.constructStory(self.input_dto.story_builder, file, + self.input_dto.director.constructStory(self.input_dto.story_builder, str(file), self.output_dto.detections, self.output_dto.baselines, self.output_dto.investigations) story = self.input_dto.story_builder.getObject() Utils.add_id(self.ids, story, file) @@ -172,7 +172,7 @@ class Factory(): elif type == SecurityContentType.unit_tests: type_string = "Unit Tests" - self.input_dto.director.constructTest(self.input_dto.basic_builder, file) + self.input_dto.director.constructTest(self.input_dto.basic_builder, str(file)) test = self.input_dto.basic_builder.getObject() Utils.add_id(self.ids, test, file) self.output_dto.tests.append(test) @@ -191,7 +191,7 @@ class Factory(): sys.exit(1) - + validation_errors.extend(Utils.check_ids_for_duplicates(self.ids)) print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) diff --git a/bin/contentctl_project/contentctl_core/application/factory/object_factory.py b/bin/contentctl_project/contentctl_core/application/factory/object_factory.py index ec6a125916..e8ccd364dc 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/object_factory.py +++ b/bin/contentctl_project/contentctl_core/application/factory/object_factory.py @@ -25,5 +25,5 @@ class ObjectFactory(): files = Utils.get_all_yml_files_from_directory(input_dto.input_path) for file in files: - input_dto.director.constructObjects(input_dto.builder, file) + input_dto.director.constructObjects(input_dto.builder, str(file)) self.objects.append(input_dto.builder.getObject()) \ No newline at end of file diff --git a/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py b/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py index 35fb65f5a7..9a96286d56 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py +++ b/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py @@ -1,21 +1,25 @@ import os +import pathlib +from typing import Tuple +from pydantic import ValidationError from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject class Utils: @staticmethod - def get_all_yml_files_from_directory(path: str) -> list: - listOfFiles = list() + def get_all_yml_files_from_directory(path: str) -> list[pathlib.Path]: + listOfFiles:list[pathlib.Path] = [] for (dirpath, dirnames, filenames) in os.walk(path): for file in filenames: if file.endswith(".yml"): - listOfFiles.append(os.path.join(dirpath, file)) + listOfFiles.append(pathlib.Path(os.path.join(dirpath, file))) return sorted(listOfFiles) + @staticmethod - def add_id(id_dict:dict[str, list[str]], obj:SecurityContentObject, path:str) -> None: + def add_id(id_dict:dict[str, list[pathlib.Path]], obj:SecurityContentObject, path:pathlib.Path) -> None: if hasattr(obj, "id"): obj_id = obj.id if obj_id in id_dict: @@ -25,11 +29,14 @@ class Utils: # Otherwise, no ID so nothing to add.... @staticmethod - def check_ids_for_duplicates(id_dict:dict[str, list[str]])->bool: - validation_error = False + def check_ids_for_duplicates(id_dict:dict[str, list[pathlib.Path]])->list[Tuple[pathlib.Path, ValidationError]]: + validation_errors:list[Tuple[pathlib.Path, ValidationError]] = [] + for key, values in id_dict.items(): if len(values) > 1: - validation_error = True - id_conflicts_string = '\n\t* '.join(values) - print(f"\nError validating id [{key}] - duplicate ID is used for the following content: \n\t* {id_conflicts_string}") - return validation_error \ No newline at end of file + for value in values: + error_file_path = pathlib.Path(value) + exception = ValueError(f"Error validating id [{key}] - duplicate ID was used") + validation_errors.append((error_file_path, exception)) + + return validation_errors \ No newline at end of file From fe36f9f5bca68c8b2dac291f0929214ebf49b371 Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Thu, 29 Sep 2022 14:35:21 -0700 Subject: [PATCH 06/10] Re-Added and improved duplicate ID checking for ESCU and BA detections. --- .../contentctl_core/application/factory/ba_factory.py | 9 ++++----- .../contentctl_core/application/factory/factory.py | 3 ++- .../contentctl_core/application/factory/utils/utils.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py b/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py index 4cd8fc529c..1f8077d3e7 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py +++ b/bin/contentctl_project/contentctl_core/application/factory/ba_factory.py @@ -39,6 +39,8 @@ class BAFactory(): print("Creating Security Content - SSA. This may take some time...") validation_errors = self.createSecurityContent(SecurityContentType.unit_tests) validation_errors.extend(self.createSecurityContent(SecurityContentType.detections)) + validation_errors.extend(Utils.check_ids_for_duplicates(self.ids)) + if len(validation_errors) != 0: print(f"There were [{len(validation_errors)}] error(s) found while parsing security_content") @@ -101,11 +103,8 @@ class BAFactory(): print(f"Unknown exception caught while Creating BA Security Content: {str(e)}") sys.exit(1) - #Check for any duplicate IDs. The structure is uses - # to track them, self.ids, is populated previously in this - # function every time content is adde. - # This will also print out the duplicates if they exist. - validation_errors.extend(Utils.check_ids_for_duplicates(self.ids)) + + print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) print("Done!") diff --git a/bin/contentctl_project/contentctl_core/application/factory/factory.py b/bin/contentctl_project/contentctl_core/application/factory/factory.py index f7720da5cf..9ca556db7d 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/factory.py +++ b/bin/contentctl_project/contentctl_core/application/factory/factory.py @@ -72,6 +72,7 @@ class Factory(): validation_errors.extend(self.createSecurityContent(SecurityContentType.playbooks)) validation_errors.extend(self.createSecurityContent(SecurityContentType.detections)) validation_errors.extend(self.createSecurityContent(SecurityContentType.stories)) + validation_errors.extend(Utils.check_ids_for_duplicates(self.ids)) LinkValidator.print_link_validation_errors() if len(validation_errors) != 0: @@ -191,7 +192,7 @@ class Factory(): sys.exit(1) - validation_errors.extend(Utils.check_ids_for_duplicates(self.ids)) + print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True) diff --git a/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py b/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py index 9a96286d56..4123c0a6fc 100644 --- a/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py +++ b/bin/contentctl_project/contentctl_core/application/factory/utils/utils.py @@ -34,9 +34,9 @@ class Utils: for key, values in id_dict.items(): if len(values) > 1: - for value in values: - error_file_path = pathlib.Path(value) - exception = ValueError(f"Error validating id [{key}] - duplicate ID was used") - validation_errors.append((error_file_path, exception)) + error_file_path = pathlib.Path("MULTIPLE") + all_files = '\n\t'.join(str(pathlib.Path(p)) for p in values) + exception = ValueError(f"Error validating id [{key}] - duplicate ID was used in the following files: \n\t{all_files}") + validation_errors.append((error_file_path, exception)) return validation_errors \ No newline at end of file From 3968693c330242b00d01d2658eb37187d21da231 Mon Sep 17 00:00:00 2001 From: pyth0n1c <87383215+pyth0n1c@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:54:19 -0700 Subject: [PATCH 07/10] Remove slim as a dependency. Still requires some testing. This will break the contentctl build functionality as it still requires slim. A user can install splunk-packaging-toolkit separately, but it is not currently compatible with Python3.10. --- .github/workflows/build-and-validate.yml | 33 +++++-------------- .../detection_testing_execution.py | 6 ++-- requirements.txt | 1 - 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-and-validate.yml b/.github/workflows/build-and-validate.yml index c1d0e09a88..f94fe83e91 100644 --- a/.github/workflows/build-and-validate.yml +++ b/.github/workflows/build-and-validate.yml @@ -144,15 +144,6 @@ jobs: # update build number and version for ssa tar -czf build/content-pack-build-ssa.tar.gz dist/ssa/* - - name: Download and Install Splunk Packaging Toolkit - run : | - source .venv/bin/activate - curl -Ls https://download.splunk.com/misc/packaging-toolkit/splunk-packaging-toolkit-1.0.1.tar.gz -o splunk-packaging-toolkit-latest.tar.gz - mkdir slim-latest - tar -zxf splunk-packaging-toolkit-latest.tar.gz -C slim-latest --strip-components=1 - cd slim-latest - python -m pip install . - cd .. - name: Build ESCU run: | @@ -162,25 +153,19 @@ jobs: tar -zxf content-pack-build-ssa.tar.gz mv dist/escu DA-ESS-ContentUpdate mv dist/ssa SSA_Content - slim package -o upload DA-ESS-ContentUpdate - cp upload/DA-ESS-ContentUpdate-*.tar.gz DA-ESS-ContentUpdate-latest.tar.gz + + #Build ESCU Content + #Do not use slim for speed, simplicity, and compatability + tar -zcf DA-ESS-ContentUpdate-latest.tar.gz DA-ESS-ContentUpdate sha256sum DA-ESS-ContentUpdate-latest.tar.gz > checksum.txt - #Do this copy so that we conform as much as possible, and have to make - #as few changes as possible, once we start generating this as a real, - #properly packaged app - tar -zcf upload/SSA_Content-NO_SLIM.tar.gz SSA_Content - cp upload/SSA_Content-*.tar.gz SSA_Content-latest.tar.gz + + #Build the SSA Content + #Do not use slim for speed, simplicity, and compatability + tar -zcf SSA_Content-latest.tar.gz SSA_Content sha256sum SSA_Content-latest.tar.gz >> checksum.txt - - - name: store_artifacts - uses: actions/upload-artifact@v2 - with: - name: package - path: | - build/upload - - name: store_artifacts_two + - name: store_artifacts uses: actions/upload-artifact@v2 with: name: content-latest diff --git a/bin/docker_detection_tester/detection_testing_execution.py b/bin/docker_detection_tester/detection_testing_execution.py index 4df19e5037..ecb592a413 100644 --- a/bin/docker_detection_tester/detection_testing_execution.py +++ b/bin/docker_detection_tester/detection_testing_execution.py @@ -212,7 +212,8 @@ def generate_escu_app(persist_security_content: bool = False) -> str: # There remove the latest file if it exists commands = ["cd slim_packaging", "cp -R ../dist/escu DA-ESS-ContentUpdate", - "slim package -o upload DA-ESS-ContentUpdate", + "mkdir upload", + "tar -czf upload/DA-ESS-ContentUpdate*.tar.gz DA-ESS-ContentUpdate", "cp upload/DA-ESS-ContentUpdate*.tar.gz %s" % (output_file_path_from_slim_latest)] else: @@ -221,7 +222,8 @@ def generate_escu_app(persist_security_content: bool = False) -> str: "mkdir slim_packaging", "cd slim_packaging", "cp -R ../dist/escu DA-ESS-ContentUpdate", - "slim package -o upload DA-ESS-ContentUpdate", + "mkdir upload", + "tar -czf upload/DA-ESS-ContentUpdate*.tar.gz DA-ESS-ContentUpdate", "cp upload/DA-ESS-ContentUpdate*.tar.gz %s" % (output_file_path_from_slim_latest)] ret = subprocess.run("; ".join(commands), diff --git a/requirements.txt b/requirements.txt index 28638cb80c..b8d35df335 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,6 @@ questionary==1.10.0 requests==2.28.1 six==1.16.0 splunk-appinspect==2.25.0 -splunk-packaging-toolkit==1.0.1 splunk-sdk==1.7.2 wrapt-timeout-decorator==1.3.12.2 xmltodict==0.13.0 From 061e486514d829881afa0921fa9d1c0f605474a1 Mon Sep 17 00:00:00 2001 From: Michael Haag <5632822+MHaggis@users.noreply.github.com> Date: Sun, 2 Oct 2022 06:52:53 -0600 Subject: [PATCH 08/10] Update exchange_powershell_abuse_via_ssrf.yml Resolving #2397 --- .../endpoint/exchange_powershell_abuse_via_ssrf.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/detections/experimental/endpoint/exchange_powershell_abuse_via_ssrf.yml b/detections/experimental/endpoint/exchange_powershell_abuse_via_ssrf.yml index 014eaac725..d5a456f636 100644 --- a/detections/experimental/endpoint/exchange_powershell_abuse_via_ssrf.yml +++ b/detections/experimental/endpoint/exchange_powershell_abuse_via_ssrf.yml @@ -1,7 +1,7 @@ name: Exchange PowerShell Abuse via SSRF id: 29228ab4-0762-11ec-94aa-acde48001122 -version: 1 -date: '2021-08-27' +version: 2 +date: '2022-10-02' author: Michael Haag, Splunk type: TTP datamodel: [] @@ -20,7 +20,7 @@ description: 'This analytic identifies suspicious behavior related to ProxyShell Review the source attempting to perform this activity against your environment. In addition, review PowerShell logs and access recently granted to Exchange roles.' -search: '| `exchange` c_uri="*//autodiscover.json*" cs_uri_query="*PowerShell*" cs_method="POST" +search: '`exchange` c_uri="*//autodiscover.json*" cs_uri_query="*PowerShell*" cs_method="POST" | stats count min(_time) as firstTime max(_time) as lastTime by dest, cs_uri_query, cs_method, c_uri | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `exchange_powershell_abuse_via_ssrf_filter`' From 2566899c76c378c9eaa15515874148e6d56ed8e8 Mon Sep 17 00:00:00 2001 From: Lou Stella Date: Mon, 3 Oct 2022 13:01:30 -0500 Subject: [PATCH 09/10] #2398 fix --- .../splunk_account_discovery_drilldown_dashboard_disclosure.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detections/experimental/application/splunk_account_discovery_drilldown_dashboard_disclosure.yml b/detections/experimental/application/splunk_account_discovery_drilldown_dashboard_disclosure.yml index 2aa47bbf0e..268a9cf325 100644 --- a/detections/experimental/application/splunk_account_discovery_drilldown_dashboard_disclosure.yml +++ b/detections/experimental/application/splunk_account_discovery_drilldown_dashboard_disclosure.yml @@ -5,7 +5,7 @@ date: '2022-08-02' author: Marissa Bower, Rod Soto, Splunk type: TTP datamodel: [] -search: '| rest splunk_server=local /servicesNS/-/-/data/ui/views | search eai:data="*$env:*" eai:data="*url*" eai:data="*options*" | rename author AS Author eai:acl.sharing AS Permissions eai:appName AS App eai:data AS "Dashboard XML" | fields Author Permissions App "Dashboard XML" | `splunk_drilldown_dashboard_disclosure_filter`' +search: '| rest splunk_server=local /servicesNS/-/-/data/ui/views | search eai:data="*$env:*" eai:data="*url*" eai:data="*options*" | rename author AS Author eai:acl.sharing AS Permissions eai:appName AS App eai:data AS "Dashboard XML" | fields Author Permissions App "Dashboard XML" | `splunk_account_discovery_drilldown_dashboard_disclosure_filter`' description: Splunk drilldown vulnerability disclosure in Dashboard application that can potentially allow exposure of tokens from privilege users. An attacker can create dashboard and share it to privileged user (admin) and detokenize variables using external urls within dashboards drilldown function. how_to_implement: This search uses REST function to query for dashboards with environment variables present in URL options. known_false_positives: This search may reveal non malicious URLs with environment variables used in organizations. From 83850b8b59452b133d57226c464af6d4af88d379 Mon Sep 17 00:00:00 2001 From: Lou Stella Date: Mon, 3 Oct 2022 13:01:57 -0500 Subject: [PATCH 10/10] #2402 fix --- .../experimental/network/remote_desktop_network_traffic.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detections/experimental/network/remote_desktop_network_traffic.yml b/detections/experimental/network/remote_desktop_network_traffic.yml index 23404f523f..653b7fcc2c 100644 --- a/detections/experimental/network/remote_desktop_network_traffic.yml +++ b/detections/experimental/network/remote_desktop_network_traffic.yml @@ -13,7 +13,7 @@ description: This search looks for network traffic on TCP/3389, the default port on your network. search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Network_Traffic where All_Traffic.dest_port=3389 AND - All_Traffic.dest_category!=common_rdp_destination AND All_Traffic.src_category!=common_rdp_source + All_Traffic.dest_category!=common_rdp_destination AND All_Traffic.src_category!=common_rdp_source AND all_Traffic.action="allowed" by All_Traffic.src All_Traffic.dest All_Traffic.dest_port | `drop_dm_object_name("All_Traffic")` | `security_content_ctime(firstTime)`| `security_content_ctime(lastTime)` | `remote_desktop_network_traffic_filter` ' how_to_implement: To successfully implement this search you need to identify systems