diff --git a/rules-threat-hunting/cloud/m365/microsoft365_susp_email_forwarding_activity.yml b/rules-threat-hunting/cloud/m365/audit/microsoft365_susp_email_forwarding_activity.yml similarity index 100% rename from rules-threat-hunting/cloud/m365/microsoft365_susp_email_forwarding_activity.yml rename to rules-threat-hunting/cloud/m365/audit/microsoft365_susp_email_forwarding_activity.yml diff --git a/rules/cloud/aws/aws_attached_malicious_lambda_layer.yml b/rules/cloud/aws/cloudtrail/aws_attached_malicious_lambda_layer.yml similarity index 100% rename from rules/cloud/aws/aws_attached_malicious_lambda_layer.yml rename to rules/cloud/aws/cloudtrail/aws_attached_malicious_lambda_layer.yml diff --git a/rules/cloud/aws/aws_cloudtrail_disable_logging.yml b/rules/cloud/aws/cloudtrail/aws_cloudtrail_disable_logging.yml similarity index 100% rename from rules/cloud/aws/aws_cloudtrail_disable_logging.yml rename to rules/cloud/aws/cloudtrail/aws_cloudtrail_disable_logging.yml diff --git a/rules/cloud/aws/aws_config_disable_recording.yml b/rules/cloud/aws/cloudtrail/aws_config_disable_recording.yml similarity index 100% rename from rules/cloud/aws/aws_config_disable_recording.yml rename to rules/cloud/aws/cloudtrail/aws_config_disable_recording.yml diff --git a/rules/cloud/aws/aws_delete_identity.yml b/rules/cloud/aws/cloudtrail/aws_delete_identity.yml similarity index 100% rename from rules/cloud/aws/aws_delete_identity.yml rename to rules/cloud/aws/cloudtrail/aws_delete_identity.yml diff --git a/rules/cloud/aws/aws_ec2_disable_encryption.yml b/rules/cloud/aws/cloudtrail/aws_ec2_disable_encryption.yml similarity index 100% rename from rules/cloud/aws/aws_ec2_disable_encryption.yml rename to rules/cloud/aws/cloudtrail/aws_ec2_disable_encryption.yml diff --git a/rules/cloud/aws/aws_ec2_startup_script_change.yml b/rules/cloud/aws/cloudtrail/aws_ec2_startup_script_change.yml similarity index 100% rename from rules/cloud/aws/aws_ec2_startup_script_change.yml rename to rules/cloud/aws/cloudtrail/aws_ec2_startup_script_change.yml diff --git a/rules/cloud/aws/aws_ec2_vm_export_failure.yml b/rules/cloud/aws/cloudtrail/aws_ec2_vm_export_failure.yml similarity index 100% rename from rules/cloud/aws/aws_ec2_vm_export_failure.yml rename to rules/cloud/aws/cloudtrail/aws_ec2_vm_export_failure.yml diff --git a/rules/cloud/aws/aws_ecs_task_definition_cred_endpoint_query.yml b/rules/cloud/aws/cloudtrail/aws_ecs_task_definition_cred_endpoint_query.yml similarity index 100% rename from rules/cloud/aws/aws_ecs_task_definition_cred_endpoint_query.yml rename to rules/cloud/aws/cloudtrail/aws_ecs_task_definition_cred_endpoint_query.yml diff --git a/rules/cloud/aws/aws_efs_fileshare_modified_or_deleted.yml b/rules/cloud/aws/cloudtrail/aws_efs_fileshare_modified_or_deleted.yml similarity index 100% rename from rules/cloud/aws/aws_efs_fileshare_modified_or_deleted.yml rename to rules/cloud/aws/cloudtrail/aws_efs_fileshare_modified_or_deleted.yml diff --git a/rules/cloud/aws/aws_efs_fileshare_mount_modified_or_deleted.yml b/rules/cloud/aws/cloudtrail/aws_efs_fileshare_mount_modified_or_deleted.yml similarity index 100% rename from rules/cloud/aws/aws_efs_fileshare_mount_modified_or_deleted.yml rename to rules/cloud/aws/cloudtrail/aws_efs_fileshare_mount_modified_or_deleted.yml diff --git a/rules/cloud/aws/aws_eks_cluster_created_or_deleted.yml b/rules/cloud/aws/cloudtrail/aws_eks_cluster_created_or_deleted.yml similarity index 100% rename from rules/cloud/aws/aws_eks_cluster_created_or_deleted.yml rename to rules/cloud/aws/cloudtrail/aws_eks_cluster_created_or_deleted.yml diff --git a/rules/cloud/aws/aws_elasticache_security_group_created.yml b/rules/cloud/aws/cloudtrail/aws_elasticache_security_group_created.yml similarity index 100% rename from rules/cloud/aws/aws_elasticache_security_group_created.yml rename to rules/cloud/aws/cloudtrail/aws_elasticache_security_group_created.yml diff --git a/rules/cloud/aws/aws_elasticache_security_group_modified_or_deleted.yml b/rules/cloud/aws/cloudtrail/aws_elasticache_security_group_modified_or_deleted.yml similarity index 100% rename from rules/cloud/aws/aws_elasticache_security_group_modified_or_deleted.yml rename to rules/cloud/aws/cloudtrail/aws_elasticache_security_group_modified_or_deleted.yml diff --git a/rules/cloud/aws/aws_enum_buckets.yml b/rules/cloud/aws/cloudtrail/aws_enum_buckets.yml similarity index 100% rename from rules/cloud/aws/aws_enum_buckets.yml rename to rules/cloud/aws/cloudtrail/aws_enum_buckets.yml diff --git a/rules/cloud/aws/aws_guardduty_disruption.yml b/rules/cloud/aws/cloudtrail/aws_guardduty_disruption.yml similarity index 100% rename from rules/cloud/aws/aws_guardduty_disruption.yml rename to rules/cloud/aws/cloudtrail/aws_guardduty_disruption.yml diff --git a/rules/cloud/aws/aws_iam_backdoor_users_keys.yml b/rules/cloud/aws/cloudtrail/aws_iam_backdoor_users_keys.yml similarity index 100% rename from rules/cloud/aws/aws_iam_backdoor_users_keys.yml rename to rules/cloud/aws/cloudtrail/aws_iam_backdoor_users_keys.yml diff --git a/rules/cloud/aws/aws_iam_s3browser_loginprofile_creation.yml b/rules/cloud/aws/cloudtrail/aws_iam_s3browser_loginprofile_creation.yml similarity index 100% rename from rules/cloud/aws/aws_iam_s3browser_loginprofile_creation.yml rename to rules/cloud/aws/cloudtrail/aws_iam_s3browser_loginprofile_creation.yml diff --git a/rules/cloud/aws/aws_iam_s3browser_templated_s3_bucket_policy_creation.yml b/rules/cloud/aws/cloudtrail/aws_iam_s3browser_templated_s3_bucket_policy_creation.yml similarity index 100% rename from rules/cloud/aws/aws_iam_s3browser_templated_s3_bucket_policy_creation.yml rename to rules/cloud/aws/cloudtrail/aws_iam_s3browser_templated_s3_bucket_policy_creation.yml diff --git a/rules/cloud/aws/aws_iam_s3browser_user_or_accesskey_creation.yml b/rules/cloud/aws/cloudtrail/aws_iam_s3browser_user_or_accesskey_creation.yml similarity index 100% rename from rules/cloud/aws/aws_iam_s3browser_user_or_accesskey_creation.yml rename to rules/cloud/aws/cloudtrail/aws_iam_s3browser_user_or_accesskey_creation.yml diff --git a/rules/cloud/aws/aws_passed_role_to_glue_development_endpoint.yml b/rules/cloud/aws/cloudtrail/aws_passed_role_to_glue_development_endpoint.yml similarity index 100% rename from rules/cloud/aws/aws_passed_role_to_glue_development_endpoint.yml rename to rules/cloud/aws/cloudtrail/aws_passed_role_to_glue_development_endpoint.yml diff --git a/rules/cloud/aws/aws_rds_change_master_password.yml b/rules/cloud/aws/cloudtrail/aws_rds_change_master_password.yml similarity index 100% rename from rules/cloud/aws/aws_rds_change_master_password.yml rename to rules/cloud/aws/cloudtrail/aws_rds_change_master_password.yml diff --git a/rules/cloud/aws/aws_rds_public_db_restore.yml b/rules/cloud/aws/cloudtrail/aws_rds_public_db_restore.yml similarity index 100% rename from rules/cloud/aws/aws_rds_public_db_restore.yml rename to rules/cloud/aws/cloudtrail/aws_rds_public_db_restore.yml diff --git a/rules/cloud/aws/aws_root_account_usage.yml b/rules/cloud/aws/cloudtrail/aws_root_account_usage.yml similarity index 100% rename from rules/cloud/aws/aws_root_account_usage.yml rename to rules/cloud/aws/cloudtrail/aws_root_account_usage.yml diff --git a/rules/cloud/aws/aws_route_53_domain_transferred_lock_disabled.yml b/rules/cloud/aws/cloudtrail/aws_route_53_domain_transferred_lock_disabled.yml similarity index 100% rename from rules/cloud/aws/aws_route_53_domain_transferred_lock_disabled.yml rename to rules/cloud/aws/cloudtrail/aws_route_53_domain_transferred_lock_disabled.yml diff --git a/rules/cloud/aws/aws_route_53_domain_transferred_to_another_account.yml b/rules/cloud/aws/cloudtrail/aws_route_53_domain_transferred_to_another_account.yml similarity index 100% rename from rules/cloud/aws/aws_route_53_domain_transferred_to_another_account.yml rename to rules/cloud/aws/cloudtrail/aws_route_53_domain_transferred_to_another_account.yml diff --git a/rules/cloud/aws/aws_s3_data_management_tampering.yml b/rules/cloud/aws/cloudtrail/aws_s3_data_management_tampering.yml similarity index 100% rename from rules/cloud/aws/aws_s3_data_management_tampering.yml rename to rules/cloud/aws/cloudtrail/aws_s3_data_management_tampering.yml diff --git a/rules/cloud/aws/aws_securityhub_finding_evasion.yml b/rules/cloud/aws/cloudtrail/aws_securityhub_finding_evasion.yml similarity index 100% rename from rules/cloud/aws/aws_securityhub_finding_evasion.yml rename to rules/cloud/aws/cloudtrail/aws_securityhub_finding_evasion.yml diff --git a/rules/cloud/aws/aws_snapshot_backup_exfiltration.yml b/rules/cloud/aws/cloudtrail/aws_snapshot_backup_exfiltration.yml similarity index 100% rename from rules/cloud/aws/aws_snapshot_backup_exfiltration.yml rename to rules/cloud/aws/cloudtrail/aws_snapshot_backup_exfiltration.yml diff --git a/rules/cloud/aws/aws_sso_idp_change.yml b/rules/cloud/aws/cloudtrail/aws_sso_idp_change.yml similarity index 100% rename from rules/cloud/aws/aws_sso_idp_change.yml rename to rules/cloud/aws/cloudtrail/aws_sso_idp_change.yml diff --git a/rules/cloud/aws/aws_sts_assumerole_misuse.yml b/rules/cloud/aws/cloudtrail/aws_sts_assumerole_misuse.yml similarity index 100% rename from rules/cloud/aws/aws_sts_assumerole_misuse.yml rename to rules/cloud/aws/cloudtrail/aws_sts_assumerole_misuse.yml diff --git a/rules/cloud/aws/aws_sts_getsessiontoken_misuse.yml b/rules/cloud/aws/cloudtrail/aws_sts_getsessiontoken_misuse.yml similarity index 100% rename from rules/cloud/aws/aws_sts_getsessiontoken_misuse.yml rename to rules/cloud/aws/cloudtrail/aws_sts_getsessiontoken_misuse.yml diff --git a/rules/cloud/aws/aws_susp_saml_activity.yml b/rules/cloud/aws/cloudtrail/aws_susp_saml_activity.yml similarity index 100% rename from rules/cloud/aws/aws_susp_saml_activity.yml rename to rules/cloud/aws/cloudtrail/aws_susp_saml_activity.yml diff --git a/rules/cloud/aws/aws_update_login_profile.yml b/rules/cloud/aws/cloudtrail/aws_update_login_profile.yml similarity index 100% rename from rules/cloud/aws/aws_update_login_profile.yml rename to rules/cloud/aws/cloudtrail/aws_update_login_profile.yml diff --git a/rules/cloud/azure/azure_aadhybridhealth_adfs_new_server.yml b/rules/cloud/azure/activity_logs/azure_aadhybridhealth_adfs_new_server.yml similarity index 96% rename from rules/cloud/azure/azure_aadhybridhealth_adfs_new_server.yml rename to rules/cloud/azure/activity_logs/azure_aadhybridhealth_adfs_new_server.yml index 9444f3b94..330590535 100644 --- a/rules/cloud/azure/azure_aadhybridhealth_adfs_new_server.yml +++ b/rules/cloud/azure/activity_logs/azure_aadhybridhealth_adfs_new_server.yml @@ -9,13 +9,13 @@ references: - https://o365blog.com/post/hybridhealthagent/ author: Roberto Rodriguez (Cyb3rWard0g), OTR (Open Threat Research), MSTIC date: 2021/08/26 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.defense_evasion - attack.t1578 logsource: product: azure - service: azureactivity + service: activitylogs detection: selection: CategoryValue: 'Administrative' diff --git a/rules/cloud/azure/azure_aadhybridhealth_adfs_service_delete.yml b/rules/cloud/azure/activity_logs/azure_aadhybridhealth_adfs_service_delete.yml similarity index 95% rename from rules/cloud/azure/azure_aadhybridhealth_adfs_service_delete.yml rename to rules/cloud/azure/activity_logs/azure_aadhybridhealth_adfs_service_delete.yml index e0437c976..e3b8547b6 100644 --- a/rules/cloud/azure/azure_aadhybridhealth_adfs_service_delete.yml +++ b/rules/cloud/azure/activity_logs/azure_aadhybridhealth_adfs_service_delete.yml @@ -9,13 +9,13 @@ references: - https://o365blog.com/post/hybridhealthagent/ author: Roberto Rodriguez (Cyb3rWard0g), OTR (Open Threat Research), MSTIC date: 2021/08/26 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.defense_evasion - attack.t1578.003 logsource: product: azure - service: azureactivity + service: activitylogs detection: selection: CategoryValue: 'Administrative' diff --git a/rules/cloud/azure/azure_ad_user_added_to_admin_role.yml b/rules/cloud/azure/activity_logs/azure_ad_user_added_to_admin_role.yml similarity index 100% rename from rules/cloud/azure/azure_ad_user_added_to_admin_role.yml rename to rules/cloud/azure/activity_logs/azure_ad_user_added_to_admin_role.yml diff --git a/rules/cloud/azure/azure_app_credential_modification.yml b/rules/cloud/azure/activity_logs/azure_app_credential_modification.yml similarity index 100% rename from rules/cloud/azure/azure_app_credential_modification.yml rename to rules/cloud/azure/activity_logs/azure_app_credential_modification.yml diff --git a/rules/cloud/azure/azure_application_deleted.yml b/rules/cloud/azure/activity_logs/azure_application_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_application_deleted.yml rename to rules/cloud/azure/activity_logs/azure_application_deleted.yml diff --git a/rules/cloud/azure/azure_application_gateway_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_application_gateway_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_application_gateway_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_application_gateway_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_application_security_group_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_application_security_group_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_application_security_group_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_application_security_group_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_container_registry_created_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_container_registry_created_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_container_registry_created_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_container_registry_created_or_deleted.yml diff --git a/rules/cloud/azure/azure_creating_number_of_resources_detection.yml b/rules/cloud/azure/activity_logs/azure_creating_number_of_resources_detection.yml similarity index 93% rename from rules/cloud/azure/azure_creating_number_of_resources_detection.yml rename to rules/cloud/azure/activity_logs/azure_creating_number_of_resources_detection.yml index 6d6a8c778..e7363dca4 100644 --- a/rules/cloud/azure/azure_creating_number_of_resources_detection.yml +++ b/rules/cloud/azure/activity_logs/azure_creating_number_of_resources_detection.yml @@ -6,13 +6,13 @@ references: - https://github.com/Azure/Azure-Sentinel/blob/e534407884b1ec5371efc9f76ead282176c9e8bb/Detections/AzureActivity/Creating_Anomalous_Number_Of_Resources_detection.yaml author: sawwinnnaung date: 2020/05/07 -modified: 2021/11/27 +modified: 2023/10/11 tags: - attack.persistence - attack.t1098 logsource: product: azure - service: azureactivity + service: activitylogs detection: keywords: - Microsoft.Compute/virtualMachines/write diff --git a/rules/cloud/azure/azure_device_no_longer_managed_or_compliant.yml b/rules/cloud/azure/activity_logs/azure_device_no_longer_managed_or_compliant.yml similarity index 100% rename from rules/cloud/azure/azure_device_no_longer_managed_or_compliant.yml rename to rules/cloud/azure/activity_logs/azure_device_no_longer_managed_or_compliant.yml diff --git a/rules/cloud/azure/azure_device_or_configuration_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_device_or_configuration_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_device_or_configuration_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_device_or_configuration_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_dns_zone_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_dns_zone_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_dns_zone_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_dns_zone_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_firewall_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_firewall_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_firewall_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_firewall_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_firewall_rule_collection_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_firewall_rule_collection_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_firewall_rule_collection_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_firewall_rule_collection_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_granting_permission_detection.yml b/rules/cloud/azure/activity_logs/azure_granting_permission_detection.yml similarity index 93% rename from rules/cloud/azure/azure_granting_permission_detection.yml rename to rules/cloud/azure/activity_logs/azure_granting_permission_detection.yml index df2b45138..58cb33d52 100644 --- a/rules/cloud/azure/azure_granting_permission_detection.yml +++ b/rules/cloud/azure/activity_logs/azure_granting_permission_detection.yml @@ -6,13 +6,13 @@ references: - https://github.com/Azure/Azure-Sentinel/blob/e534407884b1ec5371efc9f76ead282176c9e8bb/Detections/AzureActivity/Granting_Permissions_To_Account_detection.yaml author: sawwinnnaung date: 2020/05/07 -modified: 2021/11/27 +modified: 2023/10/11 tags: - attack.persistence - attack.t1098.003 logsource: product: azure - service: azureactivity + service: activitylogs detection: keywords: - Microsoft.Authorization/roleAssignments/write diff --git a/rules/cloud/azure/azure_keyvault_key_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_keyvault_key_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_keyvault_key_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_keyvault_key_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_keyvault_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_keyvault_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_keyvault_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_keyvault_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_keyvault_secrets_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_keyvault_secrets_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_keyvault_secrets_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_keyvault_secrets_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_kubernetes_admission_controller.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_admission_controller.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_admission_controller.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_admission_controller.yml diff --git a/rules/cloud/azure/azure_kubernetes_cluster_created_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_cluster_created_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_cluster_created_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_cluster_created_or_deleted.yml diff --git a/rules/cloud/azure/azure_kubernetes_cronjob.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_cronjob.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_cronjob.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_cronjob.yml diff --git a/rules/cloud/azure/azure_kubernetes_events_deleted.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_events_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_events_deleted.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_events_deleted.yml diff --git a/rules/cloud/azure/azure_kubernetes_network_policy_change.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_network_policy_change.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_network_policy_change.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_network_policy_change.yml diff --git a/rules/cloud/azure/azure_kubernetes_pods_deleted.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_pods_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_pods_deleted.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_pods_deleted.yml diff --git a/rules/cloud/azure/azure_kubernetes_role_access.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_role_access.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_role_access.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_role_access.yml diff --git a/rules/cloud/azure/azure_kubernetes_rolebinding_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_rolebinding_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_rolebinding_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_rolebinding_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_kubernetes_secret_or_config_object_access.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_secret_or_config_object_access.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_secret_or_config_object_access.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_secret_or_config_object_access.yml diff --git a/rules/cloud/azure/azure_kubernetes_service_account_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_kubernetes_service_account_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_kubernetes_service_account_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_kubernetes_service_account_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_mfa_disabled.yml b/rules/cloud/azure/activity_logs/azure_mfa_disabled.yml similarity index 100% rename from rules/cloud/azure/azure_mfa_disabled.yml rename to rules/cloud/azure/activity_logs/azure_mfa_disabled.yml diff --git a/rules/cloud/azure/azure_network_firewall_policy_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_network_firewall_policy_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_network_firewall_policy_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_network_firewall_policy_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_network_firewall_rule_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_network_firewall_rule_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_network_firewall_rule_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_network_firewall_rule_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_network_p2s_vpn_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_network_p2s_vpn_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_network_p2s_vpn_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_network_p2s_vpn_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_network_security_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_network_security_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_network_security_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_network_security_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_network_virtual_device_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_network_virtual_device_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_network_virtual_device_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_network_virtual_device_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_new_cloudshell_created.yml b/rules/cloud/azure/activity_logs/azure_new_cloudshell_created.yml similarity index 100% rename from rules/cloud/azure/azure_new_cloudshell_created.yml rename to rules/cloud/azure/activity_logs/azure_new_cloudshell_created.yml diff --git a/rules/cloud/azure/azure_owner_removed_from_application_or_service_principal.yml b/rules/cloud/azure/activity_logs/azure_owner_removed_from_application_or_service_principal.yml similarity index 100% rename from rules/cloud/azure/azure_owner_removed_from_application_or_service_principal.yml rename to rules/cloud/azure/activity_logs/azure_owner_removed_from_application_or_service_principal.yml diff --git a/rules/cloud/azure/azure_rare_operations.yml b/rules/cloud/azure/activity_logs/azure_rare_operations.yml similarity index 95% rename from rules/cloud/azure/azure_rare_operations.yml rename to rules/cloud/azure/activity_logs/azure_rare_operations.yml index ddc2442b6..f7776cf79 100644 --- a/rules/cloud/azure/azure_rare_operations.yml +++ b/rules/cloud/azure/activity_logs/azure_rare_operations.yml @@ -6,12 +6,12 @@ references: - https://github.com/Azure/Azure-Sentinel/blob/e534407884b1ec5371efc9f76ead282176c9e8bb/Detections/AzureActivity/RareOperations.yaml author: sawwinnnaung date: 2020/05/07 -modified: 2021/11/27 +modified: 2023/10/11 tags: - attack.t1003 logsource: product: azure - service: azureactivity + service: activitylogs detection: keywords: - Microsoft.DocumentDB/databaseAccounts/listKeys/action diff --git a/rules/cloud/azure/azure_service_principal_created.yml b/rules/cloud/azure/activity_logs/azure_service_principal_created.yml similarity index 100% rename from rules/cloud/azure/azure_service_principal_created.yml rename to rules/cloud/azure/activity_logs/azure_service_principal_created.yml diff --git a/rules/cloud/azure/azure_service_principal_removed.yml b/rules/cloud/azure/activity_logs/azure_service_principal_removed.yml similarity index 100% rename from rules/cloud/azure/azure_service_principal_removed.yml rename to rules/cloud/azure/activity_logs/azure_service_principal_removed.yml diff --git a/rules/cloud/azure/azure_subscription_permissions_elevation_via_activitylogs.yml b/rules/cloud/azure/activity_logs/azure_subscription_permissions_elevation_via_activitylogs.yml similarity index 100% rename from rules/cloud/azure/azure_subscription_permissions_elevation_via_activitylogs.yml rename to rules/cloud/azure/activity_logs/azure_subscription_permissions_elevation_via_activitylogs.yml diff --git a/rules/cloud/azure/azure_suppression_rule_created.yml b/rules/cloud/azure/activity_logs/azure_suppression_rule_created.yml similarity index 100% rename from rules/cloud/azure/azure_suppression_rule_created.yml rename to rules/cloud/azure/activity_logs/azure_suppression_rule_created.yml diff --git a/rules/cloud/azure/azure_virtual_network_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_virtual_network_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_virtual_network_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_virtual_network_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_vpn_connection_modified_or_deleted.yml b/rules/cloud/azure/activity_logs/azure_vpn_connection_modified_or_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_vpn_connection_modified_or_deleted.yml rename to rules/cloud/azure/activity_logs/azure_vpn_connection_modified_or_deleted.yml diff --git a/rules/cloud/azure/azure_aad_secops_ca_policy_removedby_bad_actor.yml b/rules/cloud/azure/audit_logs/azure_aad_secops_ca_policy_removedby_bad_actor.yml similarity index 100% rename from rules/cloud/azure/azure_aad_secops_ca_policy_removedby_bad_actor.yml rename to rules/cloud/azure/audit_logs/azure_aad_secops_ca_policy_removedby_bad_actor.yml diff --git a/rules/cloud/azure/azure_aad_secops_ca_policy_updatedby_bad_actor.yml b/rules/cloud/azure/audit_logs/azure_aad_secops_ca_policy_updatedby_bad_actor.yml similarity index 100% rename from rules/cloud/azure/azure_aad_secops_ca_policy_updatedby_bad_actor.yml rename to rules/cloud/azure/audit_logs/azure_aad_secops_ca_policy_updatedby_bad_actor.yml diff --git a/rules/cloud/azure/azure_aad_secops_new_ca_policy_addedby_bad_actor.yml b/rules/cloud/azure/audit_logs/azure_aad_secops_new_ca_policy_addedby_bad_actor.yml similarity index 100% rename from rules/cloud/azure/azure_aad_secops_new_ca_policy_addedby_bad_actor.yml rename to rules/cloud/azure/audit_logs/azure_aad_secops_new_ca_policy_addedby_bad_actor.yml diff --git a/rules/cloud/azure/azure_ad_account_created_deleted.yml b/rules/cloud/azure/audit_logs/azure_ad_account_created_deleted.yml similarity index 100% rename from rules/cloud/azure/azure_ad_account_created_deleted.yml rename to rules/cloud/azure/audit_logs/azure_ad_account_created_deleted.yml diff --git a/rules/cloud/azure/azure_ad_bitlocker_key_retrieval.yml b/rules/cloud/azure/audit_logs/azure_ad_bitlocker_key_retrieval.yml similarity index 100% rename from rules/cloud/azure/azure_ad_bitlocker_key_retrieval.yml rename to rules/cloud/azure/audit_logs/azure_ad_bitlocker_key_retrieval.yml diff --git a/rules/cloud/azure/azure_ad_device_registration_policy_changes.yml b/rules/cloud/azure/audit_logs/azure_ad_device_registration_policy_changes.yml similarity index 100% rename from rules/cloud/azure/azure_ad_device_registration_policy_changes.yml rename to rules/cloud/azure/audit_logs/azure_ad_device_registration_policy_changes.yml diff --git a/rules/cloud/azure/azure_ad_guest_users_invited_to_tenant_by_non_approved_inviters.yml b/rules/cloud/azure/audit_logs/azure_ad_guest_users_invited_to_tenant_by_non_approved_inviters.yml similarity index 100% rename from rules/cloud/azure/azure_ad_guest_users_invited_to_tenant_by_non_approved_inviters.yml rename to rules/cloud/azure/audit_logs/azure_ad_guest_users_invited_to_tenant_by_non_approved_inviters.yml diff --git a/rules/cloud/azure/azure_ad_users_added_to_device_admin_roles.yml b/rules/cloud/azure/audit_logs/azure_ad_users_added_to_device_admin_roles.yml similarity index 100% rename from rules/cloud/azure/azure_ad_users_added_to_device_admin_roles.yml rename to rules/cloud/azure/audit_logs/azure_ad_users_added_to_device_admin_roles.yml diff --git a/rules/cloud/azure/azure_app_appid_uri_changes.yml b/rules/cloud/azure/audit_logs/azure_app_appid_uri_changes.yml similarity index 100% rename from rules/cloud/azure/azure_app_appid_uri_changes.yml rename to rules/cloud/azure/audit_logs/azure_app_appid_uri_changes.yml diff --git a/rules/cloud/azure/azure_app_credential_added.yml b/rules/cloud/azure/audit_logs/azure_app_credential_added.yml similarity index 100% rename from rules/cloud/azure/azure_app_credential_added.yml rename to rules/cloud/azure/audit_logs/azure_app_credential_added.yml diff --git a/rules/cloud/azure/azure_app_delegated_permissions_all_users.yml b/rules/cloud/azure/audit_logs/azure_app_delegated_permissions_all_users.yml similarity index 100% rename from rules/cloud/azure/azure_app_delegated_permissions_all_users.yml rename to rules/cloud/azure/audit_logs/azure_app_delegated_permissions_all_users.yml diff --git a/rules/cloud/azure/azure_app_end_user_consent.yml b/rules/cloud/azure/audit_logs/azure_app_end_user_consent.yml similarity index 100% rename from rules/cloud/azure/azure_app_end_user_consent.yml rename to rules/cloud/azure/audit_logs/azure_app_end_user_consent.yml diff --git a/rules/cloud/azure/azure_app_end_user_consent_blocked.yml b/rules/cloud/azure/audit_logs/azure_app_end_user_consent_blocked.yml similarity index 100% rename from rules/cloud/azure/azure_app_end_user_consent_blocked.yml rename to rules/cloud/azure/audit_logs/azure_app_end_user_consent_blocked.yml diff --git a/rules/cloud/azure/azure_app_owner_added.yml b/rules/cloud/azure/audit_logs/azure_app_owner_added.yml similarity index 100% rename from rules/cloud/azure/azure_app_owner_added.yml rename to rules/cloud/azure/audit_logs/azure_app_owner_added.yml diff --git a/rules/cloud/azure/azure_app_permissions_msft.yml b/rules/cloud/azure/audit_logs/azure_app_permissions_msft.yml similarity index 100% rename from rules/cloud/azure/azure_app_permissions_msft.yml rename to rules/cloud/azure/audit_logs/azure_app_permissions_msft.yml diff --git a/rules/cloud/azure/azure_app_privileged_permissions.yml b/rules/cloud/azure/audit_logs/azure_app_privileged_permissions.yml similarity index 100% rename from rules/cloud/azure/azure_app_privileged_permissions.yml rename to rules/cloud/azure/audit_logs/azure_app_privileged_permissions.yml diff --git a/rules/cloud/azure/azure_app_role_added.yml b/rules/cloud/azure/audit_logs/azure_app_role_added.yml similarity index 100% rename from rules/cloud/azure/azure_app_role_added.yml rename to rules/cloud/azure/audit_logs/azure_app_role_added.yml diff --git a/rules/cloud/azure/azure_app_uri_modifications.yml b/rules/cloud/azure/audit_logs/azure_app_uri_modifications.yml similarity index 100% rename from rules/cloud/azure/azure_app_uri_modifications.yml rename to rules/cloud/azure/audit_logs/azure_app_uri_modifications.yml diff --git a/rules/cloud/azure/azure_change_to_authentication_method.yml b/rules/cloud/azure/audit_logs/azure_change_to_authentication_method.yml similarity index 100% rename from rules/cloud/azure/azure_change_to_authentication_method.yml rename to rules/cloud/azure/audit_logs/azure_change_to_authentication_method.yml diff --git a/rules/cloud/azure/azure_federation_modified.yml b/rules/cloud/azure/audit_logs/azure_federation_modified.yml similarity index 100% rename from rules/cloud/azure/azure_federation_modified.yml rename to rules/cloud/azure/audit_logs/azure_federation_modified.yml diff --git a/rules/cloud/azure/azure_group_user_addition_ca_modification.yml b/rules/cloud/azure/audit_logs/azure_group_user_addition_ca_modification.yml similarity index 100% rename from rules/cloud/azure/azure_group_user_addition_ca_modification.yml rename to rules/cloud/azure/audit_logs/azure_group_user_addition_ca_modification.yml diff --git a/rules/cloud/azure/azure_group_user_removal_ca_modification.yml b/rules/cloud/azure/audit_logs/azure_group_user_removal_ca_modification.yml similarity index 100% rename from rules/cloud/azure/azure_group_user_removal_ca_modification.yml rename to rules/cloud/azure/audit_logs/azure_group_user_removal_ca_modification.yml diff --git a/rules/cloud/azure/azure_guest_invite_failure.yml b/rules/cloud/azure/audit_logs/azure_guest_invite_failure.yml similarity index 100% rename from rules/cloud/azure/azure_guest_invite_failure.yml rename to rules/cloud/azure/audit_logs/azure_guest_invite_failure.yml diff --git a/rules/cloud/azure/azure_guest_to_member.yml b/rules/cloud/azure/audit_logs/azure_guest_to_member.yml similarity index 100% rename from rules/cloud/azure/azure_guest_to_member.yml rename to rules/cloud/azure/audit_logs/azure_guest_to_member.yml diff --git a/rules/cloud/azure/azure_pim_activation_approve_deny.yml b/rules/cloud/azure/audit_logs/azure_pim_activation_approve_deny.yml similarity index 100% rename from rules/cloud/azure/azure_pim_activation_approve_deny.yml rename to rules/cloud/azure/audit_logs/azure_pim_activation_approve_deny.yml diff --git a/rules/cloud/azure/azure_pim_alerts_disabled.yml b/rules/cloud/azure/audit_logs/azure_pim_alerts_disabled.yml similarity index 100% rename from rules/cloud/azure/azure_pim_alerts_disabled.yml rename to rules/cloud/azure/audit_logs/azure_pim_alerts_disabled.yml diff --git a/rules/cloud/azure/azure_pim_change_settings.yml b/rules/cloud/azure/audit_logs/azure_pim_change_settings.yml similarity index 100% rename from rules/cloud/azure/azure_pim_change_settings.yml rename to rules/cloud/azure/audit_logs/azure_pim_change_settings.yml diff --git a/rules/cloud/azure/azure_priviledged_role_assignment_add.yml b/rules/cloud/azure/audit_logs/azure_priviledged_role_assignment_add.yml similarity index 100% rename from rules/cloud/azure/azure_priviledged_role_assignment_add.yml rename to rules/cloud/azure/audit_logs/azure_priviledged_role_assignment_add.yml diff --git a/rules/cloud/azure/azure_priviledged_role_assignment_bulk_change.yml b/rules/cloud/azure/audit_logs/azure_priviledged_role_assignment_bulk_change.yml similarity index 100% rename from rules/cloud/azure/azure_priviledged_role_assignment_bulk_change.yml rename to rules/cloud/azure/audit_logs/azure_priviledged_role_assignment_bulk_change.yml diff --git a/rules/cloud/azure/azure_privileged_account_creation.yml b/rules/cloud/azure/audit_logs/azure_privileged_account_creation.yml similarity index 100% rename from rules/cloud/azure/azure_privileged_account_creation.yml rename to rules/cloud/azure/audit_logs/azure_privileged_account_creation.yml diff --git a/rules/cloud/azure/azure_subscription_permissions_elevation_via_auditlogs.yml b/rules/cloud/azure/audit_logs/azure_subscription_permissions_elevation_via_auditlogs.yml similarity index 100% rename from rules/cloud/azure/azure_subscription_permissions_elevation_via_auditlogs.yml rename to rules/cloud/azure/audit_logs/azure_subscription_permissions_elevation_via_auditlogs.yml diff --git a/rules/cloud/azure/azure_tap_added.yml b/rules/cloud/azure/audit_logs/azure_tap_added.yml similarity index 100% rename from rules/cloud/azure/azure_tap_added.yml rename to rules/cloud/azure/audit_logs/azure_tap_added.yml diff --git a/rules/cloud/azure/azure_user_password_change.yml b/rules/cloud/azure/audit_logs/azure_user_password_change.yml similarity index 100% rename from rules/cloud/azure/azure_user_password_change.yml rename to rules/cloud/azure/audit_logs/azure_user_password_change.yml diff --git a/rules/cloud/azure/azure_account_lockout.yml b/rules/cloud/azure/signin_logs/azure_account_lockout.yml similarity index 100% rename from rules/cloud/azure/azure_account_lockout.yml rename to rules/cloud/azure/signin_logs/azure_account_lockout.yml diff --git a/rules/cloud/azure/azure_ad_auth_failure_increase.yml b/rules/cloud/azure/signin_logs/azure_ad_auth_failure_increase.yml similarity index 100% rename from rules/cloud/azure/azure_ad_auth_failure_increase.yml rename to rules/cloud/azure/signin_logs/azure_ad_auth_failure_increase.yml diff --git a/rules/cloud/azure/azure_ad_auth_sucess_increase.yml b/rules/cloud/azure/signin_logs/azure_ad_auth_sucess_increase.yml similarity index 100% rename from rules/cloud/azure/azure_ad_auth_sucess_increase.yml rename to rules/cloud/azure/signin_logs/azure_ad_auth_sucess_increase.yml diff --git a/rules/cloud/azure/azure_ad_auth_to_important_apps_using_single_factor_auth.yml b/rules/cloud/azure/signin_logs/azure_ad_auth_to_important_apps_using_single_factor_auth.yml similarity index 100% rename from rules/cloud/azure/azure_ad_auth_to_important_apps_using_single_factor_auth.yml rename to rules/cloud/azure/signin_logs/azure_ad_auth_to_important_apps_using_single_factor_auth.yml diff --git a/rules/cloud/azure/azure_ad_authentications_from_countries_you_do_not_operate_out_of.yml b/rules/cloud/azure/signin_logs/azure_ad_authentications_from_countries_you_do_not_operate_out_of.yml similarity index 100% rename from rules/cloud/azure/azure_ad_authentications_from_countries_you_do_not_operate_out_of.yml rename to rules/cloud/azure/signin_logs/azure_ad_authentications_from_countries_you_do_not_operate_out_of.yml diff --git a/rules/cloud/azure/azure_ad_azurehound_discovery.yml b/rules/cloud/azure/signin_logs/azure_ad_azurehound_discovery.yml similarity index 100% rename from rules/cloud/azure/azure_ad_azurehound_discovery.yml rename to rules/cloud/azure/signin_logs/azure_ad_azurehound_discovery.yml diff --git a/rules/cloud/azure/azure_ad_device_registration_or_join_without_mfa.yml b/rules/cloud/azure/signin_logs/azure_ad_device_registration_or_join_without_mfa.yml similarity index 100% rename from rules/cloud/azure/azure_ad_device_registration_or_join_without_mfa.yml rename to rules/cloud/azure/signin_logs/azure_ad_device_registration_or_join_without_mfa.yml diff --git a/rules/cloud/azure/azure_ad_failed_auth_from_countries_you_do_not_operate_out_of.yml b/rules/cloud/azure/signin_logs/azure_ad_failed_auth_from_countries_you_do_not_operate_out_of.yml similarity index 100% rename from rules/cloud/azure/azure_ad_failed_auth_from_countries_you_do_not_operate_out_of.yml rename to rules/cloud/azure/signin_logs/azure_ad_failed_auth_from_countries_you_do_not_operate_out_of.yml diff --git a/rules/cloud/azure/azure_ad_only_single_factor_auth_required.yml b/rules/cloud/azure/signin_logs/azure_ad_only_single_factor_auth_required.yml similarity index 100% rename from rules/cloud/azure/azure_ad_only_single_factor_auth_required.yml rename to rules/cloud/azure/signin_logs/azure_ad_only_single_factor_auth_required.yml diff --git a/rules/cloud/azure/azure_ad_risky_sign_ins_with_singlefactorauth_from_unknown_devices.yml b/rules/cloud/azure/signin_logs/azure_ad_risky_sign_ins_with_singlefactorauth_from_unknown_devices.yml similarity index 100% rename from rules/cloud/azure/azure_ad_risky_sign_ins_with_singlefactorauth_from_unknown_devices.yml rename to rules/cloud/azure/signin_logs/azure_ad_risky_sign_ins_with_singlefactorauth_from_unknown_devices.yml diff --git a/rules/cloud/azure/azure_ad_sign_ins_from_noncompliant_devices.yml b/rules/cloud/azure/signin_logs/azure_ad_sign_ins_from_noncompliant_devices.yml similarity index 100% rename from rules/cloud/azure/azure_ad_sign_ins_from_noncompliant_devices.yml rename to rules/cloud/azure/signin_logs/azure_ad_sign_ins_from_noncompliant_devices.yml diff --git a/rules/cloud/azure/azure_ad_sign_ins_from_unknown_devices.yml b/rules/cloud/azure/signin_logs/azure_ad_sign_ins_from_unknown_devices.yml similarity index 100% rename from rules/cloud/azure/azure_ad_sign_ins_from_unknown_devices.yml rename to rules/cloud/azure/signin_logs/azure_ad_sign_ins_from_unknown_devices.yml diff --git a/rules/cloud/azure/azure_ad_suspicious_signin_bypassing_mfa.yml b/rules/cloud/azure/signin_logs/azure_ad_suspicious_signin_bypassing_mfa.yml similarity index 96% rename from rules/cloud/azure/azure_ad_suspicious_signin_bypassing_mfa.yml rename to rules/cloud/azure/signin_logs/azure_ad_suspicious_signin_bypassing_mfa.yml index d8974c6fb..7a663533d 100644 --- a/rules/cloud/azure/azure_ad_suspicious_signin_bypassing_mfa.yml +++ b/rules/cloud/azure/signin_logs/azure_ad_suspicious_signin_bypassing_mfa.yml @@ -18,7 +18,7 @@ logsource: detection: selection: Status: 'Success' - userAgent|contains: + userAgent|contains: - 'BAV2ROPC' - 'CBAinPROD' - 'CBAinTAR' diff --git a/rules/cloud/azure/azure_app_device_code_authentication.yml b/rules/cloud/azure/signin_logs/azure_app_device_code_authentication.yml similarity index 100% rename from rules/cloud/azure/azure_app_device_code_authentication.yml rename to rules/cloud/azure/signin_logs/azure_app_device_code_authentication.yml diff --git a/rules/cloud/azure/azure_app_ropc_authentication.yml b/rules/cloud/azure/signin_logs/azure_app_ropc_authentication.yml similarity index 100% rename from rules/cloud/azure/azure_app_ropc_authentication.yml rename to rules/cloud/azure/signin_logs/azure_app_ropc_authentication.yml diff --git a/rules/cloud/azure/azure_blocked_account_attempt.yml b/rules/cloud/azure/signin_logs/azure_blocked_account_attempt.yml similarity index 100% rename from rules/cloud/azure/azure_blocked_account_attempt.yml rename to rules/cloud/azure/signin_logs/azure_blocked_account_attempt.yml diff --git a/rules/cloud/azure/azure_conditional_access_failure.yml b/rules/cloud/azure/signin_logs/azure_conditional_access_failure.yml similarity index 100% rename from rules/cloud/azure/azure_conditional_access_failure.yml rename to rules/cloud/azure/signin_logs/azure_conditional_access_failure.yml diff --git a/rules/cloud/azure/azure_legacy_authentication_protocols.yml b/rules/cloud/azure/signin_logs/azure_legacy_authentication_protocols.yml similarity index 100% rename from rules/cloud/azure/azure_legacy_authentication_protocols.yml rename to rules/cloud/azure/signin_logs/azure_legacy_authentication_protocols.yml diff --git a/rules/cloud/azure/azure_login_to_disabled_account.yml b/rules/cloud/azure/signin_logs/azure_login_to_disabled_account.yml similarity index 100% rename from rules/cloud/azure/azure_login_to_disabled_account.yml rename to rules/cloud/azure/signin_logs/azure_login_to_disabled_account.yml diff --git a/rules/cloud/azure/azure_mfa_denies.yml b/rules/cloud/azure/signin_logs/azure_mfa_denies.yml similarity index 100% rename from rules/cloud/azure/azure_mfa_denies.yml rename to rules/cloud/azure/signin_logs/azure_mfa_denies.yml diff --git a/rules/cloud/azure/azure_mfa_interrupted.yml b/rules/cloud/azure/signin_logs/azure_mfa_interrupted.yml similarity index 100% rename from rules/cloud/azure/azure_mfa_interrupted.yml rename to rules/cloud/azure/signin_logs/azure_mfa_interrupted.yml diff --git a/rules/cloud/azure/azure_unusual_authentication_interruption.yml b/rules/cloud/azure/signin_logs/azure_unusual_authentication_interruption.yml similarity index 100% rename from rules/cloud/azure/azure_unusual_authentication_interruption.yml rename to rules/cloud/azure/signin_logs/azure_unusual_authentication_interruption.yml diff --git a/rules/cloud/azure/azure_user_login_blocked_by_conditional_access.yml b/rules/cloud/azure/signin_logs/azure_user_login_blocked_by_conditional_access.yml similarity index 100% rename from rules/cloud/azure/azure_user_login_blocked_by_conditional_access.yml rename to rules/cloud/azure/signin_logs/azure_user_login_blocked_by_conditional_access.yml diff --git a/rules/cloud/azure/azure_users_authenticating_to_other_azure_ad_tenants.yml b/rules/cloud/azure/signin_logs/azure_users_authenticating_to_other_azure_ad_tenants.yml similarity index 100% rename from rules/cloud/azure/azure_users_authenticating_to_other_azure_ad_tenants.yml rename to rules/cloud/azure/signin_logs/azure_users_authenticating_to_other_azure_ad_tenants.yml diff --git a/rules/cloud/gcp/gcp_bucket_enumeration.yml b/rules/cloud/gcp/audit/gcp_bucket_enumeration.yml similarity index 100% rename from rules/cloud/gcp/gcp_bucket_enumeration.yml rename to rules/cloud/gcp/audit/gcp_bucket_enumeration.yml diff --git a/rules/cloud/gcp/gcp_bucket_modified_or_deleted.yml b/rules/cloud/gcp/audit/gcp_bucket_modified_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_bucket_modified_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_bucket_modified_or_deleted.yml diff --git a/rules/cloud/gcp/gcp_dlp_re_identifies_sensitive_information.yml b/rules/cloud/gcp/audit/gcp_dlp_re_identifies_sensitive_information.yml similarity index 100% rename from rules/cloud/gcp/gcp_dlp_re_identifies_sensitive_information.yml rename to rules/cloud/gcp/audit/gcp_dlp_re_identifies_sensitive_information.yml diff --git a/rules/cloud/gcp/gcp_dns_zone_modified_or_deleted.yml b/rules/cloud/gcp/audit/gcp_dns_zone_modified_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_dns_zone_modified_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_dns_zone_modified_or_deleted.yml diff --git a/rules/cloud/gcp/gcp_firewall_rule_modified_or_deleted.yml b/rules/cloud/gcp/audit/gcp_firewall_rule_modified_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_firewall_rule_modified_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_firewall_rule_modified_or_deleted.yml diff --git a/rules/cloud/gcp/gcp_full_network_traffic_packet_capture.yml b/rules/cloud/gcp/audit/gcp_full_network_traffic_packet_capture.yml similarity index 100% rename from rules/cloud/gcp/gcp_full_network_traffic_packet_capture.yml rename to rules/cloud/gcp/audit/gcp_full_network_traffic_packet_capture.yml diff --git a/rules/cloud/gcp/gcp_kubernetes_admission_controller.yml b/rules/cloud/gcp/audit/gcp_kubernetes_admission_controller.yml similarity index 100% rename from rules/cloud/gcp/gcp_kubernetes_admission_controller.yml rename to rules/cloud/gcp/audit/gcp_kubernetes_admission_controller.yml diff --git a/rules/cloud/gcp/gcp_kubernetes_cronjob.yml b/rules/cloud/gcp/audit/gcp_kubernetes_cronjob.yml similarity index 100% rename from rules/cloud/gcp/gcp_kubernetes_cronjob.yml rename to rules/cloud/gcp/audit/gcp_kubernetes_cronjob.yml diff --git a/rules/cloud/gcp/gcp_kubernetes_rolebinding.yml b/rules/cloud/gcp/audit/gcp_kubernetes_rolebinding.yml similarity index 100% rename from rules/cloud/gcp/gcp_kubernetes_rolebinding.yml rename to rules/cloud/gcp/audit/gcp_kubernetes_rolebinding.yml diff --git a/rules/cloud/gcp/gcp_kubernetes_secrets_modified_or_deleted.yml b/rules/cloud/gcp/audit/gcp_kubernetes_secrets_modified_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_kubernetes_secrets_modified_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_kubernetes_secrets_modified_or_deleted.yml diff --git a/rules/cloud/gcp/gcp_service_account_disabled_or_deleted.yml b/rules/cloud/gcp/audit/gcp_service_account_disabled_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_service_account_disabled_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_service_account_disabled_or_deleted.yml diff --git a/rules/cloud/gcp/gcp_service_account_modified.yml b/rules/cloud/gcp/audit/gcp_service_account_modified.yml similarity index 100% rename from rules/cloud/gcp/gcp_service_account_modified.yml rename to rules/cloud/gcp/audit/gcp_service_account_modified.yml diff --git a/rules/cloud/gcp/gcp_sql_database_modified_or_deleted.yml b/rules/cloud/gcp/audit/gcp_sql_database_modified_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_sql_database_modified_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_sql_database_modified_or_deleted.yml diff --git a/rules/cloud/gcp/gcp_vpn_tunnel_modified_or_deleted.yml b/rules/cloud/gcp/audit/gcp_vpn_tunnel_modified_or_deleted.yml similarity index 100% rename from rules/cloud/gcp/gcp_vpn_tunnel_modified_or_deleted.yml rename to rules/cloud/gcp/audit/gcp_vpn_tunnel_modified_or_deleted.yml diff --git a/rules/cloud/gworkspace/gworkspace_application_removed.yml b/rules/cloud/gcp/gworkspace/gcp_gworkspace_application_removed.yml similarity index 94% rename from rules/cloud/gworkspace/gworkspace_application_removed.yml rename to rules/cloud/gcp/gworkspace/gcp_gworkspace_application_removed.yml index 9f0a63994..bd00afe3d 100644 --- a/rules/cloud/gworkspace/gworkspace_application_removed.yml +++ b/rules/cloud/gcp/gworkspace/gcp_gworkspace_application_removed.yml @@ -8,11 +8,11 @@ references: - https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-domain-settings?hl=en#REMOVE_APPLICATION_FROM_WHITELIST author: Austin Songer date: 2021/08/26 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.impact logsource: - product: google_workspace + product: gcp service: google_workspace.admin detection: selection: diff --git a/rules/cloud/gworkspace/gworkspace_granted_domain_api_access.yml b/rules/cloud/gcp/gworkspace/gcp_gworkspace_granted_domain_api_access.yml similarity index 93% rename from rules/cloud/gworkspace/gworkspace_granted_domain_api_access.yml rename to rules/cloud/gcp/gworkspace/gcp_gworkspace_granted_domain_api_access.yml index ea14ab20b..332ea09bf 100644 --- a/rules/cloud/gworkspace/gworkspace_granted_domain_api_access.yml +++ b/rules/cloud/gcp/gworkspace/gcp_gworkspace_granted_domain_api_access.yml @@ -7,12 +7,12 @@ references: - https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-domain-settings#AUTHORIZE_API_CLIENT_ACCESS author: Austin Songer date: 2021/08/23 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.persistence - attack.t1098 logsource: - product: google_workspace + product: gcp service: google_workspace.admin detection: selection: diff --git a/rules/cloud/gworkspace/gworkspace_mfa_disabled.yml b/rules/cloud/gcp/gworkspace/gcp_gworkspace_mfa_disabled.yml similarity index 95% rename from rules/cloud/gworkspace/gworkspace_mfa_disabled.yml rename to rules/cloud/gcp/gworkspace/gcp_gworkspace_mfa_disabled.yml index f5e988115..14b5c94b1 100644 --- a/rules/cloud/gworkspace/gworkspace_mfa_disabled.yml +++ b/rules/cloud/gcp/gworkspace/gcp_gworkspace_mfa_disabled.yml @@ -8,11 +8,11 @@ references: - https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-security-settings?hl=en#ALLOW_STRONG_AUTHENTICATION author: Austin Songer date: 2021/08/26 -modified: 2022/12/25 +modified: 2023/10/11 tags: - attack.impact logsource: - product: google_workspace + product: gcp service: google_workspace.admin detection: selection_base: diff --git a/rules/cloud/gworkspace/gworkspace_role_modified_or_deleted.yml b/rules/cloud/gcp/gworkspace/gcp_gworkspace_role_modified_or_deleted.yml similarity index 93% rename from rules/cloud/gworkspace/gworkspace_role_modified_or_deleted.yml rename to rules/cloud/gcp/gworkspace/gcp_gworkspace_role_modified_or_deleted.yml index 73f7a484a..dd6fee807 100644 --- a/rules/cloud/gworkspace/gworkspace_role_modified_or_deleted.yml +++ b/rules/cloud/gcp/gworkspace/gcp_gworkspace_role_modified_or_deleted.yml @@ -7,11 +7,11 @@ references: - https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-delegated-admin-settings author: Austin Songer date: 2021/08/24 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.impact logsource: - product: google_workspace + product: gcp service: google_workspace.admin detection: selection: diff --git a/rules/cloud/gworkspace/gworkspace_role_privilege_deleted.yml b/rules/cloud/gcp/gworkspace/gcp_gworkspace_role_privilege_deleted.yml similarity index 92% rename from rules/cloud/gworkspace/gworkspace_role_privilege_deleted.yml rename to rules/cloud/gcp/gworkspace/gcp_gworkspace_role_privilege_deleted.yml index 3ea2480b6..6732d34b6 100644 --- a/rules/cloud/gworkspace/gworkspace_role_privilege_deleted.yml +++ b/rules/cloud/gcp/gworkspace/gcp_gworkspace_role_privilege_deleted.yml @@ -7,11 +7,11 @@ references: - https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-delegated-admin-settings author: Austin Songer date: 2021/08/24 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.impact logsource: - product: google_workspace + product: gcp service: google_workspace.admin detection: selection: diff --git a/rules/cloud/gworkspace/gworkspace_user_granted_admin_privileges.yml b/rules/cloud/gcp/gworkspace/gcp_gworkspace_user_granted_admin_privileges.yml similarity index 94% rename from rules/cloud/gworkspace/gworkspace_user_granted_admin_privileges.yml rename to rules/cloud/gcp/gworkspace/gcp_gworkspace_user_granted_admin_privileges.yml index 08e4b4b68..321fa59ff 100644 --- a/rules/cloud/gworkspace/gworkspace_user_granted_admin_privileges.yml +++ b/rules/cloud/gcp/gworkspace/gcp_gworkspace_user_granted_admin_privileges.yml @@ -7,12 +7,12 @@ references: - https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-user-settings#GRANT_ADMIN_PRIVILEGE author: Austin Songer date: 2021/08/23 -modified: 2022/10/09 +modified: 2023/10/11 tags: - attack.persistence - attack.t1098 logsource: - product: google_workspace + product: gcp service: google_workspace.admin detection: selection: diff --git a/rules/cloud/m365/microsoft365_disabling_mfa.yml b/rules/cloud/m365/audit/microsoft365_disabling_mfa.yml similarity index 100% rename from rules/cloud/m365/microsoft365_disabling_mfa.yml rename to rules/cloud/m365/audit/microsoft365_disabling_mfa.yml diff --git a/rules/cloud/m365/microsoft365_new_federated_domain_added_audit.yml b/rules/cloud/m365/audit/microsoft365_new_federated_domain_added_audit.yml similarity index 100% rename from rules/cloud/m365/microsoft365_new_federated_domain_added_audit.yml rename to rules/cloud/m365/audit/microsoft365_new_federated_domain_added_audit.yml diff --git a/rules/cloud/m365/microsoft365_new_federated_domain_added_exchange.yml b/rules/cloud/m365/exchange/microsoft365_new_federated_domain_added_exchange.yml similarity index 100% rename from rules/cloud/m365/microsoft365_new_federated_domain_added_exchange.yml rename to rules/cloud/m365/exchange/microsoft365_new_federated_domain_added_exchange.yml diff --git a/rules/cloud/m365/microsoft365_from_susp_ip_addresses.yml b/rules/cloud/m365/threat_detection/microsoft365_from_susp_ip_addresses.yml similarity index 100% rename from rules/cloud/m365/microsoft365_from_susp_ip_addresses.yml rename to rules/cloud/m365/threat_detection/microsoft365_from_susp_ip_addresses.yml diff --git a/rules/cloud/m365/microsoft365_activity_by_terminated_user.yml b/rules/cloud/m365/threat_management/microsoft365_activity_by_terminated_user.yml similarity index 100% rename from rules/cloud/m365/microsoft365_activity_by_terminated_user.yml rename to rules/cloud/m365/threat_management/microsoft365_activity_by_terminated_user.yml diff --git a/rules/cloud/m365/microsoft365_activity_from_anonymous_ip_addresses.yml b/rules/cloud/m365/threat_management/microsoft365_activity_from_anonymous_ip_addresses.yml similarity index 100% rename from rules/cloud/m365/microsoft365_activity_from_anonymous_ip_addresses.yml rename to rules/cloud/m365/threat_management/microsoft365_activity_from_anonymous_ip_addresses.yml diff --git a/rules/cloud/m365/microsoft365_activity_from_infrequent_country.yml b/rules/cloud/m365/threat_management/microsoft365_activity_from_infrequent_country.yml similarity index 100% rename from rules/cloud/m365/microsoft365_activity_from_infrequent_country.yml rename to rules/cloud/m365/threat_management/microsoft365_activity_from_infrequent_country.yml diff --git a/rules/cloud/m365/microsoft365_data_exfiltration_to_unsanctioned_app.yml b/rules/cloud/m365/threat_management/microsoft365_data_exfiltration_to_unsanctioned_app.yml similarity index 100% rename from rules/cloud/m365/microsoft365_data_exfiltration_to_unsanctioned_app.yml rename to rules/cloud/m365/threat_management/microsoft365_data_exfiltration_to_unsanctioned_app.yml diff --git a/rules/cloud/m365/microsoft365_impossible_travel_activity.yml b/rules/cloud/m365/threat_management/microsoft365_impossible_travel_activity.yml similarity index 100% rename from rules/cloud/m365/microsoft365_impossible_travel_activity.yml rename to rules/cloud/m365/threat_management/microsoft365_impossible_travel_activity.yml diff --git a/rules/cloud/m365/microsoft365_logon_from_risky_ip_address.yml b/rules/cloud/m365/threat_management/microsoft365_logon_from_risky_ip_address.yml similarity index 100% rename from rules/cloud/m365/microsoft365_logon_from_risky_ip_address.yml rename to rules/cloud/m365/threat_management/microsoft365_logon_from_risky_ip_address.yml diff --git a/rules/cloud/m365/microsoft365_potential_ransomware_activity.yml b/rules/cloud/m365/threat_management/microsoft365_potential_ransomware_activity.yml similarity index 100% rename from rules/cloud/m365/microsoft365_potential_ransomware_activity.yml rename to rules/cloud/m365/threat_management/microsoft365_potential_ransomware_activity.yml diff --git a/rules/cloud/m365/microsoft365_pst_export_alert.yml b/rules/cloud/m365/threat_management/microsoft365_pst_export_alert.yml similarity index 100% rename from rules/cloud/m365/microsoft365_pst_export_alert.yml rename to rules/cloud/m365/threat_management/microsoft365_pst_export_alert.yml diff --git a/rules/cloud/m365/microsoft365_pst_export_alert_using_new_compliancesearchaction.yml b/rules/cloud/m365/threat_management/microsoft365_pst_export_alert_using_new_compliancesearchaction.yml similarity index 100% rename from rules/cloud/m365/microsoft365_pst_export_alert_using_new_compliancesearchaction.yml rename to rules/cloud/m365/threat_management/microsoft365_pst_export_alert_using_new_compliancesearchaction.yml diff --git a/rules/cloud/m365/microsoft365_susp_inbox_forwarding.yml b/rules/cloud/m365/threat_management/microsoft365_susp_inbox_forwarding.yml similarity index 100% rename from rules/cloud/m365/microsoft365_susp_inbox_forwarding.yml rename to rules/cloud/m365/threat_management/microsoft365_susp_inbox_forwarding.yml diff --git a/rules/cloud/m365/microsoft365_susp_oauth_app_file_download_activities.yml b/rules/cloud/m365/threat_management/microsoft365_susp_oauth_app_file_download_activities.yml similarity index 100% rename from rules/cloud/m365/microsoft365_susp_oauth_app_file_download_activities.yml rename to rules/cloud/m365/threat_management/microsoft365_susp_oauth_app_file_download_activities.yml diff --git a/rules/cloud/m365/microsoft365_unusual_volume_of_file_deletion.yml b/rules/cloud/m365/threat_management/microsoft365_unusual_volume_of_file_deletion.yml similarity index 100% rename from rules/cloud/m365/microsoft365_unusual_volume_of_file_deletion.yml rename to rules/cloud/m365/threat_management/microsoft365_unusual_volume_of_file_deletion.yml diff --git a/rules/cloud/m365/microsoft365_user_restricted_from_sending_email.yml b/rules/cloud/m365/threat_management/microsoft365_user_restricted_from_sending_email.yml similarity index 100% rename from rules/cloud/m365/microsoft365_user_restricted_from_sending_email.yml rename to rules/cloud/m365/threat_management/microsoft365_user_restricted_from_sending_email.yml diff --git a/tests/logsource.json b/tests/logsource.json index dafe2fd96..5b3006d40 100644 --- a/tests/logsource.json +++ b/tests/logsource.json @@ -279,8 +279,6 @@ "service":{ "activitylogs":[], "auditlogs":[], - "azureactivity":[], - "microsoft365portal":[], "riskdetection":[], "pim":[], "signinlogs":[] @@ -291,7 +289,8 @@ "empty": [], "category":{}, "service":{ - "gcp.audit":[] + "gcp.audit":[], + "google_workspace.admin":[] } }, "github":{ @@ -302,14 +301,6 @@ "audit":[] } }, - "google_workspace":{ - "commun": [], - "empty": [], - "category":{}, - "service":{ - "google_workspace.admin":[] - } - }, "m365":{ "commun": [], "empty": [], diff --git a/tests/sigma-package-release.py b/tests/sigma-package-release.py index bab05ccdf..7aebb462c 100644 --- a/tests/sigma-package-release.py +++ b/tests/sigma-package-release.py @@ -25,32 +25,63 @@ RULES_DICT = { "et": "rules-emerging-threats", "threat-hunting": "rules-threat-hunting", "th": "rules-threat-hunting", - "rules-threat-hunting": "rules-threat-hunting" - } + "rules-threat-hunting": "rules-threat-hunting", +} RULES = [x for x in RULES_DICT.keys()] + def init_arguments(arguments: list) -> list: - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('-o', '--outfile', help="Outputs the Sigma release package as ZIP archive", default="Sigma-standard.zip", required=True) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "-o", + "--outfile", + help="Outputs the Sigma release package as ZIP archive", + default="Sigma-standard.zip", + required=True, + ) arg_status = parser.add_mutually_exclusive_group(required=True) - arg_status.add_argument('-s', '--statuses', nargs='*', choices=STATUS, help="Select status of rules") - arg_status.add_argument('-ms', '--min-status', nargs='?', choices=STATUS, help="Sets the minimum status of rules to select") + arg_status.add_argument( + "-s", "--statuses", nargs="*", choices=STATUS, help="Select status of rules" + ) + arg_status.add_argument( + "-ms", + "--min-status", + nargs="?", + choices=STATUS, + help="Sets the minimum status of rules to select", + ) arg_level = parser.add_mutually_exclusive_group(required=True) - arg_level.add_argument('-l', '--levels', nargs='*', choices=LEVEL, help="Select level of rules") - arg_level.add_argument('-ml', '--min-level', nargs='?', choices=LEVEL, help="Sets the minimum level of rules to select") - parser.add_argument('-r', '--rule-types', choices=RULES, nargs='*', help="Select type of rules") + arg_level.add_argument( + "-l", "--levels", nargs="*", choices=LEVEL, help="Select level of rules" + ) + arg_level.add_argument( + "-ml", + "--min-level", + nargs="?", + choices=LEVEL, + help="Sets the minimum level of rules to select", + ) + parser.add_argument( + "-r", "--rule-types", choices=RULES, nargs="*", help="Select type of rules" + ) args = parser.parse_args(arguments) if not args.outfile.endswith(".zip"): args.outfile = args.outfile + ".zip" if os.path.exists(args.outfile): - print("[E] '{}' already exists. Choose a different output file name.".format(args.outfile)) + print( + "[E] '{}' already exists. Choose a different output file name.".format( + args.outfile + ) + ) sys.exit(1) if args.rule_types == None: args.rule_types = ["generic"] - print("[I] -r/--rule-types not defined: Using \"generic\" by default") + print('[I] -r/--rule-types not defined: Using "generic" by default') if args.min_level != None: i = LEVEL.index(args.min_level) @@ -62,19 +93,20 @@ def init_arguments(arguments: list) -> list: return args + def select_rules(args: dict) -> list: selected_rules = [] def yield_next_rule_file_path(rule_path: str) -> str: for root, _, files in os.walk(rule_path): for file in files: - if file.endswith('.yml'): + if file.endswith(".yml"): yield os.path.join(root, file) def get_rule_yaml(file_path: str) -> dict: data = [] - with open(file_path, encoding='utf-8') as f: + with open(file_path, encoding="utf-8") as f: yaml_parts = yaml.safe_load_all(f) for part in yaml_parts: data.append(part) @@ -85,22 +117,29 @@ def select_rules(args: dict) -> list: for file in yield_next_rule_file_path(rule_path=rules_path): rule_yaml = get_rule_yaml(file_path=file) if len(rule_yaml) != 1: - print("[E] rule {} is a multi-document file and will be skipped".format(file)) + print( + "[E] rule {} is a multi-document file and will be skipped".format( + file + ) + ) continue rule = rule_yaml[0] - if (rule["level"] in args.levels and - rule["status"] in args.statuses): + if rule["level"] in args.levels and rule["status"] in args.statuses: selected_rules.append(file) return selected_rules + def write_zip(outfile: str, selected_rules: list): - with zipfile.ZipFile(outfile, mode='a', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zip: + with zipfile.ZipFile( + outfile, mode="a", compression=zipfile.ZIP_DEFLATED, compresslevel=9 + ) as zip: for rule_path in selected_rules: zip.write(rule_path) return + def main(arguments: list) -> int: args = init_arguments(arguments) @@ -111,5 +150,6 @@ def main(arguments: list) -> int: write_zip(args.outfile, selected_rules) print("[I] Written all rules to output ZIP file '{}'".format(args.outfile)) -if __name__ == '__main__': + +if __name__ == "__main__": sys.exit(main(sys.argv[1:])) diff --git a/tests/test_logsource.py b/tests/test_logsource.py index cb60ca74f..2524c849b 100644 --- a/tests/test_logsource.py +++ b/tests/test_logsource.py @@ -15,30 +15,37 @@ import json class TestRules(unittest.TestCase): - - path_to_rules_ = ["rules", "rules-emerging-threats", "rules-placeholder", "rules-threat-hunting", "rules-compliance"] + path_to_rules_ = [ + "rules", + "rules-emerging-threats", + "rules-placeholder", + "rules-threat-hunting", + "rules-compliance", + ] path_to_rules = [] for path_ in path_to_rules_: - path_to_rules.append(os.path.join(os.path.dirname(os.path.realpath(__name__)), path_)) + path_to_rules.append( + os.path.join(os.path.dirname(os.path.realpath(__name__)), path_) + ) # Helper functions def yield_next_rule_file_path(self, path_to_rules: list) -> str: for path_ in path_to_rules: for root, _, files in os.walk(path_): for file in files: - if file.endswith('.yml'): + if file.endswith(".yml"): yield os.path.join(root, file) def get_rule_yaml(self, file_path: str) -> dict: data = [] - with open(file_path, encoding='utf-8') as f: + with open(file_path, encoding="utf-8") as f: yaml_parts = yaml.safe_load_all(f) for part in yaml_parts: data.append(part) return data - + def get_rule_part(self, file_path: str, part_name: str): yaml_dicts = self.get_rule_yaml(file_path) for yaml_part in yaml_dicts: @@ -47,16 +54,16 @@ class TestRules(unittest.TestCase): return None - def get_detection_field(self,detection: dict): + def get_detection_field(self, detection: dict): data = [] - + def get_field_name(selection: dict): name = [] for field in selection: - if field == '|all': + if field == "|all": continue elif "|" in field: - name.append(field.split('|')[0]) + name.append(field.split("|")[0]) else: name.append(field) return name @@ -65,56 +72,82 @@ class TestRules(unittest.TestCase): if isinstance(detection[search_identifier], dict): data += get_field_name(detection[search_identifier]) if isinstance(detection[search_identifier], list): - for list_value in detection[search_identifier]: + for list_value in detection[search_identifier]: if isinstance(list_value, dict): data += get_field_name(list_value) - return data - - def full_logsource(self,logsource: dict) -> dict: - data = {} - - data["product"] = logsource["product"] if "product" in logsource.keys() else None - data["category"] = logsource["category"] if "category" in logsource.keys() else None - data["service"] = logsource["service"] if "service" in logsource.keys() else None - return data - def exist_logsource(self,logsource:dict) -> bool: - #Check New product + def full_logsource(self, logsource: dict) -> dict: + data = {} + + data["product"] = ( + logsource["product"] if "product" in logsource.keys() else None + ) + data["category"] = ( + logsource["category"] if "category" in logsource.keys() else None + ) + data["service"] = ( + logsource["service"] if "service" in logsource.keys() else None + ) + + return data + + def exist_logsource(self, logsource: dict) -> bool: + # Check New product if logsource["product"]: if logsource["product"] in fieldname_dict.keys(): product = logsource["product"] else: return False else: - product="empty" - - if logsource["category"] and logsource["category"] in fieldname_dict[product]['category'].keys(): + product = "empty" + + if ( + logsource["category"] + and logsource["category"] in fieldname_dict[product]["category"].keys() + ): return True - elif logsource["service"] and logsource["service"] in fieldname_dict[product]['service'].keys(): + elif ( + logsource["service"] + and logsource["service"] in fieldname_dict[product]["service"].keys() + ): return True elif logsource["category"] == None and logsource["service"] == None: - return True # We known the product but there are no category or service - + return True # We known the product but there are no category or service + return False - def get_logsource(self,logsource:dict) -> list: + def get_logsource(self, logsource: dict) -> list: data = None - - product = logsource["product"] if logsource["product"] in fieldname_dict.keys() else "empty" - if logsource["category"] and logsource["category"] in fieldname_dict[product]['category'].keys(): - data= fieldname_dict[product]["category"][logsource["category"]] - elif logsource["service"] and logsource["service"] in fieldname_dict[product]['service'].keys(): - data= fieldname_dict[product]["service"][logsource["service"]] + product = ( + logsource["product"] + if logsource["product"] in fieldname_dict.keys() + else "empty" + ) + + if ( + logsource["category"] + and logsource["category"] in fieldname_dict[product]["category"].keys() + ): + data = fieldname_dict[product]["category"][logsource["category"]] + elif ( + logsource["service"] + and logsource["service"] in fieldname_dict[product]["service"].keys() + ): + data = fieldname_dict[product]["service"][logsource["service"]] elif logsource["category"] == None and logsource["service"] == None: data = fieldname_dict[product]["empty"] return data - def not_commun(self,logsource:dict,data:list) -> bool: - product = logsource["product"] if logsource["product"] in fieldname_dict.keys() else "empty" + def not_commun(self, logsource: dict, data: list) -> bool: + product = ( + logsource["product"] + if logsource["product"] in fieldname_dict.keys() + else "empty" + ) if fieldname_dict[product]["commun"] == data: return False @@ -127,15 +160,14 @@ class TestRules(unittest.TestCase): def test_invalid_logsource_attributes(self): faulty_rules = [] valid_logsource = [ - 'category', - 'product', - 'service', - 'definition', + "category", + "product", + "service", + "definition", ] for file in self.yield_next_rule_file_path(self.path_to_rules): - logsource = self.get_rule_part( - file_path=file, part_name="logsource") + logsource = self.get_rule_part(file_path=file, part_name="logsource") if not logsource: print(Fore.RED + "Rule {} has no 'logsource'.".format(file)) faulty_rules.append(file) @@ -144,37 +176,54 @@ class TestRules(unittest.TestCase): for key in logsource: if key not in valid_logsource: print( - Fore.RED + "Rule {} has a logsource with an invalid field ({})".format(file, key)) + Fore.RED + + "Rule {} has a logsource with an invalid field ({})".format( + file, key + ) + ) valid = False elif not isinstance(logsource[key], str): print( - Fore.RED + "Rule {} has a logsource with an invalid field type ({})".format(file, key)) + Fore.RED + + "Rule {} has a logsource with an invalid field type ({})".format( + file, key + ) + ) valid = False if not valid: faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with non-conform 'logsource' fields. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#log-source") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with non-conform 'logsource' fields. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#log-source", + ) def test_logsource_value(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - logsource = self.get_rule_part( - file_path=file, part_name="logsource") + logsource = self.get_rule_part(file_path=file, part_name="logsource") if logsource: full_logsource = self.full_logsource(logsource) if not self.exist_logsource(full_logsource): faulty_rules.append(file) print( - Fore.RED + "Rule {} has the unknown logsource product/category/service ({}/{}/{})".format(file, - full_logsource["product"], - full_logsource["category"], - full_logsource["service"] - )) + Fore.RED + + "Rule {} has the unknown logsource product/category/service ({}/{}/{})".format( + file, + full_logsource["product"], + full_logsource["category"], + full_logsource["service"], + ) + ) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with non-conform 'logsource' values.") + self.assertEqual( + faulty_rules, + [], + Fore.RED + "There are rules with non-conform 'logsource' values.", + ) def test_fieldname_case(self): files_with_fieldname_issues = [] @@ -182,49 +231,73 @@ class TestRules(unittest.TestCase): for file in self.yield_next_rule_file_path(self.path_to_rules): logsource = self.get_rule_part(file_path=file, part_name="logsource") detection = self.get_rule_part(file_path=file, part_name="detection") - - if logsource and detection : + + if logsource and detection: full_logsource = self.full_logsource(logsource) list_valid = self.get_logsource(full_logsource) fisrt_time = True - if list_valid and self.not_commun(full_logsource,list_valid): + if list_valid and self.not_commun(full_logsource, list_valid): for field in self.get_detection_field(detection): if not field in list_valid: print( - Fore.RED + "Rule {} has the invalid field <{}>".format(file, field)) + Fore.RED + + "Rule {} has the invalid field <{}>".format( + file, field + ) + ) if fisrt_time: files_with_fieldname_issues.append(file) - fisrt_time = False # can be many error in the same rule + fisrt_time = False # can be many error in the same rule - self.assertEqual(files_with_fieldname_issues, [], Fore.RED + - "There are rule files which contains unknown field or with cast error") + self.assertEqual( + files_with_fieldname_issues, + [], + Fore.RED + + "There are rule files which contains unknown field or with cast error", + ) -def load_fields_json(name:str): + +def load_fields_json(name: str): data = {} - file_path = os.path.abspath( os.path.dirname( __file__ ) ) +'/'+ name - with open(file_path, 'r') as file: + file_path = os.path.abspath(os.path.dirname(__file__)) + "/" + name + with open(file_path, "r") as file: json_dict = json.load(file) - + for product in json_dict["legit"]: data[product] = json_dict["legit"][product] for product in json_dict["addon"]: for category in json_dict["addon"][product]["category"]: - data[product]["category"][category] += json_dict["addon"][product]["category"][category] + data[product]["category"][category] += json_dict["addon"][product][ + "category" + ][category] for service in json_dict["addon"][product]["service"]: - data[product]["service"][service] += json_dict["addon"][product]["service"][service] - + data[product]["service"][service] += json_dict["addon"][product]["service"][ + service + ] # We use some extracted hash # Add commun field for product in data: for category in data[product]["category"]: if "Hashes" in data[product]["category"][category]: - data[product]["category"][category] += ["md5","sha1","sha256","Imphash"] - if "Hash" in data[product]["category"][category]: # Sysmon 15 create_stream_hash - data[product]["category"][category] += ["md5","sha1","sha256","Imphash"] + data[product]["category"][category] += [ + "md5", + "sha1", + "sha256", + "Imphash", + ] + if ( + "Hash" in data[product]["category"][category] + ): # Sysmon 15 create_stream_hash + data[product]["category"][category] += [ + "md5", + "sha1", + "sha256", + "Imphash", + ] if "commun" in data[product].keys(): data[product]["category"][category] += data[product]["commun"] for service in data[product]["service"]: @@ -233,10 +306,11 @@ def load_fields_json(name:str): return data + if __name__ == "__main__": init(autoreset=True) # load field name information - fieldname_dict = load_fields_json('logsource.json') + fieldname_dict = load_fields_json("logsource.json") # Run the tests unittest.main() diff --git a/tests/test_rules.py b/tests/test_rules.py index fdb574c22..1e3d1bd25 100755 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -18,7 +18,6 @@ import collections class TestRules(unittest.TestCase): - @classmethod def setUpClass(cls): print("Calling get_mitre_data()") @@ -27,20 +26,42 @@ class TestRules(unittest.TestCase): print("Catched data - starting tests...") MITRE_TECHNIQUE_NAMES = [ - "process_injection", "signed_binary_proxy_execution", "process_injection"] # incomplete list - MITRE_TACTICS = ["initial_access", "execution", "persistence", "privilege_escalation", "defense_evasion", "credential_access", - "discovery", "lateral_movement", "collection", "exfiltration", "command_and_control", "impact", "launch"] + "process_injection", + "signed_binary_proxy_execution", + "process_injection", + ] # incomplete list + MITRE_TACTICS = [ + "initial_access", + "execution", + "persistence", + "privilege_escalation", + "defense_evasion", + "credential_access", + "discovery", + "lateral_movement", + "collection", + "exfiltration", + "command_and_control", + "impact", + "launch", + ] # Don't use trademarks in rules - they require non-ASCII characters to be used on we don't want them in our rules TRADE_MARKS = {"MITRE ATT&CK", "ATT&CK"} - path_to_rules = ["rules", "rules-emerging-threats", "rules-placeholder", "rules-threat-hunting", "rules-compliance"] + path_to_rules = [ + "rules", + "rules-emerging-threats", + "rules-placeholder", + "rules-threat-hunting", + "rules-compliance", + ] # Helper functions def yield_next_rule_file_path(self, path_to_rules: list) -> str: for path_ in path_to_rules: for root, _, files in os.walk(path_): for file in files: - if file.endswith('.yml'): + if file.endswith(".yml"): yield os.path.join(root, file) def get_rule_part(self, file_path: str, part_name: str): @@ -54,56 +75,67 @@ class TestRules(unittest.TestCase): def get_rule_yaml(self, file_path: str) -> dict: data = [] - with open(file_path, encoding='utf-8') as f: + with open(file_path, encoding="utf-8") as f: yaml_parts = yaml.safe_load_all(f) for part in yaml_parts: data.append(part) return data - + # Tests # def test_confirm_extension_is_yml(self): - # files_with_incorrect_extensions = [] + # files_with_incorrect_extensions = [] - # for file in self.yield_next_rule_file_path(self.path_to_rules): - # file_name_and_extension = os.path.splitext(file) - # if len(file_name_and_extension) == 2: - # extension = file_name_and_extension[1] - # if extension != ".yml": - # files_with_incorrect_extensions.append(file) + # for file in self.yield_next_rule_file_path(self.path_to_rules): + # file_name_and_extension = os.path.splitext(file) + # if len(file_name_and_extension) == 2: + # extension = file_name_and_extension[1] + # if extension != ".yml": + # files_with_incorrect_extensions.append(file) - # self.assertEqual(files_with_incorrect_extensions, [], Fore.RED + - # "There are rule files with extensions other than .yml") + # self.assertEqual(files_with_incorrect_extensions, [], Fore.RED + + # "There are rule files with extensions other than .yml") def test_legal_trademark_violations(self): # See Issue # https://github.com/SigmaHQ/sigma/issues/1028 files_with_legal_issues = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - with open(file, 'r', encoding='utf-8') as fh: + with open(file, "r", encoding="utf-8") as fh: file_data = fh.read() for tm in self.TRADE_MARKS: if tm in file_data: files_with_legal_issues.append(file) - self.assertEqual(files_with_legal_issues, [], Fore.RED + - "There are rule files which contains a trademark or reference that doesn't comply with the respective trademark requirements - please remove the trademark to avoid legal issues") + self.assertEqual( + files_with_legal_issues, + [], + Fore.RED + + "There are rule files which contains a trademark or reference that doesn't comply with the respective trademark requirements - please remove the trademark to avoid legal issues", + ) def test_optional_tags(self): files_with_incorrect_tags = [] tags_pattern = re.compile( - r"cve\.\d+\.\d+|attack\.(t\d{4}\.\d{3}|[gts]\d{4})$|attack\.[a-z_]+|car\.\d{4}-\d{2}-\d{3}|detection\.\w+") + r"cve\.\d+\.\d+|attack\.(t\d{4}\.\d{3}|[gts]\d{4})$|attack\.[a-z_]+|car\.\d{4}-\d{2}-\d{3}|detection\.\w+" + ) for file in self.yield_next_rule_file_path(self.path_to_rules): tags = self.get_rule_part(file_path=file, part_name="tags") if tags: for tag in tags: if tags_pattern.match(tag) == None: print( - Fore.RED + "Rule {} has the invalid tag <{}>".format(file, tag)) + Fore.RED + + "Rule {} has the invalid tag <{}>".format(file, tag) + ) files_with_incorrect_tags.append(file) - self.assertEqual(files_with_incorrect_tags, [], Fore.RED + - "There are rules with incorrect/unknown Tags. (please inform us about new tags that are not yet supported in our tests) and check the correct tags here: https://github.com/SigmaHQ/sigma-specification/blob/main/Tags_specification.md ") + self.assertEqual( + files_with_incorrect_tags, + [], + Fore.RED + + "There are rules with incorrect/unknown Tags. (please inform us about new tags that are not yet supported in our tests) and check the correct tags here: https://github.com/SigmaHQ/sigma-specification/blob/main/Tags_specification.md ", + ) def test_confirm_correct_mitre_tags(self): files_with_incorrect_mitre_tags = [] @@ -114,11 +146,19 @@ class TestRules(unittest.TestCase): for tag in tags: if tag.startswith("attack.") and tag not in self.MITRE_ALL: print( - Fore.RED + "Rule {} has the following incorrect MITRE tag {}".format(file, tag)) + Fore.RED + + "Rule {} has the following incorrect MITRE tag {}".format( + file, tag + ) + ) files_with_incorrect_mitre_tags.append(file) - self.assertEqual(files_with_incorrect_mitre_tags, [], Fore.RED + - "There are rules with incorrect/unknown MITRE Tags. (please inform us about new tags that are not yet supported in our tests) and check the correct tags here: https://attack.mitre.org/ ") + self.assertEqual( + files_with_incorrect_mitre_tags, + [], + Fore.RED + + "There are rules with incorrect/unknown MITRE Tags. (please inform us about new tags that are not yet supported in our tests) and check the correct tags here: https://attack.mitre.org/ ", + ) def test_duplicate_tags(self): files_with_incorrect_mitre_tags = [] @@ -130,13 +170,18 @@ class TestRules(unittest.TestCase): for tag in tags: if tag in known_tags: print( - Fore.RED + "Rule {} has the duplicate tag {}".format(file, tag)) + Fore.RED + + "Rule {} has the duplicate tag {}".format(file, tag) + ) files_with_incorrect_mitre_tags.append(file) else: known_tags.append(tag) - self.assertEqual(files_with_incorrect_mitre_tags, [], Fore.RED + - "There are rules with duplicate tags") + self.assertEqual( + files_with_incorrect_mitre_tags, + [], + Fore.RED + "There are rules with duplicate tags", + ) def test_duplicate_references(self): files_with_duplicate_references = [] @@ -148,13 +193,20 @@ class TestRules(unittest.TestCase): for reference in references: if reference in known_references: print( - Fore.RED + "Rule {} has the duplicate reference {}".format(file, reference)) + Fore.RED + + "Rule {} has the duplicate reference {}".format( + file, reference + ) + ) files_with_duplicate_references.append(file) else: known_references.append(reference) - self.assertEqual(files_with_duplicate_references, [], Fore.RED + - "There are rules with duplicate references") + self.assertEqual( + files_with_duplicate_references, + [], + Fore.RED + "There are rules with duplicate references", + ) def test_look_for_duplicate_filters(self): def check_list_or_recurse_on_dict(item, depth: int, special: bool) -> None: @@ -162,12 +214,16 @@ class TestRules(unittest.TestCase): check_if_list_contain_duplicates(item, depth, special) elif type(item) == dict and depth <= MAX_DEPTH: for keys, sub_item in item.items(): - if "|base64" in keys or "|re" in keys: # Covers both "base64" and "base64offset" modifiers, and "re" modifier + if ( + "|base64" in keys or "|re" in keys + ): # Covers both "base64" and "base64offset" modifiers, and "re" modifier check_list_or_recurse_on_dict(sub_item, depth + 1, True) else: check_list_or_recurse_on_dict(sub_item, depth + 1, special) - def check_if_list_contain_duplicates(item: list, depth: int, special: bool) -> None: + def check_if_list_contain_duplicates( + item: list, depth: int, special: bool + ) -> None: try: # We use a list comprehension to convert all the element to lowercase. Since we don't care about casing in SIGMA except for the following modifiers # - "base64offset" @@ -176,11 +232,18 @@ class TestRules(unittest.TestCase): if special: item_ = item else: - item_= [i.lower() for i in item] + item_ = [i.lower() for i in item] if len(item_) != len(set(item_)): # We find the duplicates and then print them to the user - duplicates = [i for i, count in collections.Counter(item_).items() if count > 1] - print(Fore.RED + "Rule {} has duplicate filters {}".format(file, duplicates)) + duplicates = [ + i + for i, count in collections.Counter(item_).items() + if count > 1 + ] + print( + Fore.RED + + "Rule {} has duplicate filters {}".format(file, duplicates) + ) files_with_duplicate_filters.append(file) except: # unhashable types like dictionaries @@ -192,12 +255,14 @@ class TestRules(unittest.TestCase): files_with_duplicate_filters = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") check_list_or_recurse_on_dict(detection, 1, False) - self.assertEqual(files_with_duplicate_filters, [], Fore.RED + - "There are rules with duplicate filters") + self.assertEqual( + files_with_duplicate_filters, + [], + Fore.RED + "There are rules with duplicate filters", + ) def test_field_name_with_space(self): def key_iterator(fields, faulty): @@ -205,55 +270,67 @@ class TestRules(unittest.TestCase): if " " in key: faulty.append(key) print( - Fore.YELLOW + "Rule {} has a space in field name ({}).".format(file, key)) + Fore.YELLOW + + "Rule {} has a space in field name ({}).".format(file, key) + ) if type(value) == dict: key_iterator(value, faulty) faulty_fieldnames = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") key_iterator(detection, faulty_fieldnames) - self.assertEqual(faulty_fieldnames, [], Fore.RED + - "There are rules with an unsupported field name. Spaces are not allowed. (Replace space with an underscore character '_' )") + self.assertEqual( + faulty_fieldnames, + [], + Fore.RED + + "There are rules with an unsupported field name. Spaces are not allowed. (Replace space with an underscore character '_' )", + ) def test_single_named_condition_with_x_of_them(self): faulty_detections = [] for file in self.yield_next_rule_file_path(self.path_to_rules): yaml = self.get_rule_yaml(file_path=file) - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") has_them_in_condition = "them" in detection["condition"] has_only_one_named_condition = len(detection) == 2 not_multipart_yaml_file = len(yaml) == 1 - if has_them_in_condition and \ - has_only_one_named_condition and \ - not_multipart_yaml_file: + if ( + has_them_in_condition + and has_only_one_named_condition + and not_multipart_yaml_file + ): faulty_detections.append(file) - self.assertEqual(faulty_detections, [], Fore.RED + - "There are rules using '1/all of them' style conditions but only have one condition") + self.assertEqual( + faulty_detections, + [], + Fore.RED + + "There are rules using '1/all of them' style conditions but only have one condition", + ) def test_all_of_them_condition(self): faulty_detections = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") if "all of them" in detection["condition"]: faulty_detections.append(file) - self.assertEqual(faulty_detections, [], Fore.RED + - "There are rules using 'all of them'. Better use e.g. 'all of selection*' instead (and use the 'selection_' prefix as search-identifier).") + self.assertEqual( + faulty_detections, + [], + Fore.RED + + "There are rules using 'all of them'. Better use e.g. 'all of selection*' instead (and use the 'selection_' prefix as search-identifier).", + ) def test_duplicate_detections(self): def compare_detections(detection1: dict, detection2: dict) -> bool: - # If they have different log sources. They can't be the same # We first remove any definitions fields (if there are any) in the logsource to avoid typos detection1["logsource"].pop("definition", None) @@ -295,7 +372,9 @@ class TestRules(unittest.TestCase): # We add this check in case of keyword rules. Where no field is used. The parser returns a list instead of a dict # If the 2 list are different that means they aren't the same - if (type(detection2[named_condition]) == list) or (type(detection2[named_condition]) == list): + if (type(detection2[named_condition]) == list) or ( + type(detection2[named_condition]) == list + ): condition_value1 = detection1[named_condition] condition_value2 = detection2[named_condition] else: @@ -311,10 +390,8 @@ class TestRules(unittest.TestCase): files_and_their_detections = {} for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") - logsource = self.get_rule_part( - file_path=file, part_name="logsource") + detection = self.get_rule_part(file_path=file, part_name="detection") + logsource = self.get_rule_part(file_path=file, part_name="logsource") detection["logsource"] = {} detection["logsource"].update(logsource) yaml = self.get_rule_yaml(file_path=file) @@ -329,46 +406,87 @@ class TestRules(unittest.TestCase): files_and_their_detections[file] = detection - self.assertEqual(faulty_detections, [], Fore.YELLOW + - "There are rule files with exactly the same detection logic.") + self.assertEqual( + faulty_detections, + [], + Fore.YELLOW + "There are rule files with exactly the same detection logic.", + ) def test_source_eventlog(self): faulty_detections = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") detection_str = str(detection).lower() if "'source': 'eventlog'" in detection_str: faulty_detections.append(file) - self.assertEqual(faulty_detections, [], Fore.YELLOW + - "There are detections with 'Source: Eventlog'. This does not add value to the detection.") + self.assertEqual( + faulty_detections, + [], + Fore.YELLOW + + "There are detections with 'Source: Eventlog'. This does not add value to the detection.", + ) def test_event_id_instead_of_process_creation(self): faulty_detections = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - with open(file, encoding='utf-8') as f: + with open(file, encoding="utf-8") as f: for line in f: - if re.search(r'.*EventID: (?:1|4688)\s*$', line) and file not in faulty_detections: - detection = self.get_rule_part(file_path=file, part_name="detection") + if ( + re.search(r".*EventID: (?:1|4688)\s*$", line) + and file not in faulty_detections + ): + detection = self.get_rule_part( + file_path=file, part_name="detection" + ) if detection: for search_identifier in detection: if isinstance(detection[search_identifier], dict): for field in detection[search_identifier]: if "Provider_Name" in field: - if isinstance(detection[search_identifier]["Provider_Name"], list): - for value in detection[search_identifier]["Provider_Name"]: - if "Microsoft-Windows-Security-Auditing" in value or "Microsoft-Windows-Sysmon" in value: - if file not in faulty_detections: - faulty_detections.append(file) + if isinstance( + detection[search_identifier][ + "Provider_Name" + ], + list, + ): + for value in detection[ + search_identifier + ]["Provider_Name"]: + if ( + "Microsoft-Windows-Security-Auditing" + in value + or "Microsoft-Windows-Sysmon" + in value + ): + if ( + file + not in faulty_detections + ): + faulty_detections.append( + file + ) else: - if "Microsoft-Windows-Security-Auditing" in detection[search_identifier]["Provider_Name"] or "Microsoft-Windows-Sysmon" in detection[search_identifier]["Provider_Name"]: + if ( + "Microsoft-Windows-Security-Auditing" + in detection[search_identifier][ + "Provider_Name" + ] + or "Microsoft-Windows-Sysmon" + in detection[search_identifier][ + "Provider_Name" + ] + ): if file not in faulty_detections: - faulty_detections.append(file) + faulty_detections.append(file) - self.assertEqual(faulty_detections, [], Fore.YELLOW + - "There are rules still using Sysmon 1 or Event ID 4688. Please migrate to the process_creation category.") + self.assertEqual( + faulty_detections, + [], + Fore.YELLOW + + "There are rules still using Sysmon 1 or Event ID 4688. Please migrate to the process_creation category.", + ) def test_missing_id(self): faulty_rules = [] @@ -380,44 +498,56 @@ class TestRules(unittest.TestCase): faulty_rules.append(file) elif len(id) != 36: print( - Fore.YELLOW + "Rule {} has a malformed 'id' (not 36 chars).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'id' (not 36 chars).".format(file) + ) faulty_rules.append(file) elif id.lower() in dict_id.keys(): print( - Fore.YELLOW + "Rule {} has the same 'id' as {}. Ids have to be unique.".format(file, dict_id[id])) + Fore.YELLOW + + "Rule {} has the same 'id' as {}. Ids have to be unique.".format( + file, dict_id[id] + ) + ) faulty_rules.append(file) else: dict_id[id.lower()] = file - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with missing or malformed 'id' fields. Generate an id (e.g. here: https://www.uuidgenerator.net/version4) and add it to the reported rule(s).") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with missing or malformed 'id' fields. Generate an id (e.g. here: https://www.uuidgenerator.net/version4) and add it to the reported rule(s).", + ) def test_optional_related(self): faulty_rules = [] - valid_type = [ - "derived", - "obsoletes", - "merged", - "renamed", - "similar" - ] + valid_type = ["derived", "obsoletes", "merged", "renamed", "similar"] for file in self.yield_next_rule_file_path(self.path_to_rules): - related_lst = self.get_rule_part( - file_path=file, part_name="related") + related_lst = self.get_rule_part(file_path=file, part_name="related") if related_lst: # it exists but isn't a list if not isinstance(related_lst, list): print( - Fore.YELLOW + "Rule {} has a 'related' field that isn't a list.".format(file)) + Fore.YELLOW + + "Rule {} has a 'related' field that isn't a list.".format( + file + ) + ) faulty_rules.append(file) else: type_ok = True for ref in related_lst: try: - id_str = ref['id'] - type_str = ref['type'] + id_str = ref["id"] + type_str = ref["type"] except KeyError: - print(Fore.YELLOW + "Rule {} has an invalid form of 'related/type' value.".format(file)) + print( + Fore.YELLOW + + "Rule {} has an invalid form of 'related/type' value.".format( + file + ) + ) faulty_rules.append(file) continue if not type_str in valid_type: @@ -425,45 +555,60 @@ class TestRules(unittest.TestCase): # Only add one time if many bad type in the same file if type_ok == False: print( - Fore.YELLOW + "Rule {} has a 'related/type' invalid value.".format(file)) + Fore.YELLOW + + "Rule {} has a 'related/type' invalid value.".format(file) + ) faulty_rules.append(file) else: typo_list = [] # Add more typos - typo_list.append(self.get_rule_part(file_path=file, part_name="realted")) - typo_list.append(self.get_rule_part(file_path=file, part_name="relatde")) + typo_list.append( + self.get_rule_part(file_path=file, part_name="realted") + ) + typo_list.append( + self.get_rule_part(file_path=file, part_name="relatde") + ) typo_list.append(self.get_rule_part(file_path=file, part_name="relted")) typo_list.append(self.get_rule_part(file_path=file, part_name="rlated")) for i in typo_list: if i != None: print( - Fore.YELLOW + "Rule {} has a typo in it's 'related' field.".format(file)) + Fore.YELLOW + + "Rule {} has a typo in it's 'related' field.".format(file) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed optional 'related' fields. (check https://github.com/SigmaHQ/sigma-specification)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed optional 'related' fields. (check https://github.com/SigmaHQ/sigma-specification)", + ) def test_sysmon_rule_without_eventid(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - logsource = self.get_rule_part( - file_path=file, part_name="logsource") + logsource = self.get_rule_part(file_path=file, part_name="logsource") if logsource: - service = logsource.get('service', '') - if service.lower() == 'sysmon': - with open(file, encoding='utf-8') as f: + service = logsource.get("service", "") + if service.lower() == "sysmon": + with open(file, encoding="utf-8") as f: found = False for line in f: # might be on a single line or in multiple lines - if re.search(r'.*EventID:.*$', line): + if re.search(r".*EventID:.*$", line): found = True break if not found: faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules using sysmon events but with no EventID specified") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules using sysmon events but with no EventID specified", + ) def test_missing_date(self): faulty_rules = [] @@ -474,89 +619,138 @@ class TestRules(unittest.TestCase): faulty_rules.append(file) elif not isinstance(datefield, str): print( - Fore.YELLOW + "Rule {} has a malformed 'date' (should be YYYY/MM/DD).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'date' (should be YYYY/MM/DD).".format( + file + ) + ) faulty_rules.append(file) elif len(datefield) != 10: print( - Fore.YELLOW + "Rule {} has a malformed 'date' (not 10 chars, should be YYYY/MM/DD).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'date' (not 10 chars, should be YYYY/MM/DD).".format( + file + ) + ) faulty_rules.append(file) - elif datefield[4] != '/' or datefield[7] != '/': + elif datefield[4] != "/" or datefield[7] != "/": print( - Fore.YELLOW + "Rule {} has a malformed 'date' (should be YYYY/MM/DD).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'date' (should be YYYY/MM/DD).".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with missing or malformed 'date' fields. (create one, e.g. date: 2019/01/14)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with missing or malformed 'date' fields. (create one, e.g. date: 2019/01/14)", + ) def test_missing_description(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): descriptionfield = self.get_rule_part( - file_path=file, part_name="description") + file_path=file, part_name="description" + ) if not descriptionfield: print(Fore.YELLOW + "Rule {} has no field 'description'.".format(file)) faulty_rules.append(file) elif not isinstance(descriptionfield, str): print( - Fore.YELLOW + "Rule {} has a 'description' field that isn't a string.".format(file)) + Fore.YELLOW + + "Rule {} has a 'description' field that isn't a string.".format( + file + ) + ) faulty_rules.append(file) elif len(descriptionfield) < 16: print( - Fore.YELLOW + "Rule {} has a really short description. Please elaborate.".format(file)) + Fore.YELLOW + + "Rule {} has a really short description. Please elaborate.".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with missing or malformed 'description' field. (create one, e.g. description: Detects the suspicious behaviour of process XY doing YZ)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with missing or malformed 'description' field. (create one, e.g. description: Detects the suspicious behaviour of process XY doing YZ)", + ) def test_optional_date_modified(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - modifiedfield = self.get_rule_part( - file_path=file, part_name="modified") + modifiedfield = self.get_rule_part(file_path=file, part_name="modified") if modifiedfield: if not isinstance(modifiedfield, str): print( - Fore.YELLOW + "Rule {} has a malformed 'modified' (should be YYYY/MM/DD).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'modified' (should be YYYY/MM/DD).".format( + file + ) + ) faulty_rules.append(file) elif len(modifiedfield) != 10: print( - Fore.YELLOW + "Rule {} has a malformed 'modified' (not 10 chars, should be YYYY/MM/DD).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'modified' (not 10 chars, should be YYYY/MM/DD).".format( + file + ) + ) faulty_rules.append(file) - elif modifiedfield[4] != '/' or modifiedfield[7] != '/': + elif modifiedfield[4] != "/" or modifiedfield[7] != "/": print( - Fore.YELLOW + "Rule {} has a malformed 'modified' (should be YYYY/MM/DD).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'modified' (should be YYYY/MM/DD).".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'modified' fields. (create one, e.g. date: 2019/01/14)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'modified' fields. (create one, e.g. date: 2019/01/14)", + ) def test_optional_status(self): faulty_rules = [] - valid_status = [ - "stable", - "test", - "experimental", - "deprecated", - "unsupported" - ] + valid_status = ["stable", "test", "experimental", "deprecated", "unsupported"] for file in self.yield_next_rule_file_path(self.path_to_rules): status_str = self.get_rule_part(file_path=file, part_name="status") if status_str: if not status_str in valid_status: print( - Fore.YELLOW + "Rule {} has a invalid 'status' (check wiki).".format(file)) + Fore.YELLOW + + "Rule {} has a invalid 'status' (check wiki).".format(file) + ) faulty_rules.append(file) elif status_str == "unsupported": print( - Fore.YELLOW + "Rule {} has the unsupported 'status', can not be in rules directory".format(file)) + Fore.YELLOW + + "Rule {} has the unsupported 'status', can not be in rules directory".format( + file + ) + ) faulty_rules.append(file) else: print( - Fore.YELLOW + "Rule {} is missing the 'status' field".format(file)) + Fore.YELLOW + "Rule {} is missing the 'status' field".format(file) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed or missing 'status' fields. (check https://github.com/SigmaHQ/sigma-specification)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed or missing 'status' fields. (check https://github.com/SigmaHQ/sigma-specification)", + ) def test_level(self): faulty_rules = [] @@ -574,11 +768,17 @@ class TestRules(unittest.TestCase): faulty_rules.append(file) elif not level_str in valid_level: print( - Fore.YELLOW + "Rule {} has a invalid 'level' (check wiki).".format(file)) + Fore.YELLOW + + "Rule {} has a invalid 'level' (check wiki).".format(file) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with missing or malformed 'level' fields. (check https://github.com/SigmaHQ/sigma-specification)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with missing or malformed 'level' fields. (check https://github.com/SigmaHQ/sigma-specification)", + ) def test_optional_fields(self): faulty_rules = [] @@ -588,70 +788,103 @@ class TestRules(unittest.TestCase): # it exists but isn't a list if not isinstance(fields_str, list): print( - Fore.YELLOW + "Rule {} has a 'fields' field that isn't a list.".format(file)) + Fore.YELLOW + + "Rule {} has a 'fields' field that isn't a list.".format(file) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed optional 'fields' fields. (has to be a list of values even if it contains only a single value)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed optional 'fields' fields. (has to be a list of values even if it contains only a single value)", + ) def test_optional_falsepositives_listtype(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): falsepositives_str = self.get_rule_part( - file_path=file, part_name="falsepositives") + file_path=file, part_name="falsepositives" + ) if falsepositives_str: # it exists but isn't a list if not isinstance(falsepositives_str, list): print( - Fore.YELLOW + "Rule {} has a 'falsepositives' field that isn't a list.".format(file)) + Fore.YELLOW + + "Rule {} has a 'falsepositives' field that isn't a list.".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed optional 'falsepositives' fields. (has to be a list of values even if it contains only a single value)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed optional 'falsepositives' fields. (has to be a list of values even if it contains only a single value)", + ) def test_optional_falsepositives_capital(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - fps = self.get_rule_part( - file_path=file, part_name="falsepositives") + fps = self.get_rule_part(file_path=file, part_name="falsepositives") if fps: for fp in fps: # first letter should be capital try: if fp[0].upper() != fp[0]: print( - Fore.YELLOW + "Rule {} defines a falsepositive that does not start with a capital letter: '{}'.".format(file, fp)) + Fore.YELLOW + + "Rule {} defines a falsepositive that does not start with a capital letter: '{}'.".format( + file, fp + ) + ) faulty_rules.append(file) except TypeError as err: print("TypeError Exception for rule {}".format(file)) print("Error: {}".format(err)) print("Maybe you created an empty falsepositive item?") - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with false positives that don't start with a capital letter (e.g. 'unknown' should be 'Unknown')") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with false positives that don't start with a capital letter (e.g. 'unknown' should be 'Unknown')", + ) def test_optional_falsepositives_blocked_content(self): faulty_rules = [] banned_words = ["none", "pentest", "penetration test"] common_typos = ["unkown", "ligitimate", "legitim ", "legitimeate"] for file in self.yield_next_rule_file_path(self.path_to_rules): - fps = self.get_rule_part( - file_path=file, part_name="falsepositives") + fps = self.get_rule_part(file_path=file, part_name="falsepositives") if fps: for fp in fps: for typo in common_typos: if fp == "Unknow" or typo in fp.lower(): print( - Fore.YELLOW + "Rule {} defines a falsepositive with a common typo: '{}'.".format(file, typo)) + Fore.YELLOW + + "Rule {} defines a falsepositive with a common typo: '{}'.".format( + file, typo + ) + ) faulty_rules.append(file) for banned_word in banned_words: if banned_word in fp.lower(): print( - Fore.YELLOW + "Rule {} defines a falsepositive with an invalid reason: '{}'.".format(file, banned_word)) + Fore.YELLOW + + "Rule {} defines a falsepositive with an invalid reason: '{}'.".format( + file, banned_word + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with invalid false positive definitions (e.g. Pentest, None or common typos)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with invalid false positive definitions (e.g. Pentest, None or common typos)", + ) # Upgrade Detection Rule License 1.1 def test_optional_author(self): @@ -662,25 +895,40 @@ class TestRules(unittest.TestCase): # it exists but isn't a string if not isinstance(author_str, str): print( - Fore.YELLOW + "Rule {} has a 'author' field that isn't a string.".format(file)) + Fore.YELLOW + + "Rule {} has a 'author' field that isn't a string.".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'author' fields. (has to be a string even if it contains many author)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'author' fields. (has to be a string even if it contains many author)", + ) def test_optional_license(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - license_str = self.get_rule_part( - file_path=file, part_name="license") + license_str = self.get_rule_part(file_path=file, part_name="license") if license_str: if not isinstance(license_str, str): print( - Fore.YELLOW + "Rule {} has a malformed 'license' (has to be a string).".format(file)) + Fore.YELLOW + + "Rule {} has a malformed 'license' (has to be a string).".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'license' fields. (has to be a string )") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'license' fields. (has to be a string )", + ) def test_optional_tlp(self): faulty_rules = [] @@ -696,15 +944,23 @@ class TestRules(unittest.TestCase): # it exists but isn't a string if not isinstance(tlp_str, str): print( - Fore.YELLOW + "Rule {} has a 'tlp' field that isn't a string.".format(file)) + Fore.YELLOW + + "Rule {} has a 'tlp' field that isn't a string.".format(file) + ) faulty_rules.append(file) elif not tlp_str.upper() in valid_tlp: print( - Fore.YELLOW + "Rule {} has a 'tlp' field with not valid value.".format(file)) + Fore.YELLOW + + "Rule {} has a 'tlp' field with not valid value.".format(file) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed optional 'tlp' fields. (https://www.cisa.gov/tlp)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed optional 'tlp' fields. (https://www.cisa.gov/tlp)", + ) def test_optional_target(self): faulty_rules = [] @@ -714,17 +970,22 @@ class TestRules(unittest.TestCase): # it exists but isn't a list if not isinstance(target, list): print( - Fore.YELLOW + "Rule {} has a 'target' field that isn't a list.".format(file)) + Fore.YELLOW + + "Rule {} has a 'target' field that isn't a list.".format(file) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'target' fields. (has to be a list of values even if it contains only a single value)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'target' fields. (has to be a list of values even if it contains only a single value)", + ) def test_references(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - references = self.get_rule_part( - file_path=file, part_name="references") + references = self.get_rule_part(file_path=file, part_name="references") # Reference field doesn't exist # if not references: # print(Fore.YELLOW + "Rule {} has no field 'references'.".format(file)) @@ -733,67 +994,98 @@ class TestRules(unittest.TestCase): # it exists but isn't a list if not isinstance(references, list): print( - Fore.YELLOW + "Rule {} has a references field that isn't a list.".format(file)) + Fore.YELLOW + + "Rule {} has a references field that isn't a list.".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'references' fields. (has to be a list of values even if it contains only a single value)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'references' fields. (has to be a list of values even if it contains only a single value)", + ) def test_references_in_description(self): # This test checks for the presence of a links and special keywords in the "description" field while there is no "references" field. faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - references = self.get_rule_part( - file_path=file, part_name="references") + references = self.get_rule_part(file_path=file, part_name="references") # Reference field doesn't exist if not references: descriptionfield = self.get_rule_part( - file_path=file, part_name="description") + file_path=file, part_name="description" + ) if descriptionfield: - for i in ["http://", "https://", "internal research"]: # Extends the list with other common references starters + for i in [ + "http://", + "https://", + "internal research", + ]: # Extends the list with other common references starters if i in descriptionfield.lower(): - print(Fore.RED + "Rule {} has a field that contains references to external links but no references set. Add a 'references' key and add URLs as list items.".format(file)) + print( + Fore.RED + + "Rule {} has a field that contains references to external links but no references set. Add a 'references' key and add URLs as list items.".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'description' fields. (links and external references have to be in a seperate field named 'references'. see specification https://github.com/SigmaHQ/sigma-specification)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'description' fields. (links and external references have to be in a seperate field named 'references'. see specification https://github.com/SigmaHQ/sigma-specification)", + ) def test_references_plural(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - reference = self.get_rule_part( - file_path=file, part_name="reference") + reference = self.get_rule_part(file_path=file, part_name="reference") if reference: # it exists but in singular form faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with malformed 'references' fields. (has to be 'references' in plural form, not singular)") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with malformed 'references' fields. (has to be 'references' in plural form, not singular)", + ) def test_file_names(self): faulty_rules = [] name_lst = [] - filename_pattern = re.compile(r'[a-z0-9_]{10,90}\.yml') + filename_pattern = re.compile(r"[a-z0-9_]{10,90}\.yml") for file in self.yield_next_rule_file_path(self.path_to_rules): filename = os.path.basename(file) if filename in name_lst: print(Fore.YELLOW + "Rule {} is a duplicate file name.".format(file)) faulty_rules.append(file) elif filename[-4:] != ".yml": - print(Fore.YELLOW + - "Rule {} has a invalid extension (.yml).".format(file)) + print( + Fore.YELLOW + "Rule {} has a invalid extension (.yml).".format(file) + ) faulty_rules.append(file) elif len(filename) > 90: - print(Fore.YELLOW + - "Rule {} has a file name too long >90.".format(file)) + print( + Fore.YELLOW + "Rule {} has a file name too long >90.".format(file) + ) faulty_rules.append(file) elif len(filename) < 14: - print(Fore.YELLOW + - "Rule {} has a file name too short <10.".format(file)) - faulty_rules.append(file) - elif filename_pattern.match(filename) == None or not '_' in filename: print( - Fore.YELLOW + "Rule {} has a file name that doesn't match our standard.".format(file)) + Fore.YELLOW + "Rule {} has a file name too short <10.".format(file) + ) + faulty_rules.append(file) + elif filename_pattern.match(filename) == None or not "_" in filename: + print( + Fore.YELLOW + + "Rule {} has a file name that doesn't match our standard.".format( + file + ) + ) faulty_rules.append(file) else: # This test make sure that every rules has a filename that corresponds to @@ -804,7 +1096,7 @@ class TestRules(unittest.TestCase): pattern_prefix = "" os_infix = "" os_bool = False - for key,value in logsource.items(): + for key, value in logsource.items(): if key == "definition": pass else: @@ -823,8 +1115,6 @@ class TestRules(unittest.TestCase): pattern_prefix = "azure_" elif value == "gcp": pattern_prefix = "gcp_" - elif value == "gworkspace": - pattern_prefix = "gworkspace_" elif value == "m365": pattern_prefix = "microsoft365_" elif value == "okta": @@ -929,7 +1219,10 @@ class TestRules(unittest.TestCase): pattern_prefix = "win_bitlocker_" elif value == "capi2": pattern_prefix = "win_capi2_" - elif value == "certificateservicesclient-lifecycle-system": + elif ( + value + == "certificateservicesclient-lifecycle-system" + ): pattern_prefix = "win_certificateservicesclient_lifecycle_system_" elif value == "pim": pattern_prefix = "azure_pim_" @@ -940,36 +1233,44 @@ class TestRules(unittest.TestCase): if pattern_prefix != "": if not filename.startswith(pattern_prefix): print( - Fore.YELLOW + "Rule {} has a file name that doesn't match our standard naming convention.".format(file)) + Fore.YELLOW + + "Rule {} has a file name that doesn't match our standard naming convention.".format( + file + ) + ) faulty_rules.append(file) name_lst.append(filename) - self.assertEqual(faulty_rules, [], Fore.RED + - r'There are rules with malformed file names (too short, too long, uppercase letters, a minus sign etc.). Please see the file names used in our repository and adjust your file names accordingly. The pattern for a valid file name is \'[a-z0-9_]{10,90}\.yml\' and it has to contain at least an underline character. It also has to follow the following naming convention https://github.com/SigmaHQ/sigma-specification/blob/main/sigmahq/Sigmahq_filename_rule.md') + self.assertEqual( + faulty_rules, + [], + Fore.RED + + r"There are rules with malformed file names (too short, too long, uppercase letters, a minus sign etc.). Please see the file names used in our repository and adjust your file names accordingly. The pattern for a valid file name is \'[a-z0-9_]{10,90}\.yml\' and it has to contain at least an underline character. It also has to follow the following naming convention https://github.com/SigmaHQ/sigma-specification/blob/main/sigmahq/Sigmahq_filename_rule.md", + ) def test_title(self): faulty_rules = [] allowed_lowercase_words = [ - 'the', - 'for', - 'in', - 'with', - 'via', - 'on', - 'to', - 'without', - 'of', - 'through', - 'from', - 'by', - 'as', - 'a', - 'or', - 'at', - 'and', - 'an', - 'over', - 'new', + "the", + "for", + "in", + "with", + "via", + "on", + "to", + "without", + "of", + "through", + "from", + "by", + "as", + "a", + "or", + "at", + "and", + "an", + "over", + "new", ] for file in self.yield_next_rule_file_path(self.path_to_rules): title = self.get_rule_part(file_path=file, part_name="title") @@ -978,25 +1279,47 @@ class TestRules(unittest.TestCase): faulty_rules.append(file) continue elif len(title) > 100: - print(Fore.YELLOW + "Rule {} has a title field with too many characters (>100)".format(file)) + print( + Fore.YELLOW + + "Rule {} has a title field with too many characters (>100)".format( + file + ) + ) faulty_rules.append(file) if title.startswith("Detects "): - print(Fore.RED + "Rule {} has a title that starts with 'Detects'".format(file)) + print( + Fore.RED + + "Rule {} has a title that starts with 'Detects'".format(file) + ) faulty_rules.append(file) if title.endswith("."): print(Fore.RED + "Rule {} has a title that ends with '.'".format(file)) faulty_rules.append(file) wrong_casing = [] for word in title.split(" "): - if word.islower() and not word.lower() in allowed_lowercase_words and not "." in word and not "/" in word and not word[0].isdigit(): + if ( + word.islower() + and not word.lower() in allowed_lowercase_words + and not "." in word + and not "/" in word + and not word[0].isdigit() + ): wrong_casing.append(word) if len(wrong_casing) > 0: - print(Fore.RED + "Rule {} has a title that has not title capitalization. Words: '{}'".format( - file, ", ".join(wrong_casing))) + print( + Fore.RED + + "Rule {} has a title that has not title capitalization. Words: '{}'".format( + file, ", ".join(wrong_casing) + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with non-conform 'title' fields. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#title") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with non-conform 'title' fields. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#title", + ) def test_title_in_first_line(self): faulty_rules = [] @@ -1012,30 +1335,47 @@ class TestRules(unittest.TestCase): # (also assumes dict keeps the order from the input file) if list(yaml[0].keys())[0] != "title": print( - Fore.RED + "Rule {} does not have its 'title' attribute in the first line".format(file)) + Fore.RED + + "Rule {} does not have its 'title' attribute in the first line".format( + file + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules without the 'title' attribute in their first line.") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules without the 'title' attribute in their first line.", + ) def test_duplicate_titles(self): # This test ensure that every rule has a unique title faulty_rules = [] titles_dict = {} for file in self.yield_next_rule_file_path(self.path_to_rules): - title = self.get_rule_part(file_path=file, part_name="title").lower().rstrip() + title = ( + self.get_rule_part(file_path=file, part_name="title").lower().rstrip() + ) duplicate = False for rule, title_ in titles_dict.items(): if title == title_: - print(Fore.RED + "Rule {} has an already used title in {}.".format(file, rule)) + print( + Fore.RED + + "Rule {} has an already used title in {}.".format(file, rule) + ) duplicate = True faulty_rules.append(file) continue if not duplicate: titles_dict[file] = title - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules that share the same 'title'. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#title") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules that share the same 'title'. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#title", + ) # def test_invalid_logsource_attributes(self): # faulty_rules = [] @@ -1069,12 +1409,14 @@ class TestRules(unittest.TestCase): # "There are rules with non-conform 'logsource' fields. Please check: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#log-source") def test_selection_list_one_value(self): - def treat_list(file, values, valid_, selection_name): # rule with only list of Keywords term if len(values) == 1 and not isinstance(values[0], str): print( - Fore.RED + "Rule {} has the selection ({}) with a list of only 1 element in detection".format(file, key) + Fore.RED + + "Rule {} has the selection ({}) with a list of only 1 element in detection".format( + file, key + ) ) valid_ = False elif isinstance(values[0], dict): @@ -1088,8 +1430,11 @@ class TestRules(unittest.TestCase): if isinstance(dict_[key_], list): if len(dict_[key_]) == 1: print( - Fore.RED + "Rule {} has the selection ({}/{}) with a list of only 1 value in detection".format(file, selection_name, key_) + Fore.RED + + "Rule {} has the selection ({}/{}) with a list of only 1 value in detection".format( + file, selection_name, key_ ) + ) valid_ = False else: dict_ = values @@ -1097,17 +1442,18 @@ class TestRules(unittest.TestCase): if isinstance(dict_[key_], list): if len(dict_[key_]) == 1: print( - Fore.RED + "Rule {} has the selection ({}/{}) with a list of only 1 value in detection".format(file, selection_name, key_) + Fore.RED + + "Rule {} has the selection ({}/{}) with a list of only 1 value in detection".format( + file, selection_name, key_ ) + ) valid_ = False return valid_ faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") if detection: - valid = True for key in detection: values = detection[key] @@ -1120,38 +1466,57 @@ class TestRules(unittest.TestCase): if not valid: faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules using list with only 1 element") + self.assertEqual( + faulty_rules, + [], + Fore.RED + "There are rules using list with only 1 element", + ) def test_selection_start_or_and(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") if detection: - # This test is a best effort to avoid breaking SIGMAC parser. You could do more testing and try to fix this once and for all by modifiying the token regular expressions https://github.com/SigmaHQ/sigma/blob/b9ae5303f12cda8eb6b5b90a32fd7f11ad65645d/tools/sigma/parser/condition.py#L107-L127 for key in detection: if key[:3].lower() == "sel": - continue + continue elif key[:2].lower() == "or": - print( Fore.RED + "Rule {} has a selection '{}' that starts with the string 'or'".format(file, key)) + print( + Fore.RED + + "Rule {} has a selection '{}' that starts with the string 'or'".format( + file, key + ) + ) faulty_rules.append(file) elif key[:3].lower() == "and": - print( Fore.RED + "Rule {} has a selection '{}' that starts with the string 'and'".format(file, key)) + print( + Fore.RED + + "Rule {} has a selection '{}' that starts with the string 'and'".format( + file, key + ) + ) faulty_rules.append(file) elif key[:3].lower() == "not": - print( Fore.RED + "Rule {} has a selection '{}' that starts with the string 'not'".format(file, key)) + print( + Fore.RED + + "Rule {} has a selection '{}' that starts with the string 'not'".format( + file, key + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with bad selection names. Can't start a selection name with an 'or*' or an 'and*' or a 'not*' ") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with bad selection names. Can't start a selection name with an 'or*' or an 'and*' or a 'not*' ", + ) def test_unused_selection(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") condition = detection["condition"] wildcard_selections = re.compile(r"\sof\s([\w\*]+)(?:$|\s|\))") @@ -1166,7 +1531,16 @@ class TestRules(unittest.TestCase): continue # remove special keywords - condition_list = condition.replace("not ", '').replace("1 of ", '').replace("all of ", '').replace(' or ', ' ').replace(' and ', ' ').replace('(', '').replace(')', '').split(" ") + condition_list = ( + condition.replace("not ", "") + .replace("1 of ", "") + .replace("all of ", "") + .replace(" or ", " ") + .replace(" and ", " ") + .replace("(", "") + .replace(")", "") + .split(" ") + ) if selection in condition_list: continue @@ -1174,17 +1548,23 @@ class TestRules(unittest.TestCase): found = False for wildcard_selection in wildcard_selections.findall(condition): # wildcard matches selection - if re.search(wildcard_selection.replace(r"*", r".*"), selection) is not None: + if ( + re.search(wildcard_selection.replace(r"*", r".*"), selection) + is not None + ): found = True break # selection was not found in condition if not found: print( - Fore.RED + "Rule {} has an unused selection '{}'".format(file, selection)) + Fore.RED + + "Rule {} has an unused selection '{}'".format(file, selection) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules with unused selections") + self.assertEqual( + faulty_rules, [], Fore.RED + "There are rules with unused selections" + ) # def test_field_name_typo(self): # # add "OriginalFilename" after Aurora switched to SourceFilename @@ -1219,7 +1599,21 @@ class TestRules(unittest.TestCase): # self.assertEqual(faulty_rules, [], Fore.RED + "There are rules with common typos in field names.") def test_unknown_value_modifier(self): - known_modifiers = ["contains", "startswith", "endswith", "all", "base64offset", "base64", "utf16le", "utf16be", "wide", "utf16", "windash", "re", "cidr"] + known_modifiers = [ + "contains", + "startswith", + "endswith", + "all", + "base64offset", + "base64", + "utf16le", + "utf16be", + "wide", + "utf16", + "windash", + "re", + "cidr", + ] faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): detection = self.get_rule_part(file_path=file, part_name="detection") @@ -1228,34 +1622,54 @@ class TestRules(unittest.TestCase): if isinstance(detection[search_identifier], dict): for field in detection[search_identifier]: if "|" in field: - for current_modifier in field.split('|')[1:]: + for current_modifier in field.split("|")[1:]: found = False for target_modifier in known_modifiers: if current_modifier == target_modifier: found = True if not found: - print(Fore.RED + "Rule {} uses an unknown field modifier ({}/{})".format(file, search_identifier, field)) + print( + Fore.RED + + "Rule {} uses an unknown field modifier ({}/{})".format( + file, search_identifier, field + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + "There are rules with unknown value modifiers. Most often it is just a typo.") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with unknown value modifiers. Most often it is just a typo.", + ) def test_all_value_modifier_single_item(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") if detection: for search_identifier in detection: if isinstance(detection[search_identifier], dict): for field in detection[search_identifier]: - if "|all" in field and not isinstance(detection[search_identifier][field], list): - print(Fore.RED + "Rule {} uses the 'all' modifier on a single item in selection ({}/{})".format( - file, search_identifier, field)) + if "|all" in field and not isinstance( + detection[search_identifier][field], list + ): + print( + Fore.RED + + "Rule {} uses the 'all' modifier on a single item in selection ({}/{})".format( + file, search_identifier, field + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + "There are rules with |all modifier only having one item. " + - "Single item values are not allowed to have an all modifier as some back-ends cannot support it. " + - "If you use it as a workaround to duplicate a field in a selection, use a new selection instead.") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules with |all modifier only having one item. " + + "Single item values are not allowed to have an all modifier as some back-ends cannot support it. " + + "If you use it as a workaround to duplicate a field in a selection, use a new selection instead.", + ) def test_field_user_localization(self): def checkUser(faulty_rules, dict): @@ -1268,8 +1682,7 @@ class TestRules(unittest.TestCase): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") for sel_key, sel_value in detection.items(): if sel_key == "condition" or sel_key == "timeframe": continue @@ -1284,58 +1697,78 @@ class TestRules(unittest.TestCase): for item in sel_value: checkUser(faulty_rules, item) - self.assertEqual(faulty_rules, [], Fore.RED + "There are rules that match using localized user accounts. Better employ a generic version such as:\n" + - "User|contains: # covers many language settings\n" + - " - 'AUTHORI'\n" + - " - 'AUTORI'") + self.assertEqual( + faulty_rules, + [], + Fore.RED + + "There are rules that match using localized user accounts. Better employ a generic version such as:\n" + + "User|contains: # covers many language settings\n" + + " - 'AUTHORI'\n" + + " - 'AUTORI'", + ) def test_condition_operator_casesensitive(self): faulty_rules = [] for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") if detection: valid = True if isinstance(detection["condition"], str): - param = detection["condition"].split(' ') + param = detection["condition"].split(" ") for item in param: - if item.lower() == 'or' and not item == 'or': + if item.lower() == "or" and not item == "or": valid = False - elif item.lower() == 'and' and not item == 'and': + elif item.lower() == "and" and not item == "and": valid = False - elif item.lower() == 'not' and not item == 'not': + elif item.lower() == "not" and not item == "not": valid = False - elif item.lower() == 'of' and not item == 'of': + elif item.lower() == "of" and not item == "of": valid = False if not valid: - print(Fore.RED + "Rule {} has a invalid condition '{}' : 'or','and','not','of' are lowercase".format( - file, detection["condition"])) + print( + Fore.RED + + "Rule {} has a invalid condition '{}' : 'or','and','not','of' are lowercase".format( + file, detection["condition"] + ) + ) faulty_rules.append(file) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules using condition without lowercase operator") + self.assertEqual( + faulty_rules, + [], + Fore.RED + "There are rules using condition without lowercase operator", + ) def test_broken_thor_logsource_config(self): - faulty_config = False # This test check of the "thor.yml" config file has a missing "WinEventLog:" prefix in Windows log sources path_to_thor_config = "../tests/thor.yml" - path_to_thor_config = os.path.join(os.path.dirname(os.path.realpath(__file__)), path_to_thor_config) - thor_logsources = self.get_rule_yaml(path_to_thor_config)[0]['logsources'] + path_to_thor_config = os.path.join( + os.path.dirname(os.path.realpath(__file__)), path_to_thor_config + ) + thor_logsources = self.get_rule_yaml(path_to_thor_config)[0]["logsources"] for key, value in thor_logsources.items(): try: if value["product"] == "windows": - sources_list = value['sources'] + sources_list = value["sources"] for i in sources_list: - if not i.startswith('WinEventLog:'): + if not i.startswith("WinEventLog:"): faulty_config = True - print(Fore.RED + "/tests/thor.yml config file has a broken source. Windows Eventlog sources must start with the keyword 'WinEventLog:'") + print( + Fore.RED + + "/tests/thor.yml config file has a broken source. Windows Eventlog sources must start with the keyword 'WinEventLog:'" + ) except: pass - self.assertEqual(faulty_config, False, Fore.RED + "thor.yml configuration file located in 'tests/thor.yml' has a borken log source definition") + self.assertEqual( + faulty_config, + False, + Fore.RED + + "thor.yml configuration file located in 'tests/thor.yml' has a borken log source definition", + ) def test_re_invalid_escapes(self): faulty_rules = [] @@ -1353,22 +1786,45 @@ class TestRules(unittest.TestCase): l = tuple(re.escape(string.punctuation)) for c in l: if c == "\\": - allowed_2_be_escaped.append(l[index+1]) + allowed_2_be_escaped.append(l[index + 1]) index += 1 re_specials = [ - "A", "b", "B", "d", "D", "f", "n", "r", "s", - "S", "t", "v", "w", "W", "Z", + "A", + "b", + "B", + "d", + "D", + "f", + "n", + "r", + "s", + "S", + "t", + "v", + "w", + "W", + "Z", # Match Groups - "0", "1", "2", "3", "4", "5", - "6", "7", "8", "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", ] allowed_2_be_escaped.extend(re_specials) - allowed_2_be_escaped.extend([ - '"', - '\'', - ]) + allowed_2_be_escaped.extend( + [ + '"', + "'", + ] + ) return allowed_2_be_escaped @@ -1382,7 +1838,9 @@ class TestRules(unittest.TestCase): # check_item_for_bad_escapes(item) elif type(item) == dict and depth <= MAX_DEPTH: for keys, sub_item in item.items(): - if "|re" in keys: # Covers both "base64" and "base64offset" modifiers + if ( + "|re" in keys + ): # Covers both "base64" and "base64offset" modifiers if type(sub_item) == str or type(sub_item) == list: check_item_for_bad_escapes(sub_item) else: @@ -1407,14 +1865,23 @@ class TestRules(unittest.TestCase): if c == "\\": # 'l[index-1] != "\\"' ---> Allows "\\\\" # Check if character after \ is not in escape_allow_list and also not already found - if l[index-1] != "\\" and l[index+1] not in escape_allow_list and l[index+1] not in found_bad_escapes: + if ( + l[index - 1] != "\\" + and l[index + 1] not in escape_allow_list + and l[index + 1] not in found_bad_escapes + ): # Only for debugging: # print(f"Illegal escape found {c}{l[index+1]}") found_bad_escapes.append(f"{l[index+1]}") index += 1 if len(found_bad_escapes) > 0: - print(Fore.RED + "Rule {} has forbidden escapes in |re '{}'".format(file, ",".join(found_bad_escapes))) + print( + Fore.RED + + "Rule {} has forbidden escapes in |re '{}'".format( + file, ",".join(found_bad_escapes) + ) + ) faulty_rules.append(file) # Create escape_allow_list for this test @@ -1422,13 +1889,14 @@ class TestRules(unittest.TestCase): # For each rule file, extract detection and dive into recursion for file in self.yield_next_rule_file_path(self.path_to_rules): - detection = self.get_rule_part( - file_path=file, part_name="detection") + detection = self.get_rule_part(file_path=file, part_name="detection") if detection: check_list_or_recurse_on_dict(detection, 1, False) - self.assertEqual(faulty_rules, [], Fore.RED + - "There are rules using illegal re-escapes") + self.assertEqual( + faulty_rules, [], Fore.RED + "There are rules using illegal re-escapes" + ) + def get_mitre_data(): """ @@ -1449,38 +1917,54 @@ def get_mitre_data(): enterprise_techniques = lift.get_enterprise_techniques() for t in enterprise_techniques: MITRE_TECHNIQUE_NAMES.append( - t['name'].lower().replace(' ', '_').replace('-', '_')) + t["name"].lower().replace(" ", "_").replace("-", "_") + ) for r in t.external_references: - if 'external_id' in r: - MITRE_TECHNIQUES.append(r['external_id'].lower()) - if 'kill_chain_phases' in t: - for kc in t['kill_chain_phases']: - if 'phase_name' in kc: - MITRE_PHASE_NAMES.add(kc['phase_name'].replace('-', '_')) + if "external_id" in r: + MITRE_TECHNIQUES.append(r["external_id"].lower()) + if "kill_chain_phases" in t: + for kc in t["kill_chain_phases"]: + if "phase_name" in kc: + MITRE_PHASE_NAMES.add(kc["phase_name"].replace("-", "_")) # Tools / Malware enterprise_tools = lift.get_enterprise_tools() for t in enterprise_tools: for r in t.external_references: - if 'external_id' in r: - MITRE_TOOLS.append(r['external_id'].lower()) + if "external_id" in r: + MITRE_TOOLS.append(r["external_id"].lower()) enterprise_malware = lift.get_enterprise_malware() for m in enterprise_malware: for r in m.external_references: - if 'external_id' in r: - MITRE_TOOLS.append(r['external_id'].lower()) + if "external_id" in r: + MITRE_TOOLS.append(r["external_id"].lower()) # Groups enterprise_groups = lift.get_enterprise_groups() for g in enterprise_groups: for r in g.external_references: - if 'external_id' in r: - MITRE_GROUPS.append(r['external_id'].lower()) + if "external_id" in r: + MITRE_GROUPS.append(r["external_id"].lower()) # Debugging - print("MITRE ATT&CK LIST LENGTHS: %d %d %d %d %d" % (len(MITRE_TECHNIQUES), len( - MITRE_TECHNIQUE_NAMES), len(list(MITRE_PHASE_NAMES)), len(MITRE_GROUPS), len(MITRE_TOOLS))) + print( + "MITRE ATT&CK LIST LENGTHS: %d %d %d %d %d" + % ( + len(MITRE_TECHNIQUES), + len(MITRE_TECHNIQUE_NAMES), + len(list(MITRE_PHASE_NAMES)), + len(MITRE_GROUPS), + len(MITRE_TOOLS), + ) + ) # Combine all IDs to a big tag list - return ["attack." + item for item in MITRE_TECHNIQUES + MITRE_TECHNIQUE_NAMES + list(MITRE_PHASE_NAMES) + MITRE_GROUPS + MITRE_TOOLS] + return [ + "attack." + item + for item in MITRE_TECHNIQUES + + MITRE_TECHNIQUE_NAMES + + list(MITRE_PHASE_NAMES) + + MITRE_GROUPS + + MITRE_TOOLS + ] if __name__ == "__main__":