From 35c748a79e3420a8ae9f9a833072dbf84ba8cb1b Mon Sep 17 00:00:00 2001 From: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:29:39 -0600 Subject: [PATCH 1/8] Helper reorg (#1380) --- STYLE_GUIDE.md | 2 +- data_models/gcp_data_model.py | 3 +- data_models/gsuite_data_model.py | 2 +- data_models/zendesk_data_model.py | 2 +- .../crowdstrike_event_streams_helpers.yml | 5 - global_helpers/default_test.py | 4 +- global_helpers/gcp_environment.py | 23 - global_helpers/gcp_environment.yml | 4 - global_helpers/global_helpers_test.py | 48 +- global_helpers/panther_aws_helpers.py | 225 ++++++++ global_helpers/panther_aws_helpers.yml | 5 + global_helpers/panther_base_helpers.py | 523 ++++++------------ global_helpers/panther_box_helpers.py | 12 + global_helpers/panther_config_defaults.py | 53 ++ ...ther_crowdstrike_event_streams_helpers.py} | 0 ...ther_crowdstrike_event_streams_helpers.yml | 5 + .../panther_crowdstrike_fdr_helpers.py | 61 ++ .../panther_crowdstrike_fdr_helpers.yml | 5 + global_helpers/panther_default.py | 127 ----- global_helpers/panther_default.yml | 4 - ...base_helpers.py => panther_gcp_helpers.py} | 19 + ...se_helpers.yml => panther_gcp_helpers.yml} | 4 +- global_helpers/panther_github_helpers.py | 9 + global_helpers/panther_github_helpers.yml | 5 + global_helpers/panther_gsuite_helpers.py | 63 +++ global_helpers/panther_gsuite_helpers.yml | 5 + global_helpers/panther_iocs.py | 21 - global_helpers/panther_ipinfo_helpers.py | 41 ++ global_helpers/panther_msft_helpers.py | 20 + global_helpers/panther_msft_helpers.yml | 5 + global_helpers/panther_okta_helpers.py | 15 + global_helpers/panther_okta_helpers.yml | 5 + global_helpers/panther_oss_helpers.py | 366 ------------ global_helpers/panther_oss_helpers.yml | 4 - global_helpers/panther_slack_helpers.py | 7 + global_helpers/panther_slack_helpers.yml | 5 + global_helpers/panther_zendesk_helpers.py | 40 ++ global_helpers/panther_zendesk_helpers.yml | 5 + packs/asana.yml | 6 +- packs/atlassian.yml | 6 +- packs/auth0.yml | 4 +- packs/aws.yml | 5 +- packs/aws_cis.yml | 2 +- packs/azure_signin.yml | 6 +- packs/box.yml | 3 - packs/cloudflare.yml | 3 - packs/credential_security.yml | 6 +- packs/crowdstrike.yml | 4 +- packs/crowdstrike_event_streams.yml | 6 +- packs/dropbox.yml | 2 +- packs/duo.yml | 6 +- packs/gcp_audit.yml | 6 +- packs/gcp_k8.yml | 5 +- packs/github.yml | 7 +- packs/gsuite_reports.yml | 4 +- packs/ipinfo.yml | 4 +- packs/mongodb.yml | 6 +- packs/msft_graph.yml | 2 +- packs/multisource_correlations.yml | 6 +- packs/notion.yml | 4 - packs/okta.yml | 5 +- packs/onelogin.yml | 4 - packs/onepassword.yml | 6 +- packs/osquery.yml | 6 +- packs/panther.yml | 6 +- packs/sentinelone.yml | 6 +- packs/slack.yml | 6 +- packs/snyk.yml | 6 +- packs/standard_ruleset.yml | 3 +- packs/tailscale.yml | 6 +- packs/tines.yml | 6 +- packs/tor.yml | 3 - packs/wiz.yml | 4 +- packs/zendesk.yml | 6 +- packs/zoom.yml | 7 +- .../aws_acm_certificate_expiration.py | 2 +- .../aws_cloudtrail_cloudwatch_logs.py | 3 +- ...aws_cloudtrail_s3_bucket_access_logging.py | 2 +- .../aws_cloudtrail_s3_bucket_public.py | 3 +- .../aws_config_global_resources.py | 2 +- .../aws_dynamodb_table_ttl_enabled.py | 3 +- .../aws_ec2_cde_volume_encrypted.py | 2 +- .../aws_access_key_rotation.py | 2 +- .../aws_iam_policies/aws_access_key_unused.py | 2 +- .../aws_access_keys_at_account_creation.py | 3 +- ...ws_iam_policy_administrative_privileges.py | 2 +- .../aws_iam_role_external_permission.py | 2 +- .../aws_iam_policies/aws_password_unused.py | 2 +- .../aws_elbv2_load_balancer_has_ssl_policy.py | 2 +- ...ds_instance_backup_retention_acceptable.py | 2 +- ...aws_rds_instance_snapshot_public_access.py | 2 +- ...t_cluster_snapshot_retention_acceptable.py | 2 +- .../aws_s3_bucket_object_lock_configured.py | 3 +- ...s_network_acl_restricts_inbound_traffic.py | 2 +- ...etwork_acl_restricts_insecure_protocols.py | 2 +- ..._network_acl_restricts_outbound_traffic.py | 2 +- ...dmz_security_groups_publicly_accessible.py | 2 +- ..._security_group_restricts_access_to_cde.py | 2 +- ...ecurity_group_restricts_inbound_traffic.py | 2 +- ..._restricts_inter_security_group_traffic.py | 2 +- ...curity_group_restricts_outbound_traffic.py | 2 +- ...ity_group_restricts_traffic_leaving_cde.py | 2 +- ...group_tightly_restricts_inbound_traffic.py | 2 +- ...roup_tightly_restricts_outbound_traffic.py | 2 +- ...fault_network_acl_restricts_all_traffic.py | 4 +- .../aws_vpc_default_security_restrictions.py | 2 +- .../aws_waf_has_xss_predicate.py | 3 +- ...ation_from_crowdstrike_unmanaged_device.py | 2 +- .../aws_ami_modified_for_public_access.py | 3 +- .../aws_cloudtrail_created.py | 3 +- ...loudtrail_loginprofilecreatedormodified.py | 2 +- ...ws_cloudtrail_password_policy_discovery.py | 2 +- .../aws_cloudtrail_stopped.py | 3 +- ...aws_cloudtrail_unsuccessful_mfa_attempt.py | 2 +- .../aws_codebuild_made_public.py | 3 +- .../aws_config_service_created.py | 3 +- .../aws_config_service_disabled_deleted.py | 3 +- .../aws_console_login_without_mfa.py | 3 +- .../aws_console_login_without_saml.py | 3 +- .../aws_console_root_login.py | 9 +- .../aws_console_root_login_failed.py | 3 +- .../aws_ec2_ebs_encryption_disabled.py | 2 +- .../aws_ec2_gateway_modified.py | 3 +- .../aws_ec2_manual_security_group_changes.py | 4 +- .../aws_ec2_network_acl_modified.py | 3 +- .../aws_ec2_route_table_modified.py | 3 +- .../aws_ec2_security_group_modified.py | 3 +- .../aws_ec2_startup_script_change.py | 2 +- .../aws_ec2_stopinstances.py | 2 +- .../aws_ec2_traffic_mirroring.py | 2 +- .../aws_ec2_vpc_modified.py | 3 +- .../aws_ec2_vulnerable_xz_image_launched.py | 3 +- rules/aws_cloudtrail_rules/aws_ecr_crud.py | 2 +- rules/aws_cloudtrail_rules/aws_ecr_events.py | 2 +- .../aws_iam_anything_changed.py | 3 +- .../aws_iam_assume_role_blocklist_ignored.py | 3 +- ...m_entity_created_without_cloudformation.py | 3 +- .../aws_iam_group_read_only_events.py | 2 +- .../aws_iam_policy_modified.py | 3 +- .../aws_iam_user_key_created.py | 3 +- .../aws_iam_user_recon_denied.py | 3 +- .../aws_ipset_modified.py | 2 +- .../aws_key_compromised.py | 2 +- .../aws_cloudtrail_rules/aws_kms_cmk_loss.py | 3 +- rules/aws_cloudtrail_rules/aws_lambda_crud.py | 2 +- .../aws_network_acl_permissive_entry.py | 3 +- .../aws_rds_manual_snapshot_created.py | 2 +- .../aws_rds_publicrestore.py | 2 +- .../aws_rds_snapshot_shared.py | 2 +- .../aws_resource_made_public.py | 4 +- .../aws_root_access_key_created.py | 2 +- .../aws_cloudtrail_rules/aws_root_activity.py | 2 +- .../aws_root_password_changed.py | 2 +- .../aws_s3_bucket_deleted.py | 3 +- .../aws_s3_bucket_policy_modified.py | 3 +- .../aws_cloudtrail_rules/aws_saml_activity.py | 2 +- .../aws_security_configuration_change.py | 3 +- .../aws_securityhub_finding_evasion.py | 2 +- .../aws_snapshot_made_public.py | 4 +- .../aws_software_discovery.py | 2 +- .../aws_unauthorized_api_call.py | 2 +- .../aws_cloudtrail_rules/aws_unused_region.py | 2 +- .../aws_update_credentials.py | 3 +- .../aws_user_login_profile_modified.py | 2 +- rules/aws_eks_rules/source_ip_multiple_403.py | 2 +- .../system_namespace_public_ip.py | 2 +- .../aws_guardduty_high_sev_findings.py | 2 +- .../aws_guardduty_low_sev_findings.py | 2 +- .../aws_guardduty_med_sev_findings.py | 2 +- rules/aws_s3_rules/aws_s3_access_error.py | 3 +- .../aws_s3_access_ip_allowlist.py | 2 +- rules/aws_s3_rules/aws_s3_insecure_access.py | 3 +- .../aws_s3_unauthenticated_access.py | 2 +- .../aws_s3_unknown_requester_get_object.py | 2 +- .../aws_vpc_healthy_log_status.py | 2 +- .../aws_vpc_inbound_traffic_port_allowlist.py | 2 +- .../aws_vpc_inbound_traffic_port_blocklist.py | 2 +- .../aws_vpc_unapproved_outbound_dns.py | 2 +- rules/box_rules/box_anomalous_download.py | 3 +- rules/box_rules/box_malicious_content.py | 3 +- .../box_suspicious_login_or_session.py | 3 +- .../crowdstrike_base64_encoded_args.py | 3 +- ...dstrike_connection_to_embargoed_country.py | 2 +- .../crowdstrike_credential_dumping_tool.py | 2 +- .../crowdstrike_cryptomining_tools.py | 2 +- .../crowdstrike_detection_passthrough.py | 5 +- .../crowdstrike_dns_request.py | 2 +- rules/crowdstrike_rules/crowdstrike_lolbas.py | 2 +- .../crowdstrike_macos_add_trusted_cert.py | 2 +- ...owdstrike_macos_osascript_administrator.py | 2 +- .../crowdstrike_macos_plutil_usage.py | 2 +- .../crowdstrike_real_time_response_session.py | 2 +- ...rowdstrike_remote_access_tool_execution.py | 2 +- ...crowdstrike_reverse_shell_tool_executed.py | 2 +- .../crowdstrike_systemlog_tampering.py | 2 +- ...wdstrike_unusual_parent_child_processes.py | 2 +- .../crowdstrike_wmi_query_detection.py | 2 +- .../crowdstrike_admin_role_assigned.py | 2 +- .../crowdstrike_allowlist_removed.py | 2 +- .../crowdstrike_api_key_created.py | 2 +- .../crowdstrike_api_key_deleted.py | 2 +- .../crowdstrike_ip_allowlist_changed.py | 2 +- .../crowdstrike_new_user_created.py | 2 +- .../crowdstrike_password_change.py | 2 +- .../crowdstrike_single_ip_allowlisted.py | 2 +- .../crowdstrike_user_deleted.py | 2 +- .../gcp_cloud_run_service_created.py | 2 +- .../gcp_cloud_run_set_iam_policy.py | 2 +- ...oudbuild_potential_privilege_escalation.py | 2 +- .../gcp_cloudfunctions_functions_create.py | 2 +- .../gcp_cloudfunctions_functions_update.py | 2 +- ...teinstances_create_privilege_escalation.py | 2 +- .../gcp_dns_zone_modified_or_deleted.py | 2 +- .../gcp_firewall_rule_created.py | 2 +- .../gcp_firewall_rule_deleted.py | 2 +- .../gcp_firewall_rule_modified.py | 2 +- ...p_iam_roles_update_privilege_escalation.py | 2 +- .../gcp_iam_service_account_key_create.py | 2 +- ...s_get_access_token_privilege_escalation.py | 2 +- .../gcp_iam_service_accounts_sign_blob.py | 2 +- .../gcp_iam_serviceaccounts_signjwt.py | 2 +- .../gcp_log_bucket_or_sink_deleted.py | 2 +- .../gcp_logging_sink_modified.py | 2 +- ...vilege_escalation_by_deployments_create.py | 2 +- .../gcp_service_account_access_denied.py | 2 +- ...age_apikeys_create_privilege_escalation.py | 2 +- .../gcp_k8s_cron_job_created_or_modified.py | 2 +- rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py | 42 +- rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.yml | 10 + rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py | 2 +- .../gcp_k8s_new_daemonset_deployed.py | 2 +- ...p_k8s_pod_attached_to_node_host_network.py | 2 +- ...od_create_or_modify_host_path_vol_mount.py | 2 +- .../gcp_k8s_pod_using_host_pid_namespace.py | 2 +- .../gcp_k8s_privileged_pod_created.py | 2 +- ...gcp_k8s_service_type_node_port_deployed.py | 2 +- rules/github_rules/github_action_failed.py | 2 +- .../github_advanced_security_change.py | 2 +- .../github_rules/github_org_moderators_add.py | 2 +- ..._organization_app_integration_installed.py | 2 +- .../github_public_repository_created.py | 2 +- .../github_repo_vulnerability_dismissed.py | 2 +- .../github_repository_transfer.py | 2 +- rules/github_rules/github_webhook_modified.py | 2 +- .../gsuite_drive_overly_visible.py | 4 +- .../gsuite_drive_visibility_change.py | 2 +- .../new_aws_account_logging.py | 2 +- .../new_user_account_logging.py | 2 +- .../microsoft365_brute_force_login_by_user.py | 2 +- .../microsoft365_external_sharing.py | 2 +- .../microsoft365_mfa_disabled.py | 2 +- .../microsoft_graph_passthrough.py | 2 +- rules/okta_rules/okta_admin_role_assigned.py | 2 +- .../okta_rules/okta_anonymizing_vpn_login.py | 2 +- rules/okta_rules/okta_api_key_created.py | 2 +- rules/okta_rules/okta_api_key_revoked.py | 2 +- .../okta_app_refresh_access_token_reuse.py | 2 +- .../okta_app_unauthorized_access_attempt.py | 2 +- .../okta_group_admin_role_assigned.py | 2 +- rules/okta_rules/okta_idp_create_modify.py | 2 +- rules/okta_rules/okta_idp_signin.py | 2 +- ...ta_new_behavior_accessing_admin_console.py | 2 +- .../okta_org2org_creation_modification.py | 2 +- .../okta_password_extraction_via_scim.py | 2 +- ...ta_phishing_attempt_blocked_by_fastpass.py | 2 +- .../okta_potentially_stolen_session.py | 2 +- rules/okta_rules/okta_rate_limits.py | 2 +- rules/okta_rules/okta_support_reset.py | 2 +- ..._threatinsight_security_threat_detected.py | 2 +- rules/okta_rules/okta_user_account_locked.py | 2 +- .../okta_user_mfa_factor_suspend.py | 2 +- rules/okta_rules/okta_user_mfa_reset.py | 2 +- rules/okta_rules/okta_user_mfa_reset_all.py | 2 +- .../okta_user_reported_suspicious_activity.py | 2 +- .../slack_rules/slack_app_access_expanded.py | 2 +- rules/slack_rules/slack_app_added.py | 2 +- rules/slack_rules/slack_app_removed.py | 2 +- rules/slack_rules/slack_application_dos.py | 2 +- rules/slack_rules/slack_dlp_modified.py | 2 +- rules/slack_rules/slack_ekm_config_changed.py | 2 +- .../slack_ekm_slackbot_unenrolled.py | 2 +- rules/slack_rules/slack_ekm_unenrolled.py | 2 +- .../slack_idp_configuration_change.py | 2 +- .../slack_information_barrier_modified.py | 2 +- .../slack_rules/slack_intune_mdm_disabled.py | 2 +- .../slack_legal_hold_policy_modified.py | 2 +- .../slack_rules/slack_mfa_settings_changed.py | 2 +- rules/slack_rules/slack_org_created.py | 2 +- rules/slack_rules/slack_org_deleted.py | 2 +- .../slack_rules/slack_passthrough_anomaly.py | 2 +- ...slack_potentially_malicious_file_shared.py | 2 +- .../slack_private_channel_made_public.py | 2 +- .../slack_privilege_changed_to_user.py | 2 +- .../slack_service_owner_transferred.py | 2 +- .../slack_rules/slack_sso_settings_changed.py | 2 +- .../slack_user_privilege_escalation.py | 2 +- rules/standard_rules/brute_force_by_ip.py | 4 +- .../standard_rules/impossible_travel_login.py | 4 +- .../malicious_sso_dns_lookup.py | 2 +- .../zendesk_mobile_app_access.py | 2 +- rules/zendesk_rules/zendesk_new_owner.py | 2 +- .../zendesk_sensitive_data_redaction.py | 2 +- rules/zendesk_rules/zendesk_user_role.py | 2 +- .../zendesk_rules/zendesk_user_suspension.py | 2 +- 304 files changed, 1148 insertions(+), 1356 deletions(-) delete mode 100644 global_helpers/crowdstrike_event_streams_helpers.yml delete mode 100644 global_helpers/gcp_environment.py delete mode 100644 global_helpers/gcp_environment.yml create mode 100644 global_helpers/panther_aws_helpers.py create mode 100644 global_helpers/panther_aws_helpers.yml rename global_helpers/{crowdstrike_event_streams_helpers.py => panther_crowdstrike_event_streams_helpers.py} (100%) create mode 100644 global_helpers/panther_crowdstrike_event_streams_helpers.yml create mode 100644 global_helpers/panther_crowdstrike_fdr_helpers.py create mode 100644 global_helpers/panther_crowdstrike_fdr_helpers.yml delete mode 100644 global_helpers/panther_default.py delete mode 100644 global_helpers/panther_default.yml rename global_helpers/{gcp_base_helpers.py => panther_gcp_helpers.py} (76%) rename global_helpers/{gcp_base_helpers.yml => panther_gcp_helpers.yml} (67%) create mode 100644 global_helpers/panther_github_helpers.py create mode 100644 global_helpers/panther_github_helpers.yml create mode 100644 global_helpers/panther_gsuite_helpers.py create mode 100644 global_helpers/panther_gsuite_helpers.yml create mode 100644 global_helpers/panther_msft_helpers.py create mode 100644 global_helpers/panther_msft_helpers.yml create mode 100644 global_helpers/panther_okta_helpers.py create mode 100644 global_helpers/panther_okta_helpers.yml delete mode 100644 global_helpers/panther_oss_helpers.py delete mode 100644 global_helpers/panther_oss_helpers.yml create mode 100644 global_helpers/panther_slack_helpers.py create mode 100644 global_helpers/panther_slack_helpers.yml create mode 100644 global_helpers/panther_zendesk_helpers.py create mode 100644 global_helpers/panther_zendesk_helpers.yml diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index 029dfdb91..85605b873 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -91,7 +91,7 @@ Panther's [dynamic auxiliary functions](https://docs.panther.com/detections/rule Check for `alert_context` functions in `global_helpers` for the LogType you are developing against. Alert context can be extended in specific rules, for example: ```python -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def alert_context(event): return aws_rule_context(event) | {'another_field': 'another_value'} diff --git a/data_models/gcp_data_model.py b/data_models/gcp_data_model.py index 46d236d1c..87bd37afc 100644 --- a/data_models/gcp_data_model.py +++ b/data_models/gcp_data_model.py @@ -2,7 +2,8 @@ from fnmatch import fnmatch import panther_event_type_helpers as event_type -from panther_base_helpers import deep_get, get_binding_deltas +from panther_base_helpers import deep_get +from panther_gcp_helpers import get_binding_deltas ADMIN_ROLES = { # Primitive Rolesx diff --git a/data_models/gsuite_data_model.py b/data_models/gsuite_data_model.py index afdca9ab1..f9366ca10 100644 --- a/data_models/gsuite_data_model.py +++ b/data_models/gsuite_data_model.py @@ -1,6 +1,6 @@ import panther_event_type_helpers as event_type from panther_base_helpers import deep_get -from panther_base_helpers import gsuite_details_lookup as details_lookup +from panther_gsuite_helpers import gsuite_details_lookup as details_lookup def get_event_type(event): diff --git a/data_models/zendesk_data_model.py b/data_models/zendesk_data_model.py index 1b9932392..735b3b21d 100644 --- a/data_models/zendesk_data_model.py +++ b/data_models/zendesk_data_model.py @@ -1,5 +1,5 @@ import panther_event_type_helpers as event_type -from panther_base_helpers import ZENDESK_CHANGE_DESCRIPTION, zendesk_get_roles +from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION, zendesk_get_roles ZENDESK_TWO_FACTOR_SOURCES = { "Two-Factor authentication for all admins and agents", diff --git a/global_helpers/crowdstrike_event_streams_helpers.yml b/global_helpers/crowdstrike_event_streams_helpers.yml deleted file mode 100644 index d27678594..000000000 --- a/global_helpers/crowdstrike_event_streams_helpers.yml +++ /dev/null @@ -1,5 +0,0 @@ -AnalysisType: global -Filename: crowdstrike_event_streams_helpers.py -GlobalID: "crowdstrike_event_streams_helpers" -Description: > - Helpers for Crowdstrike Event Streams detections. diff --git a/global_helpers/default_test.py b/global_helpers/default_test.py index 79e034c7d..c3fd27fda 100644 --- a/global_helpers/default_test.py +++ b/global_helpers/default_test.py @@ -6,11 +6,11 @@ import unittest sys.path.append(os.path.dirname(__file__)) -import panther_default as p_d # pylint: disable=C0413 +import panther_aws_helpers as p_aws_h # pylint: disable=C0413 class TestAWSKeyAccountId(unittest.TestCase): def test_aws_key_account_id(self): aws_key_id = "ASIAY34FZKBOKMUTVV7A" - account_id = p_d.aws_key_account_id(aws_key_id) + account_id = p_aws_h.aws_key_account_id(aws_key_id) self.assertEqual(account_id, "609629065308") diff --git a/global_helpers/gcp_environment.py b/global_helpers/gcp_environment.py deleted file mode 100644 index 72d19a5e1..000000000 --- a/global_helpers/gcp_environment.py +++ /dev/null @@ -1,23 +0,0 @@ -PRODUCTION_PROJECT_IDS = ["example-production", "example-platform"] -ORG_ID = "888888888888" - -rule_exceptions = { - "gcp_k8s_exec_into_pod": { - "allowed_principals": [ - { - "principals": [ - "system:serviceaccount:example-namespace:example-namespace-service-account" - ], - # If empty, then all namespaces - "namespaces": [], - # If projects empty then all projects - "projects": [], - }, - { - "principals": ["example-allowed-user@example.com"], - "namespaces": ["istio-system"], - "projects": [], - }, - ] - } -} diff --git a/global_helpers/gcp_environment.yml b/global_helpers/gcp_environment.yml deleted file mode 100644 index dfbdb78d7..000000000 --- a/global_helpers/gcp_environment.yml +++ /dev/null @@ -1,4 +0,0 @@ -AnalysisType: global -Description: Environment variables like ORG_ID, folder id/name mappings -Filename: gcp_environment.py -GlobalID: "gcp_environment" diff --git a/global_helpers/global_helpers_test.py b/global_helpers/global_helpers_test.py index f26321396..8e0be5120 100755 --- a/global_helpers/global_helpers_test.py +++ b/global_helpers/global_helpers_test.py @@ -19,14 +19,16 @@ import panther_asana_helpers as p_a_h # pylint: disable=C0413 import panther_auth0_helpers as p_auth0_h # pylint: disable=C0413 +import panther_aws_helpers as p_aws_h # pylint: disable=C0413 import panther_azuresignin_helpers as p_asi_h # pylint: disable=C0413 import panther_base_helpers as p_b_h # pylint: disable=C0413 +import panther_box_helpers as p_box_h # pylint: disable=C0413 import panther_cloudflare_helpers as p_cf_h # pylint: disable=C0413 +import panther_crowdstrike_fdr_helpers as p_cf_fdr_h # pylint: disable=C0413 import panther_greynoise_helpers as p_greynoise_h # pylint: disable=C0413 import panther_ipinfo_helpers as p_i_h # pylint: disable=C0413 import panther_lookuptable_helpers as p_l_h # pylint: disable=C0413 import panther_notion_helpers as p_notion_h # pylint: disable=C0413 -import panther_oss_helpers as p_o_h # pylint: disable=C0413 import panther_snyk_helpers as p_snyk_h # pylint: disable=C0413 import panther_tailscale_helpers as p_tscale_h # pylint: disable=C0413 import panther_tines_helpers as p_tines_h # pylint: disable=C0413 @@ -94,7 +96,7 @@ def setUp(self): ) def test_complete_event(self): - response = p_b_h.eks_panther_obj_ref(self.event) + response = p_aws_h.eks_panther_obj_ref(self.event) self.assertEqual(response.get("actor", ""), "kubernetes-admin") self.assertEqual(response.get("object", ""), "some-job-xxx1y") self.assertEqual(response.get("ns", ""), "default") @@ -112,7 +114,7 @@ def test_all_missing_event(self): del temp_event["verb"] del temp_event["p_source_label"] temp_event = PantherEvent(temp_event) - response = p_b_h.eks_panther_obj_ref(temp_event) + response = p_aws_h.eks_panther_obj_ref(temp_event) self.assertEqual(response.get("actor", ""), "") self.assertEqual(response.get("object", ""), "") self.assertEqual(response.get("ns", ""), "") @@ -126,7 +128,7 @@ def test_missing_subresource_event(self): temp_event = self.event.to_dict() del temp_event["objectRef"]["subresource"] temp_event = PantherEvent(temp_event) - response = p_b_h.eks_panther_obj_ref(temp_event) + response = p_aws_h.eks_panther_obj_ref(temp_event) self.assertEqual(response.get("resource", ""), "pods") @@ -168,37 +170,37 @@ def setUp(self): def test_additional_details_string(self): event = ImmutableCaseInsensitiveDict({"additional_details": self.initial_str}) - returns = p_b_h.box_parse_additional_details(event) + returns = p_box_h.box_parse_additional_details(event) self.assertEqual(returns.get("t", 0), 10) # in the case of a byte array, we expect the empty dict def test_additional_details_bytes(self): event = ImmutableCaseInsensitiveDict({"additional_details": self.initial_bytes}) - returns = p_b_h.box_parse_additional_details(event) + returns = p_box_h.box_parse_additional_details(event) self.assertEqual(len(returns), 0) # In the case of a list ( not a string or bytes array ), expect un-altered return def test_additional_details_list(self): event = ImmutableCaseInsensitiveDict({"additional_details": self.initial_list}) - returns = p_b_h.box_parse_additional_details(event) + returns = p_box_h.box_parse_additional_details(event) self.assertEqual(len(returns), 4) # in the case of a dict or similar, we expect it to be returned un-altered def test_additional_details_dict(self): event = ImmutableCaseInsensitiveDict({"additional_details": self.initial_dict}) - returns = p_b_h.box_parse_additional_details(event) + returns = p_box_h.box_parse_additional_details(event) self.assertEqual(returns.get("t", 0), 10) # If it's a string with no json object to be decoded, we expect an empty dict back def test_additional_details_plain_str(self): event = ImmutableCaseInsensitiveDict({"additional_details": self.initial_str_no_json}) - returns = p_b_h.box_parse_additional_details(event) + returns = p_box_h.box_parse_additional_details(event) self.assertEqual(len(returns), 0) # If it's a string with a json list, we expect the list def test_additional_details_str_list_json(self): event = ImmutableCaseInsensitiveDict({"additional_details": self.initial_str_list_json}) - returns = p_b_h.box_parse_additional_details(event) + returns = p_box_h.box_parse_additional_details(event) self.assertEqual(len(returns), 4) @@ -1100,11 +1102,11 @@ def setUp(self): ) def test_is_different_with_fdr_event_type_provided(self): - response = p_b_h.filter_crowdstrike_fdr_event_type(self.input, "SomethingElse") + response = p_cf_fdr_h.filter_crowdstrike_fdr_event_type(self.input, "SomethingElse") self.assertEqual(response, True) def test_is_same_with_the_fdr_event_type_provided(self): - response = p_b_h.filter_crowdstrike_fdr_event_type(self.input, "DnsRequest") + response = p_cf_fdr_h.filter_crowdstrike_fdr_event_type(self.input, "DnsRequest") self.assertEqual(response, False) def test_is_entirely_different_type(self): @@ -1115,7 +1117,7 @@ def test_is_entirely_different_type(self): "event": {"foo": "bar"}, } ) - response = p_b_h.filter_crowdstrike_fdr_event_type(self.input, "DnsRequest") + response = p_cf_fdr_h.filter_crowdstrike_fdr_event_type(self.input, "DnsRequest") self.assertEqual(response, False) @@ -1131,30 +1133,30 @@ def setUp(self): ) def test_input_key_default_works(self): - response = p_b_h.get_crowdstrike_field(self.input, "zee", default="hello") + response = p_cf_fdr_h.get_crowdstrike_field(self.input, "zee", default="hello") self.assertEqual(response, "hello") def test_input_key_does_not_exist(self): - response = p_b_h.get_crowdstrike_field(self.input, "zee") + response = p_cf_fdr_h.get_crowdstrike_field(self.input, "zee") self.assertEqual(response, None) def test_input_key_exists(self): - response = p_b_h.get_crowdstrike_field(self.input, "cid") + response = p_cf_fdr_h.get_crowdstrike_field(self.input, "cid") self.assertEqual(response, "something") def test_input_key_can_be_found_in_event(self): - response = p_b_h.get_crowdstrike_field(self.input, "foo") + response = p_cf_fdr_h.get_crowdstrike_field(self.input, "foo") self.assertEqual(response, "bar") def test_input_key_can_be_found_in_unknown(self): - response = p_b_h.get_crowdstrike_field(self.input, "field") + response = p_cf_fdr_h.get_crowdstrike_field(self.input, "field") self.assertEqual(response, "is") def test_precedence(self): temp_event = self.input.to_dict() temp_event["event"]["field"] = "found" temp_event = PantherEvent(temp_event) - response = p_b_h.get_crowdstrike_field(temp_event, "field") + response = p_cf_fdr_h.get_crowdstrike_field(temp_event, "field") self.assertEqual(response, "found") @@ -1974,10 +1976,10 @@ def setUp(self): ) def test_distances(self): - nyc_to_sfo = p_o_h.km_between_ipinfo_loc(self.loc_nyc, self.loc_sfo) - nyc_to_athens = p_o_h.km_between_ipinfo_loc(self.loc_nyc, self.loc_athens) - nyc_to_aukland = p_o_h.km_between_ipinfo_loc(self.loc_nyc, self.loc_aukland) - aukland_to_nyc = p_o_h.km_between_ipinfo_loc(self.loc_aukland, self.loc_nyc) + nyc_to_sfo = p_i_h.km_between_ipinfo_loc(self.loc_nyc, self.loc_sfo) + nyc_to_athens = p_i_h.km_between_ipinfo_loc(self.loc_nyc, self.loc_athens) + nyc_to_aukland = p_i_h.km_between_ipinfo_loc(self.loc_nyc, self.loc_aukland) + aukland_to_nyc = p_i_h.km_between_ipinfo_loc(self.loc_aukland, self.loc_nyc) # I used https://www.nhc.noaa.gov/gccalc.shtml to get test comparison distances # # delta is set to 0.5% of total computed distanc from gccalc diff --git a/global_helpers/panther_aws_helpers.py b/global_helpers/panther_aws_helpers.py new file mode 100644 index 000000000..4da927105 --- /dev/null +++ b/global_helpers/panther_aws_helpers.py @@ -0,0 +1,225 @@ +import base64 +import binascii +import os +from typing import Any, Dict, List + +import boto3 +from panther_config import config + + +class BadLookup(Exception): + """Error returned when a resource lookup fails.""" + + +class PantherBadInput(Exception): + """Error returned when a Panther helper function is provided bad input.""" + + +AWS_ACCOUNTS = config.AWS_ACCOUNTS +_RESOURCE_TABLE = None # boto3.Table resource, lazily constructed +FIPS_ENABLED = os.getenv("ENABLE_FIPS", "").lower() == "true" +FIPS_SUFFIX = "-fips." + os.getenv("AWS_REGION", "") + ".amazonaws.com" + + +def aws_strip_role_session_id(user_identity_arn): + # The ARN structure is arn:aws:sts::123456789012:assumed-role/RoleName/ + arn_parts = user_identity_arn.split("/") + if arn_parts: + return "/".join(arn_parts[:2]) + return user_identity_arn + + +def aws_rule_context(event: dict): + return { + "eventName": event.get("eventName", ""), + "eventSource": event.get("eventSource", ""), + "awsRegion": event.get("awsRegion", ""), + "recipientAccountId": event.get("recipientAccountId", ""), + "sourceIPAddress": event.get("sourceIPAddress", ""), + "userAgent": event.get("userAgent", ""), + "userIdentity": event.get("userIdentity", ""), + } + + +def aws_guardduty_context(event: dict): + return { + "description": event.get("description", ""), + "severity": event.get("severity", ""), + "id": event.get("id", ""), + "type": event.get("type", ""), + "resource": event.get("resource", {}), + "service": event.get("service", {}), + } + + +def eks_panther_obj_ref(event): + user = event.deep_get("user", "username", default="") + source_ips = event.get("sourceIPs", ["0.0.0.0"]) # nosec + verb = event.get("verb", "") + obj_name = event.deep_get("objectRef", "name", default="") + obj_ns = event.deep_get("objectRef", "namespace", default="") + obj_res = event.deep_get("objectRef", "resource", default="") + obj_subres = event.deep_get("objectRef", "subresource", default="") + p_source_label = event.get("p_source_label", "") + if obj_subres: + obj_res = "/".join([obj_res, obj_subres]) + return { + "actor": user, + "ns": obj_ns, + "object": obj_name, + "resource": obj_res, + "sourceIPs": source_ips, + "verb": verb, + "p_source_label": p_source_label, + } + + +def aws_cloudtrail_success(event): + if event.get("errorCode", "") or event.get("errorMessage", ""): + return False + return True + + +def aws_event_tense(event_name): + """Convert an AWS CloudTrail eventName to be interpolated in alert titles + + An example is passing in StartInstance and returning 'started'. + This would then be used in an alert title such as + 'The EC2 instance my-instance was started'. + + Args: + event_name (str): The CloudTrail eventName + + Returns: + str: A tensed version of the event name + """ + mapping = { + "Create": "created", + "Delete": "deleted", + "Start": "started", + "Stop": "stopped", + "Update": "updated", + } + for event_prefix, tensed in mapping.items(): + if event_name.startswith(event_prefix): + return tensed + # If the event pattern doesn't exist, return original + return event_name + + +# Adapted from https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489 +def aws_key_account_id(aws_key: str): + """retrieve the AWS account ID associated with a given access key ID""" + key_no_prefix = aws_key[4:] # remove the four-character prefix + base32_key = base64.b32decode(key_no_prefix) # remainder of the key is base32-encoded + decoded_key = base32_key[0:6] # retrieve the 10-byte string + + # Convert the 10-byte string to an integer + key_id_int = int.from_bytes(decoded_key, byteorder="big", signed=False) + mask = int.from_bytes(binascii.unhexlify(b"7fffffffff80"), byteorder="big", signed=False) + + # Do a bitwise AND with the mask to retrieve the account ID + # (divide the 10-byte key integer by 128 and remove the fractional part(s)) + account_id = (key_id_int & mask) >> 7 + return str(account_id) + + +def aws_regions() -> List[str]: + """return a list of AWS regions""" + return [ + "ap-east-1", + "ap-northeast-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-south-2", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ap-southeast-4", + "ca-central-1", + "ca-central-1", + "eu-central-1", + "eu-central-1", + "eu-central-2", + "eu-north-1", + "eu-north-1", + "eu-south-1", + "eu-south-2", + "eu-west-1", + "eu-west-1", + "eu-west-2", + "eu-west-2", + "eu-west-3", + "eu-west-3", + "il-central-1", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + ] + + +def lookup_aws_account_name(account_id): + """Lookup the AWS account name, return the ID if not found + + Args: + account_id (str): The AWS account ID + + Returns: + str: The name of the AWS account ID + or + str: The AWS account ID (unnamed account) + """ + return AWS_ACCOUNTS.get(account_id, f"{account_id} (unnamed account)") + + +def get_s3_arn_by_name(name: str) -> str: + """This function is used to construct an s3 bucket ARN from its name.""" + if name == "": + raise PantherBadInput("s3 name cannot be blank") + return "arn:aws:s3:::" + name + + +def s3_lookup_by_name(name: str) -> Dict[str, Any]: + """This function is used to get an S3 bucket resource from just its name.""" + return resource_lookup(get_s3_arn_by_name(name)) + + +def resource_table() -> boto3.resource: + """Lazily build resource table""" + # pylint: disable=global-statement + global _RESOURCE_TABLE + if not _RESOURCE_TABLE: + # pylint: disable=no-member + _RESOURCE_TABLE = boto3.resource( + "dynamodb", + endpoint_url="https://dynamodb" + FIPS_SUFFIX if FIPS_ENABLED else None, + ).Table("panther-resources") + return _RESOURCE_TABLE + + +def resource_lookup(resource_id: str) -> Dict[str, Any]: + """This function is used to get a resource from the resources-api based on its resourceID.""" + # Validate input so we can provide meaningful error messages to users + if resource_id == "": + raise PantherBadInput("resourceId cannot be blank") + + # Get the item from dynamo + response = resource_table().get_item(Key={"id": resource_id}) + + # Check if dynamo failed + status_code = response["ResponseMetadata"]["HTTPStatusCode"] + if status_code != 200: + raise BadLookup("dynamodb - " + str(status_code) + " HTTPStatusCode") + + # Check if the item was found + if "Item" not in response: + raise BadLookup(resource_id + " not found") + + # Return just the attributes of the item + return response["Item"]["attributes"] diff --git a/global_helpers/panther_aws_helpers.yml b/global_helpers/panther_aws_helpers.yml new file mode 100644 index 000000000..891605da4 --- /dev/null +++ b/global_helpers/panther_aws_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_aws_helpers.py +GlobalID: "panther_aws_helpers" +Description: > + Used to define global helpers and variables for AWS events. diff --git a/global_helpers/panther_base_helpers.py b/global_helpers/panther_base_helpers.py index f17549a6a..6a0970d3b 100644 --- a/global_helpers/panther_base_helpers.py +++ b/global_helpers/panther_base_helpers.py @@ -1,4 +1,3 @@ -import json import re from base64 import b64decode from binascii import Error as AsciiError @@ -10,7 +9,7 @@ from ipaddress import ip_address, ip_network from typing import Any, List, Optional, Sequence, Union -from panther_config import config +from dateutil import parser # # # # # # # # # # # # # # # Exceptions # @@ -21,281 +20,11 @@ class PantherUnexpectedAlert(Exception): """Error returned when a Panther detection encounters an unexpected situation.""" -# # # # # # # # # # # # # # -# Compliance Helpers # -# # # # # # # # # # # # # # - -# Expects a map with a Key 'Tags' that maps to a map of key/value string pairs, or None if no -# tags are present. -# All Panther defined resources meet this requirement. -CDE_TAG_KEY = "environment" -CDE_TAG_VALUE = "pci" - - -# Defaults to True to assume something is in scope if it is not tagged -def in_pci_scope_tags(resource): - if resource.get("Tags") is None: - return True - return resource["Tags"].get(CDE_TAG_KEY) == CDE_TAG_VALUE - - -PCI_NETWORKS = config.PCI_NETWORKS - - -# Expects a string in cidr notation (e.g. '10.0.0.0/24') indicating the ip range being checked -# Returns True if any ip in the range is marked as in scope -def is_pci_scope_cidr(ip_range): - return any(ip_network(ip_range).overlaps(pci_network) for pci_network in PCI_NETWORKS) - - -DMZ_NETWORKS = config.DMZ_NETWORKS - - -# Expects a string in cidr notation (e.g. '10.0.0.0/24') indicating the ip range being checked -# Returns True if any ip in the range is marked as DMZ space. -def is_dmz_cidr(ip_range): - """This function determines whether a given IP range is within the defined DMZ IP range.""" - return any(ip_network(ip_range).overlaps(dmz_network) for dmz_network in DMZ_NETWORKS) - - -# Defaults to False to assume something is not a DMZ if it is not tagged -def is_dmz_tags(resource, dmz_tags): - """This function determines whether a given resource is tagged as existing in a DMZ.""" - if resource["Tags"] is None: - return False - for key, value in dmz_tags: - if resource["Tags"].get(key) == value: - return True - return False - - -# Function variables here so that implementation details of these functions can be changed without -# having to rename the function in all locations its used, or having an outdated name on the actual -# function being used, etc. -IN_PCI_SCOPE = in_pci_scope_tags - -# # # # # # # # # # # # # # -# GSuite Helpers # -# # # # # # # # # # # # # # - -GSUITE_PARAMETER_VALUES = [ - "value", - "intValue", - "boolValue", - "multiValue", - "multiIntValue", - "messageValue", - "multiMessageValue", -] - - -# GSuite parameters are formatted as a list of dictionaries, where each dictionary has a 'name' key -# that maps to the name of the parameter, and one key from GSUITE_PARAMETER_VALUES that maps to the -# value of the parameter. This means to lookup the value of a particular parameter, you must -# traverse the entire list of parameters to find it and then know (or guess) what type of value it -# contains. This helper function handles that for us. -# -# Example parameters list: -# parameters = [ -# { -# "name": "event_id", -# "value": "abc123" -# }, -# { -# "name": "start_time", -# "intValue": 63731901000 -# }, -# { -# "name": "end_time", -# "intValue": 63731903000 -# }, -# { -# "name": "things", -# "multiValue": [ "DRIVE" , "MEME"] -# } -# ] -def gsuite_parameter_lookup(parameters, key): - for param in parameters: - if param["name"] != key: - continue - for value in GSUITE_PARAMETER_VALUES: - if value in param: - return param[value] - return None - return None - - -# GSuite event details are formatted as a list of dictionaries. Each entry has a 'type' and 'name'. -# -# In order to find the event details of interest, you must loop through -# the list searching for a particular type and name. -# -# This helper function handles the looping functionality that is common in many of the gsuite rules -def gsuite_details_lookup(detail_type, detail_names, event): - for details in event.get("events", {}): - if details.get("type") == detail_type and details.get("name") in detail_names: - return details - # not found, return empty dict - return {} - - -# # # # # # # # # # # # # # -# Zendesk Helpers # -# # # # # # # # # # # # # # - -# key names -ZENDESK_CHANGE_DESCRIPTION = "change_description" -ZENDESK_APP_ROLE_ASSIGNED = re.compile( - r"(?P.*) role changed from (?P.+) to (?P.*)", re.IGNORECASE -) -ZENDESK_ROLE_ASSIGNED = re.compile( - r"Role changed from (?P.+) to (?P[^$]+)", re.IGNORECASE -) - - -def zendesk_get_roles(event): - old_role = "" - new_role = "" - role_change = event.get(ZENDESK_CHANGE_DESCRIPTION, "") - if "\n" in role_change: - for app_change in role_change.split("\n"): - matches = ZENDESK_APP_ROLE_ASSIGNED.match(app_change) - if matches: - if old_role: - old_role += " ; " - old_role += matches.group("app") + ":" + matches.group("old_role") - if new_role: - new_role += " ; " - new_role += matches.group("app") + ":" + matches.group("new_role") - else: - matches = ZENDESK_ROLE_ASSIGNED.match(role_change) - if matches: - old_role = matches.group("old_role") - new_role = matches.group("new_role") - if not old_role: - old_role = ":" - if not new_role: - new_role = ":" - return old_role, new_role - - # # # # # # # # # # # # # # # Generic Helpers # # # # # # # # # # # # # # # -# 'additional_details' from box logs varies by event_type. -# This helper wraps the process of extracting those details. -def box_parse_additional_details(event: dict): - additional_details = event.get("additional_details", {}) - if isinstance(additional_details, (str, bytes)): - try: - return json.loads(additional_details) - except ValueError: - return {} - return additional_details - - -def okta_alert_context(event: dict): - """Returns common context for automation of Okta alerts""" - return { - "event_type": event.get("eventtype", ""), - "severity": event.get("severity", ""), - "actor": event.get("actor", {}), - "client": event.get("client", {}), - "request": event.get("request", {}), - "outcome": event.get("outcome", {}), - "target": event.get("target", []), - "debug_context": event.get("debugcontext", {}), - "authentication_context": event.get("authenticationcontext", {}), - "security_context": event.get("securitycontext", {}), - "ips": event.get("p_any_ip_addresses", []), - } - - -def crowdstrike_detection_alert_context(event: dict): - """Returns common context for Crowdstrike detections""" - return { - "aid": get_crowdstrike_field(event, "aid", default=""), - "user": get_crowdstrike_field(event, "UserName", default=""), - "console-link": get_crowdstrike_field(event, "FalconHostLink", default=""), - "commandline": get_crowdstrike_field(event, "CommandLine", default=""), - "parentcommandline": get_crowdstrike_field(event, "ParentCommandLine", default=""), - "filename": get_crowdstrike_field(event, "FileName", default=""), - "filepath": get_crowdstrike_field(event, "FilePath", default=""), - "description": get_crowdstrike_field(event, "DetectDescription", default=""), - "action": get_crowdstrike_field(event, "PatternDispositionDescription", default=""), - } - - -def crowdstrike_process_alert_context(event: dict): - """Returns common process context for Crowdstrike detections""" - return { - "aid": get_crowdstrike_field(event, "aid", default=""), - "CommandLine": get_crowdstrike_field(event, "CommandLine", default=""), - "TargetProcessId": get_crowdstrike_field(event, "TargetProcessId", default=""), - "RawProcessId": get_crowdstrike_field(event, "RawProcessId", default=""), - "ParentBaseFileName": get_crowdstrike_field(event, "ParentBaseFileName", default=""), - "ParentProcessId": get_crowdstrike_field(event, "ParentProcessId", default=""), - "ImageFileName": get_crowdstrike_field(event, "ImageFileName", default=""), - "SHA256Hash": get_crowdstrike_field(event, "SHA256HashData", default=""), - "platform": get_crowdstrike_field(event, "event_platform", default=""), - } - - -def crowdstrike_network_detection_alert_context(event: dict): - """Returns common network context for Crowdstrike detections""" - return { - "LocalAddressIP4": get_crowdstrike_field(event, "LocalAddressIP4", default=""), - "LocalPort": get_crowdstrike_field(event, "LocalPort", default=""), - "RemoteAddressIP4": get_crowdstrike_field(event, "RemoteAddressIP4", default=""), - "RemotePort": get_crowdstrike_field(event, "RemotePort", default=""), - "Protocol": get_crowdstrike_field(event, "Protocol", default=""), - "event_simpleName": get_crowdstrike_field(event, "event_simpleName", default=""), - "aid": get_crowdstrike_field(event, "aid", default=""), - "ContextProcessId": get_crowdstrike_field(event, "ContextProcessId", default=""), - } - - -def filter_crowdstrike_fdr_event_type(event, name: str) -> bool: - """ - Checks if the event belongs to the Crowdstrike.FDREvent log type - and the event type is not the name parameter. - """ - if event.get("p_log_type") != "Crowdstrike.FDREvent": - return False - return event.get("fdr_event_type", "") != name - - -def get_crowdstrike_field(event, field_name, default=None): - return ( - event.deep_get(field_name) - or event.deep_get("event", field_name) - or event.deep_get("unknown_payload", field_name) - or default - ) - - -def slack_alert_context(event): - return { - "actor-name": event.deep_get("actor", "user", "name", default=""), - "actor-email": event.deep_get("actor", "user", "email", default=""), - "actor-ip": event.deep_get("context", "ip_address", default=""), - "user-agent": event.deep_get("context", "ua", default=""), - } - - -def github_alert_context(event): - return { - "action": event.get("action", ""), - "actor": event.get("actor", ""), - "actor_location": event.deep_get("actor_location", "country_code"), - "org": event.get("org", ""), - "repo": event.get("repo", ""), - "user": event.get("user", ""), - } - - def deep_get(dictionary: dict, *keys, default=None): """Safely return the value of an arbitrarily nested map @@ -381,59 +110,6 @@ def get_val_from_list(list_of_dicts, return_field_key, field_cmp_key, field_cmp_ return values_of_return_field -def aws_strip_role_session_id(user_identity_arn): - # The ARN structure is arn:aws:sts::123456789012:assumed-role/RoleName/ - arn_parts = user_identity_arn.split("/") - if arn_parts: - return "/".join(arn_parts[:2]) - return user_identity_arn - - -def aws_rule_context(event: dict): - return { - "eventName": event.get("eventName", ""), - "eventSource": event.get("eventSource", ""), - "awsRegion": event.get("awsRegion", ""), - "recipientAccountId": event.get("recipientAccountId", ""), - "sourceIPAddress": event.get("sourceIPAddress", ""), - "userAgent": event.get("userAgent", ""), - "userIdentity": event.get("userIdentity", ""), - } - - -def aws_guardduty_context(event: dict): - return { - "description": event.get("description", ""), - "severity": event.get("severity", ""), - "id": event.get("id", ""), - "type": event.get("type", ""), - "resource": event.get("resource", {}), - "service": event.get("service", {}), - } - - -def eks_panther_obj_ref(event): - user = event.deep_get("user", "username", default="") - source_ips = event.get("sourceIPs", ["0.0.0.0"]) # nosec - verb = event.get("verb", "") - obj_name = event.deep_get("objectRef", "name", default="") - obj_ns = event.deep_get("objectRef", "namespace", default="") - obj_res = event.deep_get("objectRef", "resource", default="") - obj_subres = event.deep_get("objectRef", "subresource", default="") - p_source_label = event.get("p_source_label", "") - if obj_subres: - obj_res = "/".join([obj_res, obj_subres]) - return { - "actor": user, - "ns": obj_ns, - "object": obj_name, - "resource": obj_res, - "sourceIPs": source_ips, - "verb": verb, - "p_source_label": p_source_label, - } - - def is_ip_in_network(ip_addr, networks): """Check that a given IP is within a list of IP ranges""" return any(ip_address(ip_addr) in ip_network(network) for network in networks) @@ -449,51 +125,23 @@ def pattern_match_list(string_to_match: str, patterns: Sequence[str]): return any(fnmatch(string_to_match, p) for p in patterns) -def get_binding_deltas(event): - """A GCP helper function to return the binding deltas from audit events - - Binding deltas provide context on a permission change, including the - action, role, and member associated with the request. - """ - if event.get("protoPayload", {}).get("methodName") != "SetIamPolicy": - return [] - - service_data = event.get("protoPayload", {}).get("serviceData") - if not service_data: - return [] - - # Reference: bit.ly/2WsJdZS - binding_deltas = service_data.get("policyDelta", {}).get("bindingDeltas") - if not binding_deltas: - return [] - return binding_deltas - - -def msft_graph_alert_context(event): - return { - "category": event.get("category", ""), - "description": event.get("description", ""), - "userStates": event.get("userStates", []), - "fileStates": event.get("fileStates", []), - "hostStates": event.get("hostStates", []), - } - +def defang_ioc(ioc: str) -> str: + """return defanged IOC from 1.1.1.1 to 1[.]1[.]1[.]1""" + ioc = ioc.replace("http://", "hxxp://") + ioc = ioc.replace("https://", "hxxps://") + return ioc.replace(".", "[.]") -def m365_alert_context(event): - return { - "operation": event.get("Operation", ""), - "organization_id": event.get("OrganizationId", ""), - "client_ip": event.get("ClientIp", ""), - "extended_properties": event.get("ExtendedProperties", []), - "modified_properties": event.get("ModifiedProperties", []), - "application": event.get("Application", ""), - "actor": event.get("Actor", []), - } +# IOC Helper functions: +def ioc_match(indicators: list, known_iocs: set) -> list: + """Matches a set of indicators against known Indicators of Compromise -def defang_ioc(ioc): - """return defanged IOC from 1.1.1.1 to 1[.]1[.]1[.]1""" - return ioc.replace(".", "[.]") + :param indicators: List of potential indicators of compromise + :param known_iocs: Set of known indicators of compromise + :return: List of any indicator matches + """ + # Check through the IP IOCs + return [ioc for ioc in (indicators or []) if ioc in known_iocs] def panther_nanotime_to_python_datetime(panther_time: str) -> datetime: @@ -538,3 +186,144 @@ def key_value_list_to_dict(list_objects: List[dict], key: str, value: str) -> di # example: [{'key': 'a', 'value': 1}, {'key': 'b', 'value': 2}] # becomes: {'a': 1, 'b': 2} return {item[key]: item[value] for item in list_objects} + + +# When a single item is loaded from json, it is loaded as a single item +# When a list of items is loaded from json, it is loaded as a list of that item +# When we want to iterate over something that could be a single item or a list +# of items we can use listify and just continue as if it's always a list +def listify(maybe_list): + try: + iter(maybe_list) + except TypeError: + # not a list + return [maybe_list] + # either a list or string + return [maybe_list] if isinstance(maybe_list, (str, bytes, dict)) else maybe_list + + +# Auto Time Resolution Parameters +EPOCH_REGEX = r"([0-9]{9,12}(\.\d+)?)" +TIME_FORMATS = [ + "%Y-%m-%d %H:%M:%S", # Panther p_event_time Timestamp + "%Y-%m-%dT%H:%M:%SZ", # AWS Timestamp + "%Y-%m-%dT%H:%M:%S.%fZ", # Panther Timestamp + "%Y-%m-%dT%H:%M:%S*%f%z", + "%Y %b %d %H:%M:%S.%f %Z", + "%b %d %H:%M:%S %z %Y", + "%d/%b/%Y:%H:%M:%S %z", + "%b %d, %Y %I:%M:%S %p", + "%b %d %Y %H:%M:%S", + "%b %d %H:%M:%S %Y", + "%b %d %H:%M:%S %z", + "%b %d %H:%M:%S", + "%Y-%m-%dT%H:%M:%S%z", + "%Y-%m-%dT%H:%M:%S.%f%z", + "%Y-%m-%d %H:%M:%S %z", + "%Y-%m-%d %H:%M:%S%z", + "%Y-%m-%d %H:%M:%S,%f", + "%Y/%m/%d*%H:%M:%S", + "%Y %b %d %H:%M:%S.%f*%Z", + "%Y %b %d %H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S,%f%z", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S.%f%z", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%dT%H:%M:%S%z", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d*%H:%M:%S:%f", + "%Y-%m-%d*%H:%M:%S", + "%y-%m-%d %H:%M:%S,%f %z", + "%y-%m-%d %H:%M:%S,%f", + "%y-%m-%d %H:%M:%S", + "%y/%m/%d %H:%M:%S", + "%y%m%d %H:%M:%S", + "%Y%m%d %H:%M:%S.%f", + "%m/%d/%y*%H:%M:%S", + "%m/%d/%Y*%H:%M:%S", + "%m/%d/%Y*%H:%M:%S*%f", + "%m/%d/%y %H:%M:%S %z", + "%m/%d/%Y %H:%M:%S %z", + "%H:%M:%S", + "%H:%M:%S.%f", + "%H:%M:%S,%f", + "%d/%b %H:%M:%S,%f", + "%d/%b/%Y:%H:%M:%S", + "%d/%b/%Y %H:%M:%S", + "%d-%b-%Y %H:%M:%S", + "%d-%b-%Y %H:%M:%S.%f", + "%d %b %Y %H:%M:%S", + "%d %b %Y %H:%M:%S*%f", + "%m%d_%H:%M:%S", + "%m%d_%H:%M:%S.%f", + "%m/%d/%Y %I:%M:%S %p:%f", + "%m/%d/%Y %I:%M:%S %p", +] + + +def resolve_timestamp_string(timestamp: str) -> Optional[datetime]: + """Auto Time Resolution""" + if not timestamp: + return None + + # Removes weird single-quotes used in some timestamp formats + ts_format = timestamp.replace("'", "") + # Attempt to resolve timestamp format + for each_format in TIME_FORMATS: + try: + return datetime.strptime(ts_format, each_format) + except (ValueError, TypeError): + continue + try: + return parser.parse(timestamp) + except (ValueError, TypeError, parser.ParserError): + pass + + # Attempt to resolve epoch format + # Since datetime.utcfromtimestamp supports 9 through 12 digit epoch timestamps + # and we only want the first 12 digits. + match = re.match(EPOCH_REGEX, timestamp) + if match.group(0) != "": + try: + return datetime.utcfromtimestamp(float(match.group(0))) + except (ValueError, TypeError): + return None + return None + + +# returns the difference between time1 and later time 2 in human-readable time period string +def time_delta(time1, time2: str) -> str: + time1_truncated = nano_to_micro(time1) + time2_truncated = nano_to_micro(time2) + delta_timedelta = resolve_timestamp_string(time2_truncated) - resolve_timestamp_string( + time1_truncated + ) + days = delta_timedelta.days + hours, remainder = divmod(delta_timedelta.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + delta = "" + if days > 0: + delta = f"{days} day(s) " + if hours > 0: + delta = "".join([delta, f"{hours} hour(s) "]) + if minutes > 0: + delta = "".join([delta, f"{minutes} minute(s) "]) + if seconds > 0: + delta = "".join([delta, f"{seconds} second(s)"]) + return delta + + +def nano_to_micro(time_str: str) -> str: + parts = time_str.split(":") + # pylint: disable=consider-using-f-string + parts[-1] = "{:06f}".format(float(parts[-1])) + return ":".join(parts) + + +# adds parsing delay to an alert_context +def add_parse_delay(event, context: dict) -> dict: + parsing_delay = time_delta(event.get("p_event_time"), event.get("p_parse_time")) + context["parseDelay"] = f"{parsing_delay}" + return context diff --git a/global_helpers/panther_box_helpers.py b/global_helpers/panther_box_helpers.py index 39e004c46..cf4259c97 100644 --- a/global_helpers/panther_box_helpers.py +++ b/global_helpers/panther_box_helpers.py @@ -135,3 +135,15 @@ def build_jwt_settings(response: dict) -> dict: "enterpriseID": data[BOX_ENTERPRISE_ID], } return settings + + +# 'additional_details' from box logs varies by event_type. +# This helper wraps the process of extracting those details. +def box_parse_additional_details(event: dict): + additional_details = event.get("additional_details", {}) + if isinstance(additional_details, (str, bytes)): + try: + return json.loads(additional_details) + except ValueError: + return {} + return additional_details diff --git a/global_helpers/panther_config_defaults.py b/global_helpers/panther_config_defaults.py index d98ac2f3a..365a7bfc7 100644 --- a/global_helpers/panther_config_defaults.py +++ b/global_helpers/panther_config_defaults.py @@ -2,18 +2,33 @@ Here, default values for `panther_config.config` are defined """ +from ipaddress import ip_network + # A list of public DNS domain names that fall under the administrative domain of # the Panther installation ORGANIZATION_DOMAINS = ["example.com"] +AWS_ACCOUNTS = { + # Add your AWS account IDs/names below: + "123456789012": "sample-account", +} DROPBOX_ALLOWED_SHARE_DOMAINS = ORGANIZATION_DOMAINS DROPBOX_TRUSTED_OWNERSHIP_DOMAINS = ORGANIZATION_DOMAINS +GCP_PRODUCTION_PROJECT_IDS = ["example-production", "example-platform"] +GCP_ORG_ID = "888888888888" GSUITE_TRUSTED_FORWARDING_DESTINATION_DOMAINS = ORGANIZATION_DOMAINS GSUITE_TRUSTED_OWNERSHIP_DOMAINS = ORGANIZATION_DOMAINS MS_EXCHANGE_ALLOWED_FORWARDING_DESTINATION_DOMAINS = ORGANIZATION_DOMAINS MS_EXCHANGE_ALLOWED_FORWARDING_DESTINATION_EMAILS = ["postmaster@" + ORGANIZATION_DOMAINS[0]] TELEPORT_ORGANIZATION_DOMAINS = ORGANIZATION_DOMAINS +# Expects a map with a Key 'Tags' that maps to a map of key/value string pairs, or None if no +# tags are present. +# All Panther defined resources meet this requirement. +CDE_TAG_KEY = "environment" +CDE_TAG_VALUE = "pci" + + DMZ_NETWORKS = [ # ip_network("10.1.0.0/24"), ] @@ -24,6 +39,44 @@ ] ) + PCI_NETWORKS = [ # ip_network("10.0.0.0/24"), ] + + +# Expects a string in cidr notation (e.g. '10.0.0.0/24') indicating the ip range being checked +# Returns True if any ip in the range is marked as DMZ space. +def is_dmz_cidr(ip_range): + """This function determines whether a given IP range is within the defined DMZ IP range.""" + return any(ip_network(ip_range).overlaps(dmz_network) for dmz_network in DMZ_NETWORKS) + + +# Defaults to False to assume something is not a DMZ if it is not tagged +def is_dmz_tags(resource, dmz_tags): + """This function determines whether a given resource is tagged as existing in a DMZ.""" + if resource["Tags"] is None: + return False + for key, value in dmz_tags: + if resource["Tags"].get(key) == value: + return True + return False + + +# Expects a string in cidr notation (e.g. '10.0.0.0/24') indicating the ip range being checked +# Returns True if any ip in the range is marked as in scope +def is_pci_scope_cidr(ip_range): + return any(ip_network(ip_range).overlaps(pci_network) for pci_network in PCI_NETWORKS) + + +# Defaults to True to assume something is in scope if it is not tagged +def in_pci_scope_tags(resource): + if resource.get("Tags") is None: + return True + return resource["Tags"].get(CDE_TAG_KEY) == CDE_TAG_VALUE + + +# Function variables here so that implementation details of these functions can be changed without +# having to rename the function in all locations its used, or having an outdated name on the actual +# function being used, etc. +IN_PCI_SCOPE = in_pci_scope_tags diff --git a/global_helpers/crowdstrike_event_streams_helpers.py b/global_helpers/panther_crowdstrike_event_streams_helpers.py similarity index 100% rename from global_helpers/crowdstrike_event_streams_helpers.py rename to global_helpers/panther_crowdstrike_event_streams_helpers.py diff --git a/global_helpers/panther_crowdstrike_event_streams_helpers.yml b/global_helpers/panther_crowdstrike_event_streams_helpers.yml new file mode 100644 index 000000000..354e719e3 --- /dev/null +++ b/global_helpers/panther_crowdstrike_event_streams_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_crowdstrike_event_streams_helpers.py +GlobalID: "panther_crowdstrike_event_streams_helpers" +Description: > + Helpers for Crowdstrike Event Streams detections. diff --git a/global_helpers/panther_crowdstrike_fdr_helpers.py b/global_helpers/panther_crowdstrike_fdr_helpers.py new file mode 100644 index 000000000..15c28f9db --- /dev/null +++ b/global_helpers/panther_crowdstrike_fdr_helpers.py @@ -0,0 +1,61 @@ +def crowdstrike_detection_alert_context(event: dict): + """Returns common context for Crowdstrike detections""" + return { + "aid": get_crowdstrike_field(event, "aid", default=""), + "user": get_crowdstrike_field(event, "UserName", default=""), + "console-link": get_crowdstrike_field(event, "FalconHostLink", default=""), + "commandline": get_crowdstrike_field(event, "CommandLine", default=""), + "parentcommandline": get_crowdstrike_field(event, "ParentCommandLine", default=""), + "filename": get_crowdstrike_field(event, "FileName", default=""), + "filepath": get_crowdstrike_field(event, "FilePath", default=""), + "description": get_crowdstrike_field(event, "DetectDescription", default=""), + "action": get_crowdstrike_field(event, "PatternDispositionDescription", default=""), + } + + +def crowdstrike_process_alert_context(event: dict): + """Returns common process context for Crowdstrike detections""" + return { + "aid": get_crowdstrike_field(event, "aid", default=""), + "CommandLine": get_crowdstrike_field(event, "CommandLine", default=""), + "TargetProcessId": get_crowdstrike_field(event, "TargetProcessId", default=""), + "RawProcessId": get_crowdstrike_field(event, "RawProcessId", default=""), + "ParentBaseFileName": get_crowdstrike_field(event, "ParentBaseFileName", default=""), + "ParentProcessId": get_crowdstrike_field(event, "ParentProcessId", default=""), + "ImageFileName": get_crowdstrike_field(event, "ImageFileName", default=""), + "SHA256Hash": get_crowdstrike_field(event, "SHA256HashData", default=""), + "platform": get_crowdstrike_field(event, "event_platform", default=""), + } + + +def crowdstrike_network_detection_alert_context(event: dict): + """Returns common network context for Crowdstrike detections""" + return { + "LocalAddressIP4": get_crowdstrike_field(event, "LocalAddressIP4", default=""), + "LocalPort": get_crowdstrike_field(event, "LocalPort", default=""), + "RemoteAddressIP4": get_crowdstrike_field(event, "RemoteAddressIP4", default=""), + "RemotePort": get_crowdstrike_field(event, "RemotePort", default=""), + "Protocol": get_crowdstrike_field(event, "Protocol", default=""), + "event_simpleName": get_crowdstrike_field(event, "event_simpleName", default=""), + "aid": get_crowdstrike_field(event, "aid", default=""), + "ContextProcessId": get_crowdstrike_field(event, "ContextProcessId", default=""), + } + + +def filter_crowdstrike_fdr_event_type(event, name: str) -> bool: + """ + Checks if the event belongs to the Crowdstrike.FDREvent log type + and the event type is not the name parameter. + """ + if event.get("p_log_type") != "Crowdstrike.FDREvent": + return False + return event.get("fdr_event_type", "") != name + + +def get_crowdstrike_field(event, field_name, default=None): + return ( + event.deep_get(field_name) + or event.deep_get("event", field_name) + or event.deep_get("unknown_payload", field_name) + or default + ) diff --git a/global_helpers/panther_crowdstrike_fdr_helpers.yml b/global_helpers/panther_crowdstrike_fdr_helpers.yml new file mode 100644 index 000000000..3afaf9035 --- /dev/null +++ b/global_helpers/panther_crowdstrike_fdr_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_crowdstrike_fdr_helpers.py +GlobalID: "panther_crowdstrike_fdr_helpers" +Description: > + Used to define global helpers and variables for CrowdStrike FDR events. diff --git a/global_helpers/panther_default.py b/global_helpers/panther_default.py deleted file mode 100644 index 73b175feb..000000000 --- a/global_helpers/panther_default.py +++ /dev/null @@ -1,127 +0,0 @@ -# Define common code here that all of your policies and rules can share. -# -# Example usage: -# -# import panther_default -# def policy(resource): -# return panther.example_helper() -# - - -import base64 -import binascii -from typing import List - - -def example_helper(): - return True - - -AWS_ACCOUNTS = { - # Add your AWS account IDs/names below: - "123456789012": "sample-account", -} - - -def lookup_aws_account_name(account_id): - """Lookup the AWS account name, return the ID if not found - - Args: - account_id (str): The AWS account ID - - Returns: - str: The name of the AWS account ID - or - str: The AWS account ID (unnamed account) - """ - return AWS_ACCOUNTS.get(account_id, f"{account_id} (unnamed account)") - - -def aws_cloudtrail_success(event): - if event.get("errorCode", "") or event.get("errorMessage", ""): - return False - return True - - -def aws_event_tense(event_name): - """Convert an AWS CloudTrail eventName to be interpolated in alert titles - - An example is passing in StartInstance and returning 'started'. - This would then be used in an alert title such as - 'The EC2 instance my-instance was started'. - - Args: - event_name (str): The CloudTrail eventName - - Returns: - str: A tensed version of the event name - """ - mapping = { - "Create": "created", - "Delete": "deleted", - "Start": "started", - "Stop": "stopped", - "Update": "updated", - } - for event_prefix, tensed in mapping.items(): - if event_name.startswith(event_prefix): - return tensed - # If the event pattern doesn't exist, return original - return event_name - - -# Adapted from https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489 -def aws_key_account_id(aws_key: str): - """retrieve the AWS account ID associated with a given access key ID""" - key_no_prefix = aws_key[4:] # remove the four-character prefix - base32_key = base64.b32decode(key_no_prefix) # remainder of the key is base32-encoded - decoded_key = base32_key[0:6] # retrieve the 10-byte string - - # Convert the 10-byte string to an integer - key_id_int = int.from_bytes(decoded_key, byteorder="big", signed=False) - mask = int.from_bytes(binascii.unhexlify(b"7fffffffff80"), byteorder="big", signed=False) - - # Do a bitwise AND with the mask to retrieve the account ID - # (divide the 10-byte key integer by 128 and remove the fractional part(s)) - account_id = (key_id_int & mask) >> 7 - return str(account_id) - - -def aws_regions() -> List[str]: - """return a list of AWS regions""" - return [ - "ap-east-1", - "ap-northeast-1", - "ap-northeast-1", - "ap-northeast-2", - "ap-northeast-3", - "ap-south-1", - "ap-south-2", - "ap-southeast-1", - "ap-southeast-2", - "ap-southeast-3", - "ap-southeast-4", - "ca-central-1", - "ca-central-1", - "eu-central-1", - "eu-central-1", - "eu-central-2", - "eu-north-1", - "eu-north-1", - "eu-south-1", - "eu-south-2", - "eu-west-1", - "eu-west-1", - "eu-west-2", - "eu-west-2", - "eu-west-3", - "eu-west-3", - "il-central-1", - "me-central-1", - "me-south-1", - "sa-east-1", - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - ] diff --git a/global_helpers/panther_default.yml b/global_helpers/panther_default.yml deleted file mode 100644 index 097395682..000000000 --- a/global_helpers/panther_default.yml +++ /dev/null @@ -1,4 +0,0 @@ -AnalysisType: global -GlobalID: "panther_default" -Filename: panther_default.py -Description: The default global accessible via the Panther web UI. diff --git a/global_helpers/gcp_base_helpers.py b/global_helpers/panther_gcp_helpers.py similarity index 76% rename from global_helpers/gcp_base_helpers.py rename to global_helpers/panther_gcp_helpers.py index 82246a21f..bbc0f2d3b 100644 --- a/global_helpers/gcp_base_helpers.py +++ b/global_helpers/panther_gcp_helpers.py @@ -44,3 +44,22 @@ def gcp_alert_context(event): "resourceName": event.deep_get("protoPayload", "resourceName", default=""), "serviceName": event.deep_get("protoPayload", "serviceName", default=""), } + + +def get_binding_deltas(event): + """A GCP helper function to return the binding deltas from audit events + + Binding deltas provide context on a permission change, including the + action, role, and member associated with the request. + """ + if event.get("protoPayload", {}).get("methodName") != "SetIamPolicy": + return [] + + service_data = event.get("protoPayload", {}).get("serviceData") + if not service_data: + return [] + + binding_deltas = service_data.get("policyDelta", {}).get("bindingDeltas") + if not binding_deltas: + return [] + return binding_deltas diff --git a/global_helpers/gcp_base_helpers.yml b/global_helpers/panther_gcp_helpers.yml similarity index 67% rename from global_helpers/gcp_base_helpers.yml rename to global_helpers/panther_gcp_helpers.yml index 922e39879..d3c852b3c 100644 --- a/global_helpers/gcp_base_helpers.yml +++ b/global_helpers/panther_gcp_helpers.yml @@ -1,5 +1,5 @@ AnalysisType: global -Filename: gcp_base_helpers.py -GlobalID: "gcp_base_helpers" +Filename: panther_gcp_helpers.py +GlobalID: "panther_gcp_helpers" Description: > get_info, get_k8s_info, get_flow_logs_info etc return dicts of the most commonly used fields. diff --git a/global_helpers/panther_github_helpers.py b/global_helpers/panther_github_helpers.py new file mode 100644 index 000000000..555d57da5 --- /dev/null +++ b/global_helpers/panther_github_helpers.py @@ -0,0 +1,9 @@ +def github_alert_context(event): + return { + "action": event.get("action", ""), + "actor": event.get("actor", ""), + "actor_location": event.deep_get("actor_location", "country_code"), + "org": event.get("org", ""), + "repo": event.get("repo", ""), + "user": event.get("user", ""), + } diff --git a/global_helpers/panther_github_helpers.yml b/global_helpers/panther_github_helpers.yml new file mode 100644 index 000000000..f2d891f1e --- /dev/null +++ b/global_helpers/panther_github_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_github_helpers.py +GlobalID: "panther_github_helpers" +Description: > + Used to define global helpers and variables for GitHub events. diff --git a/global_helpers/panther_gsuite_helpers.py b/global_helpers/panther_gsuite_helpers.py new file mode 100644 index 000000000..2e7c69c26 --- /dev/null +++ b/global_helpers/panther_gsuite_helpers.py @@ -0,0 +1,63 @@ +# # # # # # # # # # # # # # +# GSuite Helpers # +# # # # # # # # # # # # # # + +GSUITE_PARAMETER_VALUES = [ + "value", + "intValue", + "boolValue", + "multiValue", + "multiIntValue", + "messageValue", + "multiMessageValue", +] + + +# GSuite parameters are formatted as a list of dictionaries, where each dictionary has a 'name' key +# that maps to the name of the parameter, and one key from GSUITE_PARAMETER_VALUES that maps to the +# value of the parameter. This means to lookup the value of a particular parameter, you must +# traverse the entire list of parameters to find it and then know (or guess) what type of value it +# contains. This helper function handles that for us. +# +# Example parameters list: +# parameters = [ +# { +# "name": "event_id", +# "value": "abc123" +# }, +# { +# "name": "start_time", +# "intValue": 63731901000 +# }, +# { +# "name": "end_time", +# "intValue": 63731903000 +# }, +# { +# "name": "things", +# "multiValue": [ "DRIVE" , "MEME"] +# } +# ] +def gsuite_parameter_lookup(parameters, key): + for param in parameters: + if param["name"] != key: + continue + for value in GSUITE_PARAMETER_VALUES: + if value in param: + return param[value] + return None + return None + + +# GSuite event details are formatted as a list of dictionaries. Each entry has a 'type' and 'name'. +# +# In order to find the event details of interest, you must loop through +# the list searching for a particular type and name. +# +# This helper function handles the looping functionality that is common in many of the gsuite rules +def gsuite_details_lookup(detail_type, detail_names, event): + for details in event.get("events", {}): + if details.get("type") == detail_type and details.get("name") in detail_names: + return details + # not found, return empty dict + return {} diff --git a/global_helpers/panther_gsuite_helpers.yml b/global_helpers/panther_gsuite_helpers.yml new file mode 100644 index 000000000..c5840fba9 --- /dev/null +++ b/global_helpers/panther_gsuite_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_gsuite_helpers.py +GlobalID: "panther_gsuite_helpers" +Description: > + Used to define global helpers and variables for GSuite events. diff --git a/global_helpers/panther_iocs.py b/global_helpers/panther_iocs.py index 1eee37f7e..3dac3fb05 100644 --- a/global_helpers/panther_iocs.py +++ b/global_helpers/panther_iocs.py @@ -538,24 +538,3 @@ "ami-09b81f0f9f2acfcdf", # fedora-coreos-40.20240331.1.0-x86_64 us-west-2 "ami-083bb1ae22e9bf463", # fedora-coreos-40.20240329.10.0-aarch64 us-west-2 } - - -# IOC Helper functions: -def ioc_match(indicators: list, known_iocs: set) -> list: - """Matches a set of indicators against known Indicators of Compromise - - :param indicators: List of potential indicators of compromise - :param known_iocs: Set of known indicators of compromise - :return: List of any indicator matches - """ - # Check through the IP IOCs - return [ioc for ioc in (indicators or []) if ioc in known_iocs] - - -def sanitize_domain(domain: str) -> str: - """Makes a potential malicious domain not render as a domain in most systems - - :param domain: Original domain - :return: Sanitized domain - """ - return domain.replace(".", "[.]") diff --git a/global_helpers/panther_ipinfo_helpers.py b/global_helpers/panther_ipinfo_helpers.py index 9aa7d01f4..84235d27e 100644 --- a/global_helpers/panther_ipinfo_helpers.py +++ b/global_helpers/panther_ipinfo_helpers.py @@ -1,3 +1,4 @@ +from math import atan2, cos, radians, sin, sqrt from typing import Union from panther_base_helpers import deep_get @@ -177,3 +178,43 @@ def geoinfo_from_ip(event, match_field: str): "postal": location.postal_code(match_field), "timezone": location.timezone(match_field), } + + +def geoinfo_from_ip_formatted(event, match_field: str) -> str: + """Formatting wrapper for geoinfo_from_ip for use in human-readable text""" + geoinfo = geoinfo_from_ip(event, match_field) + return ( + f"{geoinfo.get('ip')} in {geoinfo.get('city')}, " + f"{geoinfo.get('region')} in {geoinfo.get('country')}" + ) + + +def km_between_ipinfo_loc(ipinfo_loc_one: dict, ipinfo_loc_two: dict): + """ + compute the number of kilometers between two ipinfo_location enrichments + This uses a haversine computation which is imperfect and holds the benefit + of being supportable via stdlib. At polar opposites, haversine might be + 0.3-0.5% off + See also https://en.wikipedia.org/wiki/Haversine_formula + See also https://stackoverflow.com/a/19412565 + See also https://www.sunearthtools.com/tools/distance.php + """ + if not set({"lat", "lng"}).issubset(set(ipinfo_loc_one.keys())): + # input ipinfo_loc_one doesn't have lat and lng keys + return None + if not set({"lat", "lng"}).issubset(set(ipinfo_loc_two.keys())): + # input ipinfo_loc_two doesn't have lat and lng keys + return None + lat_1 = radians(float(ipinfo_loc_one.get("lat"))) + lng_1 = radians(float(ipinfo_loc_one.get("lng"))) + lat_2 = radians(float(ipinfo_loc_two.get("lat"))) + lng_2 = radians(float(ipinfo_loc_two.get("lng"))) + # radius of the earth in kms + radius = 6372.795477598 + lng_diff = lng_2 - lng_1 + lat_diff = lat_2 - lat_1 + + step_1 = sin(lat_diff / 2) ** 2 + cos(lat_1) * cos(lat_2) * sin(lng_diff / 2) ** 2 + step_2 = 2 * atan2(sqrt(step_1), sqrt(1 - step_1)) + distance = radius * step_2 + return distance diff --git a/global_helpers/panther_msft_helpers.py b/global_helpers/panther_msft_helpers.py new file mode 100644 index 000000000..620f453c4 --- /dev/null +++ b/global_helpers/panther_msft_helpers.py @@ -0,0 +1,20 @@ +def msft_graph_alert_context(event): + return { + "category": event.get("category", ""), + "description": event.get("description", ""), + "userStates": event.get("userStates", []), + "fileStates": event.get("fileStates", []), + "hostStates": event.get("hostStates", []), + } + + +def m365_alert_context(event): + return { + "operation": event.get("Operation", ""), + "organization_id": event.get("OrganizationId", ""), + "client_ip": event.get("ClientIp", ""), + "extended_properties": event.get("ExtendedProperties", []), + "modified_properties": event.get("ModifiedProperties", []), + "application": event.get("Application", ""), + "actor": event.get("Actor", []), + } diff --git a/global_helpers/panther_msft_helpers.yml b/global_helpers/panther_msft_helpers.yml new file mode 100644 index 000000000..82812c934 --- /dev/null +++ b/global_helpers/panther_msft_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_msft_helpers.py +GlobalID: "panther_msft_helpers" +Description: > + Used to define global helpers and variables for MSFT events. diff --git a/global_helpers/panther_okta_helpers.py b/global_helpers/panther_okta_helpers.py new file mode 100644 index 000000000..b8cf79cbe --- /dev/null +++ b/global_helpers/panther_okta_helpers.py @@ -0,0 +1,15 @@ +def okta_alert_context(event: dict): + """Returns common context for automation of Okta alerts""" + return { + "event_type": event.get("eventtype", ""), + "severity": event.get("severity", ""), + "actor": event.get("actor", {}), + "client": event.get("client", {}), + "request": event.get("request", {}), + "outcome": event.get("outcome", {}), + "target": event.get("target", []), + "debug_context": event.get("debugcontext", {}), + "authentication_context": event.get("authenticationcontext", {}), + "security_context": event.get("securitycontext", {}), + "ips": event.get("p_any_ip_addresses", []), + } diff --git a/global_helpers/panther_okta_helpers.yml b/global_helpers/panther_okta_helpers.yml new file mode 100644 index 000000000..7b461bce3 --- /dev/null +++ b/global_helpers/panther_okta_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_okta_helpers.py +GlobalID: "panther_okta_helpers" +Description: > + Used to define global helpers and variables for Okta events. diff --git a/global_helpers/panther_oss_helpers.py b/global_helpers/panther_oss_helpers.py deleted file mode 100644 index 6dc401b6a..000000000 --- a/global_helpers/panther_oss_helpers.py +++ /dev/null @@ -1,366 +0,0 @@ -"""Utility functions provided to policies and rules during execution.""" - -import json -import os -import re -from datetime import datetime -from ipaddress import ip_address -from math import atan2, cos, radians, sin, sqrt -from typing import Any, Dict, Optional, Sequence, Set, Union - -import boto3 -import requests -from dateutil import parser -from panther_detection_helpers import caching - -_RESOURCE_TABLE = None # boto3.Table resource, lazily constructed -FIPS_ENABLED = os.getenv("ENABLE_FIPS", "").lower() == "true" -FIPS_SUFFIX = "-fips." + os.getenv("AWS_REGION", "") + ".amazonaws.com" - -# Auto Time Resolution Parameters -EPOCH_REGEX = r"([0-9]{9,12}(\.\d+)?)" -TIME_FORMATS = [ - "%Y-%m-%d %H:%M:%S", # Panther p_event_time Timestamp - "%Y-%m-%dT%H:%M:%SZ", # AWS Timestamp - "%Y-%m-%dT%H:%M:%S.%fZ", # Panther Timestamp - "%Y-%m-%dT%H:%M:%S*%f%z", - "%Y %b %d %H:%M:%S.%f %Z", - "%b %d %H:%M:%S %z %Y", - "%d/%b/%Y:%H:%M:%S %z", - "%b %d, %Y %I:%M:%S %p", - "%b %d %Y %H:%M:%S", - "%b %d %H:%M:%S %Y", - "%b %d %H:%M:%S %z", - "%b %d %H:%M:%S", - "%Y-%m-%dT%H:%M:%S%z", - "%Y-%m-%dT%H:%M:%S.%f%z", - "%Y-%m-%d %H:%M:%S %z", - "%Y-%m-%d %H:%M:%S%z", - "%Y-%m-%d %H:%M:%S,%f", - "%Y/%m/%d*%H:%M:%S", - "%Y %b %d %H:%M:%S.%f*%Z", - "%Y %b %d %H:%M:%S.%f", - "%Y-%m-%d %H:%M:%S,%f%z", - "%Y-%m-%d %H:%M:%S.%f", - "%Y-%m-%d %H:%M:%S.%f%z", - "%Y-%m-%dT%H:%M:%S.%f", - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%dT%H:%M:%S%z", - "%Y-%m-%dT%H:%M:%S.%f", - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%d*%H:%M:%S:%f", - "%Y-%m-%d*%H:%M:%S", - "%y-%m-%d %H:%M:%S,%f %z", - "%y-%m-%d %H:%M:%S,%f", - "%y-%m-%d %H:%M:%S", - "%y/%m/%d %H:%M:%S", - "%y%m%d %H:%M:%S", - "%Y%m%d %H:%M:%S.%f", - "%m/%d/%y*%H:%M:%S", - "%m/%d/%Y*%H:%M:%S", - "%m/%d/%Y*%H:%M:%S*%f", - "%m/%d/%y %H:%M:%S %z", - "%m/%d/%Y %H:%M:%S %z", - "%H:%M:%S", - "%H:%M:%S.%f", - "%H:%M:%S,%f", - "%d/%b %H:%M:%S,%f", - "%d/%b/%Y:%H:%M:%S", - "%d/%b/%Y %H:%M:%S", - "%d-%b-%Y %H:%M:%S", - "%d-%b-%Y %H:%M:%S.%f", - "%d %b %Y %H:%M:%S", - "%d %b %Y %H:%M:%S*%f", - "%m%d_%H:%M:%S", - "%m%d_%H:%M:%S.%f", - "%m/%d/%Y %I:%M:%S %p:%f", - "%m/%d/%Y %I:%M:%S %p", -] - - -class BadLookup(Exception): - """Error returned when a resource lookup fails.""" - - -class PantherBadInput(Exception): - """Error returned when a Panther helper function is provided bad input.""" - - -def resolve_timestamp_string(timestamp: str) -> Optional[datetime]: - """Auto Time Resolution""" - if not timestamp: - return None - - # Removes weird single-quotes used in some timestamp formats - ts_format = timestamp.replace("'", "") - # Attempt to resolve timestamp format - for each_format in TIME_FORMATS: - try: - return datetime.strptime(ts_format, each_format) - except (ValueError, TypeError): - continue - try: - return parser.parse(timestamp) - except (ValueError, TypeError, parser.ParserError): - pass - - # Attempt to resolve epoch format - # Since datetime.utcfromtimestamp supports 9 through 12 digit epoch timestamps - # and we only want the first 12 digits. - match = re.match(EPOCH_REGEX, timestamp) - if match.group(0) != "": - try: - return datetime.utcfromtimestamp(float(match.group(0))) - except (ValueError, TypeError): - return None - return None - - -def get_s3_arn_by_name(name: str) -> str: - """This function is used to construct an s3 bucket ARN from its name.""" - if name == "": - raise PantherBadInput("s3 name cannot be blank") - return "arn:aws:s3:::" + name - - -def s3_lookup_by_name(name: str) -> Dict[str, Any]: - """This function is used to get an S3 bucket resource from just its name.""" - return resource_lookup(get_s3_arn_by_name(name)) - - -def resource_table() -> boto3.resource: - """Lazily build resource table""" - # pylint: disable=global-statement - global _RESOURCE_TABLE - if not _RESOURCE_TABLE: - # pylint: disable=no-member - _RESOURCE_TABLE = boto3.resource( - "dynamodb", - endpoint_url="https://dynamodb" + FIPS_SUFFIX if FIPS_ENABLED else None, - ).Table("panther-resources") - return _RESOURCE_TABLE - - -def resource_lookup(resource_id: str) -> Dict[str, Any]: - """This function is used to get a resource from the resources-api based on its resourceID.""" - # Validate input so we can provide meaningful error messages to users - if resource_id == "": - raise PantherBadInput("resourceId cannot be blank") - - # Get the item from dynamo - response = resource_table().get_item(Key={"id": resource_id}) - - # Check if dynamo failed - status_code = response["ResponseMetadata"]["HTTPStatusCode"] - if status_code != 200: - raise BadLookup("dynamodb - " + str(status_code) + " HTTPStatusCode") - - # Check if the item was found - if "Item" not in response: - raise BadLookup(resource_id + " not found") - - # Return just the attributes of the item - return response["Item"]["attributes"] - - -def ttl_expired(response: dict) -> bool: - """Global `ttl_expired` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import ttl_expired`.""" - return caching.ttl_expired(response) - - -def get_counter(key: str, force_ttl_check: bool = False) -> int: - """Global `get_counter` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import get_counter`.""" - return caching.get_counter(key=key, force_ttl_check=force_ttl_check) - - -def increment_counter(key: str, val: int = 1) -> int: - """Global `increment_counter` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import increment_counter`.""" - return caching.increment_counter(key=key, val=val) - - -def reset_counter(key: str) -> None: - """Global `reset_counter` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import reset_counter`.""" - return caching.reset_counter(key=key) - - -def set_key_expiration(key: str, epoch_seconds: int) -> None: - """Global `set_key_expiration` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import set_key_expiration`.""" - return caching.set_key_expiration(key=key, epoch_seconds=epoch_seconds) - - -def put_dictionary(key: str, val: dict, epoch_seconds: int = None): - """Global `put_dictionary` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import put_dictionary`.""" - return caching.put_dictionary(key=key, val=val, epoch_seconds=epoch_seconds) - - -def get_dictionary(key: str, force_ttl_check: bool = False) -> dict: - """Global `get_dictionary` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import get_dictionary`.""" - return caching.get_dictionary(key=key, force_ttl_check=force_ttl_check) - - -def get_string_set(key: str, force_ttl_check: bool = False) -> Set[str]: - """Global `get_string_set` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import get_string_set`.""" - return caching.get_string_set(key=key, force_ttl_check=force_ttl_check) - - -def put_string_set(key: str, val: Sequence[str], epoch_seconds: int = None) -> None: - """Global `put_string_set` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import put_string_set`.""" - return caching.put_string_set(key=key, val=val, epoch_seconds=epoch_seconds) - - -def add_to_string_set(key: str, val: Union[str, Sequence[str]]) -> Set[str]: - """Global `add_to_string_set` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import add_to_string_set`.""" - return caching.add_to_string_set(key=key, val=val) - - -def remove_from_string_set(key: str, val: Union[str, Sequence[str]]) -> Set[str]: - """Global `remove_from_string_set` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import remove_from_string_set`.""" - return caching.remove_from_string_set(key=key, val=val) - - -def reset_string_set(key: str) -> None: - """Global `reset_string_set` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import reset_string_set`.""" - return caching.reset_string_set(key=key) - - -def evaluate_threshold(key: str, threshold: int = 10, expiry_seconds: int = 3600) -> bool: - """Global `evaluate_threshold` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import evaluate_threshold`.""" - return caching.evaluate_threshold(key=key, threshold=threshold, expiry_seconds=expiry_seconds) - - -def check_account_age(key): - """Global `check_account_age` is DEPRECATED. - Instead, use `from panther_detection_helpers.caching import check_account_age`.""" - return caching.check_account_age(key=key) - - -def km_between_ipinfo_loc(ipinfo_loc_one: dict, ipinfo_loc_two: dict): - """ - compute the number of kilometers between two ipinfo_location enrichments - This uses a haversine computation which is imperfect and holds the benefit - of being supportable via stdlib. At polar opposites, haversine might be - 0.3-0.5% off - See also https://en.wikipedia.org/wiki/Haversine_formula - See also https://stackoverflow.com/a/19412565 - See also https://www.sunearthtools.com/tools/distance.php - """ - if not set({"lat", "lng"}).issubset(set(ipinfo_loc_one.keys())): - # input ipinfo_loc_one doesn't have lat and lng keys - return None - if not set({"lat", "lng"}).issubset(set(ipinfo_loc_two.keys())): - # input ipinfo_loc_two doesn't have lat and lng keys - return None - lat_1 = radians(float(ipinfo_loc_one.get("lat"))) - lng_1 = radians(float(ipinfo_loc_one.get("lng"))) - lat_2 = radians(float(ipinfo_loc_two.get("lat"))) - lng_2 = radians(float(ipinfo_loc_two.get("lng"))) - # radius of the earth in kms - radius = 6372.795477598 - lng_diff = lng_2 - lng_1 - lat_diff = lat_2 - lat_1 - - step_1 = sin(lat_diff / 2) ** 2 + cos(lat_1) * cos(lat_2) * sin(lng_diff / 2) ** 2 - step_2 = 2 * atan2(sqrt(step_1), sqrt(1 - step_1)) - distance = radius * step_2 - return distance - - -def geoinfo_from_ip(ip: str) -> dict: # pylint: disable=invalid-name - """Looks up the geolocation of an IP address using ipinfo.io - - Example ipinfo output: - { - "ip": "1.1.1.1", - "hostname": "one.one.one.one", - "anycast": true, - "city": "Miami", - "region": "Florida", - "country": "US", - "loc": "25.7867,-80.1800", - "org": "AS13335 Cloudflare, Inc.", - "postal": "33132", - "timezone": "America/New_York", - "readme": "https://ipinfo.io/missingauth" - } - """ - - valid_ip = ip_address(ip) - url = f"https://ipinfo.io/{valid_ip}/json" - resp = requests.get(url, timeout=5) - if resp.status_code != 200: - # pylint: disable=broad-exception-raised - raise Exception(f"Geo lookup failed: GET {url} returned {resp.status_code}") - geoinfo = json.loads(resp.text) - return geoinfo - - -def geoinfo_from_ip_formatted(ip: str) -> str: # pylint: disable=invalid-name - """Formatting wrapper for geoinfo_from_ip for use in human-readable text""" - geoinfo = geoinfo_from_ip(ip) - return ( - f"{geoinfo.get('ip')} in {geoinfo.get('city')}, " - f"{geoinfo.get('region')} in {geoinfo.get('country')}" - ) - - -# returns the difference between time1 and later time 2 in human-readable time period string -def time_delta(time1, time2: str) -> str: - time1_truncated = nano_to_micro(time1) - time2_truncated = nano_to_micro(time2) - delta_timedelta = resolve_timestamp_string(time2_truncated) - resolve_timestamp_string( - time1_truncated - ) - days = delta_timedelta.days - hours, remainder = divmod(delta_timedelta.seconds, 3600) - minutes, seconds = divmod(remainder, 60) - delta = "" - if days > 0: - delta = f"{days} day(s) " - if hours > 0: - delta = "".join([delta, f"{hours} hour(s) "]) - if minutes > 0: - delta = "".join([delta, f"{minutes} minute(s) "]) - if seconds > 0: - delta = "".join([delta, f"{seconds} second(s)"]) - return delta - - -def nano_to_micro(time_str: str) -> str: - parts = time_str.split(":") - # pylint: disable=consider-using-f-string - parts[-1] = "{:06f}".format(float(parts[-1])) - return ":".join(parts) - - -# adds parsing delay to an alert_context -def add_parse_delay(event, context: dict) -> dict: - parsing_delay = time_delta(event.get("p_event_time"), event.get("p_parse_time")) - context["parseDelay"] = f"{parsing_delay}" - return context - - -# When a single item is loaded from json, it is loaded as a single item -# When a list of items is loaded from json, it is loaded as a list of that item -# When we want to iterate over something that could be a single item or a list -# of items we can use listify and just continue as if it's always a list -def listify(maybe_list): - try: - iter(maybe_list) - except TypeError: - # not a list - return [maybe_list] - # either a list or string - return [maybe_list] if isinstance(maybe_list, (str, bytes, dict)) else maybe_list diff --git a/global_helpers/panther_oss_helpers.yml b/global_helpers/panther_oss_helpers.yml deleted file mode 100644 index 917fe319a..000000000 --- a/global_helpers/panther_oss_helpers.yml +++ /dev/null @@ -1,4 +0,0 @@ -AnalysisType: global -GlobalID: "panther_oss_helpers" -Filename: panther_oss_helpers.py -Description: Used to define global helpers and variables. diff --git a/global_helpers/panther_slack_helpers.py b/global_helpers/panther_slack_helpers.py new file mode 100644 index 000000000..bd2cf5338 --- /dev/null +++ b/global_helpers/panther_slack_helpers.py @@ -0,0 +1,7 @@ +def slack_alert_context(event): + return { + "actor-name": event.deep_get("actor", "user", "name", default=""), + "actor-email": event.deep_get("actor", "user", "email", default=""), + "actor-ip": event.deep_get("context", "ip_address", default=""), + "user-agent": event.deep_get("context", "ua", default=""), + } diff --git a/global_helpers/panther_slack_helpers.yml b/global_helpers/panther_slack_helpers.yml new file mode 100644 index 000000000..fed6386f0 --- /dev/null +++ b/global_helpers/panther_slack_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_slack_helpers.py +GlobalID: "panther_slack_helpers" +Description: > + Used to define global helpers and variables for Slack events. diff --git a/global_helpers/panther_zendesk_helpers.py b/global_helpers/panther_zendesk_helpers.py new file mode 100644 index 000000000..eac9e570d --- /dev/null +++ b/global_helpers/panther_zendesk_helpers.py @@ -0,0 +1,40 @@ +# # # # # # # # # # # # # # +# Zendesk Helpers # +# # # # # # # # # # # # # # + +import re + +# key names +ZENDESK_CHANGE_DESCRIPTION = "change_description" +ZENDESK_APP_ROLE_ASSIGNED = re.compile( + r"(?P.*) role changed from (?P.+) to (?P.*)", re.IGNORECASE +) +ZENDESK_ROLE_ASSIGNED = re.compile( + r"Role changed from (?P.+) to (?P[^$]+)", re.IGNORECASE +) + + +def zendesk_get_roles(event): + old_role = "" + new_role = "" + role_change = event.get(ZENDESK_CHANGE_DESCRIPTION, "") + if "\n" in role_change: + for app_change in role_change.split("\n"): + matches = ZENDESK_APP_ROLE_ASSIGNED.match(app_change) + if matches: + if old_role: + old_role += " ; " + old_role += matches.group("app") + ":" + matches.group("old_role") + if new_role: + new_role += " ; " + new_role += matches.group("app") + ":" + matches.group("new_role") + else: + matches = ZENDESK_ROLE_ASSIGNED.match(role_change) + if matches: + old_role = matches.group("old_role") + new_role = matches.group("new_role") + if not old_role: + old_role = ":" + if not new_role: + new_role = ":" + return old_role, new_role diff --git a/global_helpers/panther_zendesk_helpers.yml b/global_helpers/panther_zendesk_helpers.yml new file mode 100644 index 000000000..9d2099cd4 --- /dev/null +++ b/global_helpers/panther_zendesk_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_zendesk_helpers.py +GlobalID: "panther_zendesk_helpers" +Description: > + Used to define global helpers and variables for ZenDesk events. diff --git a/packs/asana.yml b/packs/asana.yml index e3ed3ccce..b697679df 100644 --- a/packs/asana.yml +++ b/packs/asana.yml @@ -16,10 +16,8 @@ PackDefinition: - Asana.Workspace.New.Admin # Globals used in these detections - panther_asana_helpers - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + - panther_event_type_helpers # Data Model - Standard.Asana.Audit diff --git a/packs/atlassian.yml b/packs/atlassian.yml index a729c0f30..ccb198074 100644 --- a/packs/atlassian.yml +++ b/packs/atlassian.yml @@ -5,10 +5,8 @@ PackDefinition: IDs: - Atlassian.User.LoggedInAsUser # Globals used in these detections - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + - panther_event_type_helpers # Data Model - Standard.Atlassian.Audit diff --git a/packs/auth0.yml b/packs/auth0.yml index a48fc108d..a2b2cd343 100644 --- a/packs/auth0.yml +++ b/packs/auth0.yml @@ -18,7 +18,5 @@ PackDefinition: - panther_base_helpers - panther_auth0_helpers - global_filter_auth0 - - panther_config - - panther_config_defaults - - panther_config_overrides + DisplayName: "Panther Auth0 Pack" diff --git a/packs/aws.yml b/packs/aws.yml index 4415e4da9..d224547cc 100644 --- a/packs/aws.yml +++ b/packs/aws.yml @@ -190,12 +190,11 @@ PackDefinition: - Standard.OCSF.NetworkActivity - Standard.OCSF.DnsActivity # Globals used in these rules/policies + - panther_aws_helpers - panther_base_helpers - panther_config - panther_config_defaults - panther_config_overrides - - panther_default - - panther_event_type_helpers - panther_iocs + - panther_ipinfo_helpers - panther_lookuptable_helpers - - panther_oss_helpers diff --git a/packs/aws_cis.yml b/packs/aws_cis.yml index abb278a91..98fd8cee6 100644 --- a/packs/aws_cis.yml +++ b/packs/aws_cis.yml @@ -37,7 +37,7 @@ PackDefinition: - AWS.WAF.HasXSSPredicate # Globals used in these detections - panther_base_helpers - - panther_oss_helpers + - panther_aws_helpers - panther_config - panther_config_defaults - panther_config_overrides diff --git a/packs/azure_signin.yml b/packs/azure_signin.yml index 82feb941f..25cbb9e89 100644 --- a/packs/azure_signin.yml +++ b/packs/azure_signin.yml @@ -9,10 +9,8 @@ PackDefinition: # Globals used in these detections - global_filter_azuresignin - panther_azuresignin_helpers - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + - panther_event_type_helpers # Data Models - Standard.Azure.Audit.SignIn diff --git a/packs/box.yml b/packs/box.yml index b92654f19..d58b9da45 100644 --- a/packs/box.yml +++ b/packs/box.yml @@ -15,9 +15,6 @@ PackDefinition: # Globals used in these detections - panther_base_helpers - panther_box_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides - panther_event_type_helpers # Data Models - Standard.Box.Event diff --git a/packs/cloudflare.yml b/packs/cloudflare.yml index 5aa901364..200b65ef3 100644 --- a/packs/cloudflare.yml +++ b/packs/cloudflare.yml @@ -11,9 +11,6 @@ PackDefinition: - panther_cloudflare_helpers - panther_lookuptable_helpers - global_filter_cloudflare - - panther_config - - panther_config_defaults - - panther_config_overrides # Data Models - Standard.Cloudflare.Firewall - Standard.Cloudflare.HttpReq diff --git a/packs/credential_security.yml b/packs/credential_security.yml index c11e46c5d..457f0bf97 100644 --- a/packs/credential_security.yml +++ b/packs/credential_security.yml @@ -20,11 +20,15 @@ PackDefinition: - global_filter_github - panther_auth0_helpers - panther_base_helpers - - panther_default + - panther_aws_helpers - panther_event_type_helpers - panther_config - panther_config_defaults - panther_config_overrides + - panther_crowdstrike_fdr_helpers + - panther_msft_helpers + - panther_okta_helpers + - panther_slack_helpers # Rules - AWS.CloudTrail.RootPasswordChanged - AWS.IAM.AccessKeyCompromised diff --git a/packs/crowdstrike.yml b/packs/crowdstrike.yml index 6d90ef2f9..bcec0d772 100644 --- a/packs/crowdstrike.yml +++ b/packs/crowdstrike.yml @@ -20,9 +20,7 @@ PackDefinition: - Crowdstrike.Macos.Osascript.Administrator # Globals used in these detections - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + - panther_crowdstrike_fdr_helpers # Data models - Standard.AWS.VPCDns - Standard.CiscoUmbrella.DNS diff --git a/packs/crowdstrike_event_streams.yml b/packs/crowdstrike_event_streams.yml index cd22520f4..6cf46dbbf 100644 --- a/packs/crowdstrike_event_streams.yml +++ b/packs/crowdstrike_event_streams.yml @@ -3,12 +3,8 @@ PackID: PantherManaged.CrowdstrikeEventStreams Description: Group of all Crowdstrike Event Stream detections PackDefinition: IDs: - - crowdstrike_event_streams_helpers + - panther_crowdstrike_event_streams_helpers - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides - - Crowdstrike.AdminRoleAssigned - Crowdstrike.AllowlistRemoved - Crowdstrike.API.Key.Created diff --git a/packs/dropbox.yml b/packs/dropbox.yml index 3ad8e3845..b78084b70 100644 --- a/packs/dropbox.yml +++ b/packs/dropbox.yml @@ -10,7 +10,7 @@ PackDefinition: - Dropbox.User.Disabled.2FA - Dropbox.Admin.sign.in.as.Session # Globals used in these detections - - panther_base_helpers + - panther_config - panther_config_defaults - panther_config_overrides diff --git a/packs/duo.yml b/packs/duo.yml index aaa99e3a3..187841d2f 100644 --- a/packs/duo.yml +++ b/packs/duo.yml @@ -20,8 +20,6 @@ PackDefinition: - Duo.Admin.SSO.SAML.Requirement.Disabled - Duo.Admin.User.MFA.Bypass.Enabled # Globals used in these detections - - panther_base_helpers + - panther_duo_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/gcp_audit.yml b/packs/gcp_audit.yml index 17fc09f05..48cd70c7e 100644 --- a/packs/gcp_audit.yml +++ b/packs/gcp_audit.yml @@ -48,11 +48,7 @@ PackDefinition: # Data model - Standard.GCP.AuditLog # Globals used in these rules/policies - - gcp_base_helpers - - gcp_environment + - panther_gcp_helpers - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides - panther_event_type_helpers DisplayName: "Panther GCP Audit Pack" diff --git a/packs/gcp_k8.yml b/packs/gcp_k8.yml index 77db82283..366e22019 100644 --- a/packs/gcp_k8.yml +++ b/packs/gcp_k8.yml @@ -15,8 +15,5 @@ PackDefinition: - GCP.K8s.Pod.Attached.To.Node.Host.Network - GCP.K8s.Pod.Using.Host.PID.Namespace # Globals - - gcp_base_helpers + - panther_gcp_helpers - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides \ No newline at end of file diff --git a/packs/github.yml b/packs/github.yml index c3582c652..65e313afb 100644 --- a/packs/github.yml +++ b/packs/github.yml @@ -29,10 +29,7 @@ PackDefinition: # Data model - Standard.Github.Audit # Globals - - panther_base_helpers + - panther_github_helpers - panther_event_type_helpers - - panther_oss_helpers - global_filter_github - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/gsuite_reports.yml b/packs/gsuite_reports.yml index 3ecd1f099..ed62291ec 100644 --- a/packs/gsuite_reports.yml +++ b/packs/gsuite_reports.yml @@ -36,9 +36,7 @@ PackDefinition: - Standard.GSuite.Reports # Globals used in these detections - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + - panther_gsuite_helpers - panther_event_type_helpers - panther_lookuptable_helpers # Queries diff --git a/packs/ipinfo.yml b/packs/ipinfo.yml index 771b49594..007f67c76 100644 --- a/packs/ipinfo.yml +++ b/packs/ipinfo.yml @@ -12,7 +12,5 @@ PackDefinition: - panther_base_helpers - panther_ipinfo_helpers - panther_lookuptable_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + DisplayName: "IPInfo" diff --git a/packs/mongodb.yml b/packs/mongodb.yml index 9dca2905a..6ef82e8aa 100644 --- a/packs/mongodb.yml +++ b/packs/mongodb.yml @@ -15,8 +15,6 @@ PackDefinition: - MongoDB.External.UserInvited.NoConfig - MongoDB.Logging.Toggled # Globals - - panther_base_helpers + - panther_mongodb_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/msft_graph.yml b/packs/msft_graph.yml index 231abe46c..a669f0d3f 100644 --- a/packs/msft_graph.yml +++ b/packs/msft_graph.yml @@ -9,7 +9,7 @@ PackDefinition: - Microsoft365.MFA.Disabled - Microsoft365.Exchange.External.Forwarding # Globals - - panther_base_helpers + - panther_msft_helpers - panther_config - panther_config_defaults - panther_config_overrides diff --git a/packs/multisource_correlations.yml b/packs/multisource_correlations.yml index 0bc0dacf0..b3d5cde96 100644 --- a/packs/multisource_correlations.yml +++ b/packs/multisource_correlations.yml @@ -27,8 +27,6 @@ PackDefinition: # Global Helpers - global_filter_github - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + - panther_event_type_helpers \ No newline at end of file diff --git a/packs/notion.yml b/packs/notion.yml index 6af944201..bf4db23f8 100644 --- a/packs/notion.yml +++ b/packs/notion.yml @@ -24,14 +24,10 @@ PackDefinition: # Globals used in these detections - global_filter_notion - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides - panther_event_type_helpers - panther_ipinfo_helpers - panther_lookuptable_helpers - panther_notion_helpers - - panther_oss_helpers # Data Model - Standard.Notion.AuditLogs DisplayName: "Panther Notion Pack" diff --git a/packs/okta.yml b/packs/okta.yml index 721176782..a1584be4e 100644 --- a/packs/okta.yml +++ b/packs/okta.yml @@ -29,11 +29,8 @@ PackDefinition: - Okta.Support.Reset # Globals used in these detections - panther_base_helpers - - panther_oss_helpers - panther_event_type_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + - panther_okta_helpers # Data Model - Standard.Okta.SystemLog DisplayName: "Panther Okta Pack" diff --git a/packs/onelogin.yml b/packs/onelogin.yml index 7fc9f0c64..cff035e39 100644 --- a/packs/onelogin.yml +++ b/packs/onelogin.yml @@ -19,10 +19,6 @@ PackDefinition: - OneLogin.Login # Globals used in these detections - panther_base_helpers - - panther_oss_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides - panther_event_type_helpers # Data Model - Standard.OneLogin.Events diff --git a/packs/onepassword.yml b/packs/onepassword.yml index 8ea7183df..73c54a824 100644 --- a/packs/onepassword.yml +++ b/packs/onepassword.yml @@ -9,8 +9,6 @@ PackDefinition: # 1Password Specific Rules - OnePassword.Unusual.Client # Supporting Global Helpers - - panther_base_helpers + - panther_event_type_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/osquery.yml b/packs/osquery.yml index b4c2aca11..5ccd65e09 100644 --- a/packs/osquery.yml +++ b/packs/osquery.yml @@ -16,8 +16,6 @@ PackDefinition: - Osquery.SSHListener - Osquery.SuspiciousCron # Globals used in these detections - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + DisplayName: "Panther OSQuery Pack" diff --git a/packs/panther.yml b/packs/panther.yml index 678ea2ad0..bebabd803 100644 --- a/packs/panther.yml +++ b/packs/panther.yml @@ -10,9 +10,7 @@ PackDefinition: # Data Model - Standard.Panther.Audit # Helpers - - panther_base_helpers + - panther_event_type_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + DisplayName: "Panther Audit Logs Pack" diff --git a/packs/sentinelone.yml b/packs/sentinelone.yml index 2824e6d8e..9f4ac105e 100644 --- a/packs/sentinelone.yml +++ b/packs/sentinelone.yml @@ -6,8 +6,6 @@ PackDefinition: - SentinelOne.Alert.Passthrough - SentinelOne.Threats # Globals used in these detections - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + DisplayName: "Panther SentinelOne Pack" diff --git a/packs/slack.yml b/packs/slack.yml index 2437cceb5..bdb95d21a 100644 --- a/packs/slack.yml +++ b/packs/slack.yml @@ -27,10 +27,6 @@ PackDefinition: - Slack.AuditLogs.UserPrivilegeEscalation - Slack.AuditLogs.UserPrivilegeChangedToUser # Globals used in these rules/policies - - panther_base_helpers - - panther_oss_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + - panther_slack_helpers # Data Model - Standard.Slack.AuditLogs diff --git a/packs/snyk.yml b/packs/snyk.yml index 68466acae..9cf32cf34 100644 --- a/packs/snyk.yml +++ b/packs/snyk.yml @@ -16,8 +16,6 @@ PackDefinition: - Snyk.User.Management # Globals - global_filter_snyk - - panther_base_helpers + - panther_snyk_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/standard_ruleset.yml b/packs/standard_ruleset.yml index 92f7f84b3..fa02d2b5b 100644 --- a/packs/standard_ruleset.yml +++ b/packs/standard_ruleset.yml @@ -28,11 +28,10 @@ PackDefinition: - Standard.NewUserAccountCreated # Global Helpers - panther_base_helpers - - panther_default + - panther_aws_helpers - panther_config - panther_config_defaults - panther_config_overrides - panther_event_type_helpers - panther_ipinfo_helpers - panther_lookuptable_helpers - - panther_oss_helpers diff --git a/packs/tailscale.yml b/packs/tailscale.yml index 1eb976890..75ed1203a 100644 --- a/packs/tailscale.yml +++ b/packs/tailscale.yml @@ -7,10 +7,8 @@ PackDefinition: - Tailscale.HTTPS.Disabled - Tailscale.Machine.Approval.Requirements.Disabled # Globals used in these detections - - panther_base_helpers + - panther_tailscale_helpers - global_filter_tailscale - - panther_config - - panther_config_defaults - - panther_config_overrides + DisplayName: "Panther Tailscale Pack" diff --git a/packs/tines.yml b/packs/tines.yml index 59fb15934..58a02be59 100644 --- a/packs/tines.yml +++ b/packs/tines.yml @@ -15,8 +15,6 @@ PackDefinition: - Tines.Actions.DisabledChanges # Globals - global_filter_tines - - panther_base_helpers + - panther_tines_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/tor.yml b/packs/tor.yml index c4360bcad..5c9b444c2 100644 --- a/packs/tor.yml +++ b/packs/tor.yml @@ -7,7 +7,4 @@ PackDefinition: - panther_base_helpers - panther_lookuptable_helpers - panther_tor_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides DisplayName: "Tor Lookup Tables" diff --git a/packs/wiz.yml b/packs/wiz.yml index ff87a58e4..937befca2 100644 --- a/packs/wiz.yml +++ b/packs/wiz.yml @@ -23,6 +23,4 @@ PackDefinition: - Wiz.CICD.Scan.Policy.Updated.Or.Deleted - panther_wiz_helpers - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/zendesk.yml b/packs/zendesk.yml index 3476be440..ac1f7e371 100644 --- a/packs/zendesk.yml +++ b/packs/zendesk.yml @@ -14,8 +14,6 @@ PackDefinition: # Data model - Standard.Zendesk.AuditLog # Globals - - panther_base_helpers + - panther_zendesk_helpers - panther_event_type_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + diff --git a/packs/zoom.yml b/packs/zoom.yml index 18a7ba113..9b3fdc469 100644 --- a/packs/zoom.yml +++ b/packs/zoom.yml @@ -15,10 +15,7 @@ PackDefinition: # Data Models used in these detections - Standard.Zoom.Operation # Globals used in these detections - - panther_base_helpers - - panther_config - - panther_config_defaults - - panther_config_overrides + + - panther_event_type_helpers - - panther_oss_helpers - panther_zoom_helpers diff --git a/policies/aws_acm_policies/aws_acm_certificate_expiration.py b/policies/aws_acm_policies/aws_acm_certificate_expiration.py index 4e5f3c03d..a87b93358 100644 --- a/policies/aws_acm_policies/aws_acm_certificate_expiration.py +++ b/policies/aws_acm_policies/aws_acm_certificate_expiration.py @@ -1,6 +1,6 @@ import datetime -from panther_oss_helpers import resolve_timestamp_string +from panther_base_helpers import resolve_timestamp_string AWS_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%SZ" EXPIRATION_BUFFER = datetime.timedelta(days=60) diff --git a/policies/aws_cloudtrail_policies/aws_cloudtrail_cloudwatch_logs.py b/policies/aws_cloudtrail_policies/aws_cloudtrail_cloudwatch_logs.py index ec6624216..06eeb0405 100644 --- a/policies/aws_cloudtrail_policies/aws_cloudtrail_cloudwatch_logs.py +++ b/policies/aws_cloudtrail_policies/aws_cloudtrail_cloudwatch_logs.py @@ -1,7 +1,6 @@ import datetime -from panther_base_helpers import deep_get -from panther_oss_helpers import resolve_timestamp_string +from panther_base_helpers import deep_get, resolve_timestamp_string MAX_TIME_BETWEEN_LOGS = datetime.timedelta(hours=24) diff --git a/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_access_logging.py b/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_access_logging.py index 60332dc57..0be93d571 100644 --- a/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_access_logging.py +++ b/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_access_logging.py @@ -1,4 +1,4 @@ -from panther_oss_helpers import BadLookup, resource_lookup +from panther_aws_helpers import BadLookup, resource_lookup def policy(resource): diff --git a/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_public.py b/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_public.py index 6a5c45c99..6edf7d182 100644 --- a/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_public.py +++ b/policies/aws_cloudtrail_policies/aws_cloudtrail_s3_bucket_public.py @@ -1,6 +1,5 @@ +from panther_aws_helpers import BadLookup, aws_regions, resource_lookup from panther_base_helpers import deep_get -from panther_default import aws_regions -from panther_oss_helpers import BadLookup, resource_lookup BAD_PERMISSIONS = { "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", diff --git a/policies/aws_config_policies/aws_config_global_resources.py b/policies/aws_config_policies/aws_config_global_resources.py index a3427e95a..0deb430db 100644 --- a/policies/aws_config_policies/aws_config_global_resources.py +++ b/policies/aws_config_policies/aws_config_global_resources.py @@ -1,7 +1,7 @@ import json +from panther_aws_helpers import BadLookup, resource_lookup from panther_base_helpers import deep_get -from panther_oss_helpers import BadLookup, resource_lookup # TODO: Once Detection Pipelines are merged, implement downgraded (INFO) case for multiple # global resource recorders. diff --git a/policies/aws_dynamodb_policies/aws_dynamodb_table_ttl_enabled.py b/policies/aws_dynamodb_policies/aws_dynamodb_table_ttl_enabled.py index d53e2f0e9..e49f21e80 100644 --- a/policies/aws_dynamodb_policies/aws_dynamodb_table_ttl_enabled.py +++ b/policies/aws_dynamodb_policies/aws_dynamodb_table_ttl_enabled.py @@ -1,4 +1,5 @@ -from panther_base_helpers import IN_PCI_SCOPE, deep_get +from panther_base_helpers import deep_get +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_ec2_policies/aws_ec2_cde_volume_encrypted.py b/policies/aws_ec2_policies/aws_ec2_cde_volume_encrypted.py index 8ba40898a..c1a2226fd 100644 --- a/policies/aws_ec2_policies/aws_ec2_cde_volume_encrypted.py +++ b/policies/aws_ec2_policies/aws_ec2_cde_volume_encrypted.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_iam_policies/aws_access_key_rotation.py b/policies/aws_iam_policies/aws_access_key_rotation.py index 740f1eb0c..bd2995f65 100644 --- a/policies/aws_iam_policies/aws_access_key_rotation.py +++ b/policies/aws_iam_policies/aws_access_key_rotation.py @@ -1,6 +1,6 @@ import datetime -from panther_oss_helpers import resolve_timestamp_string +from panther_base_helpers import resolve_timestamp_string TIMEOUT_DAYS = datetime.timedelta(days=90) diff --git a/policies/aws_iam_policies/aws_access_key_unused.py b/policies/aws_iam_policies/aws_access_key_unused.py index 00311944b..0422fb069 100644 --- a/policies/aws_iam_policies/aws_access_key_unused.py +++ b/policies/aws_iam_policies/aws_access_key_unused.py @@ -1,6 +1,6 @@ import datetime -from panther_oss_helpers import resolve_timestamp_string +from panther_base_helpers import resolve_timestamp_string TIMEOUT_DAYS = datetime.timedelta(days=90) DEFAULT_TIME = "0001-01-01T00:00:00Z" diff --git a/policies/aws_iam_policies/aws_access_keys_at_account_creation.py b/policies/aws_iam_policies/aws_access_keys_at_account_creation.py index 8dd1ee37c..b514a4669 100644 --- a/policies/aws_iam_policies/aws_access_keys_at_account_creation.py +++ b/policies/aws_iam_policies/aws_access_keys_at_account_creation.py @@ -1,7 +1,6 @@ from datetime import timedelta -from panther_base_helpers import deep_get -from panther_oss_helpers import resolve_timestamp_string +from panther_base_helpers import deep_get, resolve_timestamp_string AWS_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" DEFAULT_TIME = "0001-01-01T00:00:00Z" diff --git a/policies/aws_iam_policies/aws_iam_policy_administrative_privileges.py b/policies/aws_iam_policies/aws_iam_policy_administrative_privileges.py index 1e3bf4f73..1a2431c0e 100644 --- a/policies/aws_iam_policies/aws_iam_policy_administrative_privileges.py +++ b/policies/aws_iam_policies/aws_iam_policy_administrative_privileges.py @@ -1,6 +1,6 @@ import json -from panther_oss_helpers import listify +from panther_base_helpers import listify def policy(resource): diff --git a/policies/aws_iam_policies/aws_iam_role_external_permission.py b/policies/aws_iam_policies/aws_iam_role_external_permission.py index 6d15b5d22..67a50ae1e 100644 --- a/policies/aws_iam_policies/aws_iam_role_external_permission.py +++ b/policies/aws_iam_policies/aws_iam_role_external_permission.py @@ -1,7 +1,7 @@ import json from botocore.exceptions import NoCredentialsError -from panther_oss_helpers import BadLookup, resource_lookup +from panther_aws_helpers import BadLookup, resource_lookup # This is a list of the account numbers included in the organization # Example: diff --git a/policies/aws_iam_policies/aws_password_unused.py b/policies/aws_iam_policies/aws_password_unused.py index 90dc26232..c28a4844a 100644 --- a/policies/aws_iam_policies/aws_password_unused.py +++ b/policies/aws_iam_policies/aws_password_unused.py @@ -1,6 +1,6 @@ import datetime -from panther_oss_helpers import resolve_timestamp_string +from panther_base_helpers import resolve_timestamp_string TIMEOUT_DAYS = datetime.timedelta(days=90) DEFAULT_TIME = "0001-01-01T00:00:00Z" diff --git a/policies/aws_load_balancer_policies/aws_elbv2_load_balancer_has_ssl_policy.py b/policies/aws_load_balancer_policies/aws_elbv2_load_balancer_has_ssl_policy.py index fb3297763..15c9f0dad 100644 --- a/policies/aws_load_balancer_policies/aws_elbv2_load_balancer_has_ssl_policy.py +++ b/policies/aws_load_balancer_policies/aws_elbv2_load_balancer_has_ssl_policy.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_rds_policies/aws_rds_instance_backup_retention_acceptable.py b/policies/aws_rds_policies/aws_rds_instance_backup_retention_acceptable.py index e846445aa..d7f2b4b05 100644 --- a/policies/aws_rds_policies/aws_rds_instance_backup_retention_acceptable.py +++ b/policies/aws_rds_policies/aws_rds_instance_backup_retention_acceptable.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE MAX_RETENTION_DAYS = 180 MIN_RETENTION_DAYS = 7 diff --git a/policies/aws_rds_policies/aws_rds_instance_snapshot_public_access.py b/policies/aws_rds_policies/aws_rds_instance_snapshot_public_access.py index 8923b53e2..078cb4a76 100644 --- a/policies/aws_rds_policies/aws_rds_instance_snapshot_public_access.py +++ b/policies/aws_rds_policies/aws_rds_instance_snapshot_public_access.py @@ -1,4 +1,4 @@ -from panther_oss_helpers import listify +from panther_base_helpers import listify def policy(resource): diff --git a/policies/aws_redshift_policies/aws_redshift_cluster_snapshot_retention_acceptable.py b/policies/aws_redshift_policies/aws_redshift_cluster_snapshot_retention_acceptable.py index 9e59001b8..ace8f0676 100644 --- a/policies/aws_redshift_policies/aws_redshift_cluster_snapshot_retention_acceptable.py +++ b/policies/aws_redshift_policies/aws_redshift_cluster_snapshot_retention_acceptable.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE # Retention period in days MIN_RETENTION_DAYS = 3 diff --git a/policies/aws_s3_policies/aws_s3_bucket_object_lock_configured.py b/policies/aws_s3_policies/aws_s3_bucket_object_lock_configured.py index 0cca2998f..03295ca1e 100644 --- a/policies/aws_s3_policies/aws_s3_bucket_object_lock_configured.py +++ b/policies/aws_s3_policies/aws_s3_bucket_object_lock_configured.py @@ -1,4 +1,5 @@ -from panther_base_helpers import IN_PCI_SCOPE, deep_get +from panther_base_helpers import deep_get +from panther_config_defaults import IN_PCI_SCOPE RETENTION_PERIOD_DAYS = 365 diff --git a/policies/aws_vpc_policies/aws_network_acl_restricts_inbound_traffic.py b/policies/aws_vpc_policies/aws_network_acl_restricts_inbound_traffic.py index 84b9982c4..3d01bdc3a 100644 --- a/policies/aws_vpc_policies/aws_network_acl_restricts_inbound_traffic.py +++ b/policies/aws_vpc_policies/aws_network_acl_restricts_inbound_traffic.py @@ -1,6 +1,6 @@ # This is a generic policy for checking inbound rules on a Network ACL. # It is recommended to add additional logic here based on your own use cases. -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_vpc_policies/aws_network_acl_restricts_insecure_protocols.py b/policies/aws_vpc_policies/aws_network_acl_restricts_insecure_protocols.py index 051fb473d..515d1747d 100644 --- a/policies/aws_vpc_policies/aws_network_acl_restricts_insecure_protocols.py +++ b/policies/aws_vpc_policies/aws_network_acl_restricts_insecure_protocols.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE # This is a list of default ports for insecure protocols. As AWS Network ACLs and Security Groups # are not application layer aware, this is the closest approximation that can be made to blocking diff --git a/policies/aws_vpc_policies/aws_network_acl_restricts_outbound_traffic.py b/policies/aws_vpc_policies/aws_network_acl_restricts_outbound_traffic.py index 8adec25f1..31d12e887 100644 --- a/policies/aws_vpc_policies/aws_network_acl_restricts_outbound_traffic.py +++ b/policies/aws_vpc_policies/aws_network_acl_restricts_outbound_traffic.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE # This is generic policy that checks outbound traffic rules on a Network ACL. # It is recommended you add additional logic for your own use cases. diff --git a/policies/aws_vpc_policies/aws_only_dmz_security_groups_publicly_accessible.py b/policies/aws_vpc_policies/aws_only_dmz_security_groups_publicly_accessible.py index 1c8e209a7..243da074d 100644 --- a/policies/aws_vpc_policies/aws_only_dmz_security_groups_publicly_accessible.py +++ b/policies/aws_vpc_policies/aws_only_dmz_security_groups_publicly_accessible.py @@ -2,8 +2,8 @@ from ipaddress import ip_network from unittest.mock import MagicMock -from panther_base_helpers import is_dmz_tags from panther_config import config +from panther_config_defaults import is_dmz_tags DMZ_TAGS = config.DMZ_TAGS diff --git a/policies/aws_vpc_policies/aws_security_group_restricts_access_to_cde.py b/policies/aws_vpc_policies/aws_security_group_restricts_access_to_cde.py index db26fa7ca..2a5373927 100644 --- a/policies/aws_vpc_policies/aws_security_group_restricts_access_to_cde.py +++ b/policies/aws_vpc_policies/aws_security_group_restricts_access_to_cde.py @@ -1,6 +1,6 @@ from ipaddress import ip_network -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_vpc_policies/aws_security_group_restricts_inbound_traffic.py b/policies/aws_vpc_policies/aws_security_group_restricts_inbound_traffic.py index f362d00ba..94cd9667d 100644 --- a/policies/aws_vpc_policies/aws_security_group_restricts_inbound_traffic.py +++ b/policies/aws_vpc_policies/aws_security_group_restricts_inbound_traffic.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE # This is a generic policy that checks inbound permissions on a Security Group. # You may wish to add additional logic specific to your use cases. diff --git a/policies/aws_vpc_policies/aws_security_group_restricts_inter_security_group_traffic.py b/policies/aws_vpc_policies/aws_security_group_restricts_inter_security_group_traffic.py index 8a6fa0d96..91a10d18c 100644 --- a/policies/aws_vpc_policies/aws_security_group_restricts_inter_security_group_traffic.py +++ b/policies/aws_vpc_policies/aws_security_group_restricts_inter_security_group_traffic.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_vpc_policies/aws_security_group_restricts_outbound_traffic.py b/policies/aws_vpc_policies/aws_security_group_restricts_outbound_traffic.py index 9a12edd75..5906db6db 100644 --- a/policies/aws_vpc_policies/aws_security_group_restricts_outbound_traffic.py +++ b/policies/aws_vpc_policies/aws_security_group_restricts_outbound_traffic.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE # This is a generic policy that checks outbound permissions on Security Groups. # You may wish to add additional logic specific to your use case. diff --git a/policies/aws_vpc_policies/aws_security_group_restricts_traffic_leaving_cde.py b/policies/aws_vpc_policies/aws_security_group_restricts_traffic_leaving_cde.py index 26b4088a4..fea2776fe 100644 --- a/policies/aws_vpc_policies/aws_security_group_restricts_traffic_leaving_cde.py +++ b/policies/aws_vpc_policies/aws_security_group_restricts_traffic_leaving_cde.py @@ -1,6 +1,6 @@ from ipaddress import ip_network -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_vpc_policies/aws_security_group_tightly_restricts_inbound_traffic.py b/policies/aws_vpc_policies/aws_security_group_tightly_restricts_inbound_traffic.py index ad99bad39..54cca937b 100644 --- a/policies/aws_vpc_policies/aws_security_group_tightly_restricts_inbound_traffic.py +++ b/policies/aws_vpc_policies/aws_security_group_tightly_restricts_inbound_traffic.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE MAX_PORTS_PER_PERMISSION = 10 RESTRICTED_PORTS = [ diff --git a/policies/aws_vpc_policies/aws_security_group_tightly_restricts_outbound_traffic.py b/policies/aws_vpc_policies/aws_security_group_tightly_restricts_outbound_traffic.py index 17203dd5e..a7dea2968 100644 --- a/policies/aws_vpc_policies/aws_security_group_tightly_restricts_outbound_traffic.py +++ b/policies/aws_vpc_policies/aws_security_group_tightly_restricts_outbound_traffic.py @@ -1,4 +1,4 @@ -from panther_base_helpers import IN_PCI_SCOPE +from panther_config_defaults import IN_PCI_SCOPE MAX_PORTS_PER_PERMISSION = 10 RESTRICTED_PORTS = [ diff --git a/policies/aws_vpc_policies/aws_vpc_default_network_acl_restricts_all_traffic.py b/policies/aws_vpc_policies/aws_vpc_default_network_acl_restricts_all_traffic.py index c3c405747..c83392fea 100644 --- a/policies/aws_vpc_policies/aws_vpc_default_network_acl_restricts_all_traffic.py +++ b/policies/aws_vpc_policies/aws_vpc_default_network_acl_restricts_all_traffic.py @@ -1,5 +1,5 @@ -from panther_base_helpers import IN_PCI_SCOPE -from panther_oss_helpers import BadLookup, resource_lookup +from panther_aws_helpers import BadLookup, resource_lookup +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/policies/aws_vpc_policies/aws_vpc_default_security_restrictions.py b/policies/aws_vpc_policies/aws_vpc_default_security_restrictions.py index 62378bbeb..f933f4c9a 100644 --- a/policies/aws_vpc_policies/aws_vpc_default_security_restrictions.py +++ b/policies/aws_vpc_policies/aws_vpc_default_security_restrictions.py @@ -1,4 +1,4 @@ -from panther_oss_helpers import BadLookup, resource_lookup +from panther_aws_helpers import BadLookup, resource_lookup def policy(resource): diff --git a/policies/aws_waf_policies/aws_waf_has_xss_predicate.py b/policies/aws_waf_policies/aws_waf_has_xss_predicate.py index 263491ec0..e17520102 100644 --- a/policies/aws_waf_policies/aws_waf_has_xss_predicate.py +++ b/policies/aws_waf_policies/aws_waf_has_xss_predicate.py @@ -1,4 +1,5 @@ -from panther_base_helpers import IN_PCI_SCOPE, deep_get +from panther_base_helpers import deep_get +from panther_config_defaults import IN_PCI_SCOPE def policy(resource): diff --git a/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py b/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py index 9095a199e..f5d753eb6 100644 --- a/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py +++ b/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(_): diff --git a/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py b/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py index cfca5857e..0f04b9c4f 100644 --- a/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py +++ b/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py index 7f6694839..9c5d85560 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of CloudTrail changes CLOUDTRAIL_CREATE_UPDATE = { diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py index e6e01a70c..3921e4c4f 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context PROFILE_EVENTS = { "UpdateLoginProfile", diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py index da144c0aa..fa015b57f 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context PASSWORD_DISCOVERY_EVENTS = [ "GetAccountPasswordPolicy", diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py index 468a7298f..cd72761ff 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success, lookup_aws_account_name +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context, lookup_aws_account_name # API calls that are indicative of CloudTrail changes CLOUDTRAIL_STOP_DELETE = { diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py index 2ef367fd6..f12af23c8 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py b/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py index 49b8b90f3..a97caafe9 100644 --- a/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py +++ b/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import lookup_aws_account_name +from panther_aws_helpers import aws_rule_context, lookup_aws_account_name def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_config_service_created.py b/rules/aws_cloudtrail_rules/aws_config_service_created.py index 30754d502..d41bcb835 100644 --- a/rules/aws_cloudtrail_rules/aws_config_service_created.py +++ b/rules/aws_cloudtrail_rules/aws_config_service_created.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an AWS Config Service change CONFIG_SERVICE_CREATE_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_config_service_disabled_deleted.py b/rules/aws_cloudtrail_rules/aws_config_service_disabled_deleted.py index 48d1db056..9afa15851 100644 --- a/rules/aws_cloudtrail_rules/aws_config_service_disabled_deleted.py +++ b/rules/aws_cloudtrail_rules/aws_config_service_disabled_deleted.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an AWS Config Service change CONFIG_SERVICE_DISABLE_DELETE_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py b/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py index d73e99168..f7232cd64 100644 --- a/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py +++ b/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py @@ -1,7 +1,6 @@ import logging -from panther_base_helpers import aws_rule_context -from panther_default import lookup_aws_account_name +from panther_aws_helpers import aws_rule_context, lookup_aws_account_name from panther_detection_helpers.caching import check_account_age # Set to True for environments that permit direct role assumption via external IDP diff --git a/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py b/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py index a8761353d..05d136cb1 100644 --- a/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py +++ b/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import lookup_aws_account_name +from panther_aws_helpers import aws_rule_context, lookup_aws_account_name def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_console_root_login.py b/rules/aws_cloudtrail_rules/aws_console_root_login.py index 3b76e34bb..1527b6d10 100644 --- a/rules/aws_cloudtrail_rules/aws_console_root_login.py +++ b/rules/aws_cloudtrail_rules/aws_console_root_login.py @@ -1,5 +1,5 @@ -from panther_default import lookup_aws_account_name -from panther_oss_helpers import geoinfo_from_ip_formatted +from panther_aws_helpers import lookup_aws_account_name +from panther_ipinfo_helpers import geoinfo_from_ip_formatted def rule(event): @@ -11,10 +11,9 @@ def rule(event): def title(event): - ip_address = event.get("sourceIPAddress") return ( - f"AWS root login detected from [{ip_address}] " - f"({geoinfo_from_ip_formatted(ip_address)}) " + "AWS root login detected from " + f"({geoinfo_from_ip_formatted(event, 'sourceIPAddress')}) " f"in account " f"[{lookup_aws_account_name(event.get('recipientAccountId'))}]" ) diff --git a/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py b/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py index 030353307..cfe2f56ea 100644 --- a/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py +++ b/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import lookup_aws_account_name +from panther_aws_helpers import aws_rule_context, lookup_aws_account_name def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_ec2_ebs_encryption_disabled.py b/rules/aws_cloudtrail_rules/aws_ec2_ebs_encryption_disabled.py index d64916186..942a357ba 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_ebs_encryption_disabled.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_ebs_encryption_disabled.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_ec2_gateway_modified.py b/rules/aws_cloudtrail_rules/aws_ec2_gateway_modified.py index d05732e3e..d01ec3b38 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_gateway_modified.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_gateway_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an EC2 Network Gateway modification EC2_GATEWAY_MODIFIED_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py b/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py index 5deda785b..913c555c8 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py @@ -1,5 +1,5 @@ -from panther_base_helpers import aws_rule_context, pattern_match_list -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context +from panther_base_helpers import pattern_match_list PROD_ACCOUNT_IDS = {"11111111111111", "112233445566"} SG_CHANGE_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_ec2_network_acl_modified.py b/rules/aws_cloudtrail_rules/aws_ec2_network_acl_modified.py index 21a8726ea..1488160f5 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_network_acl_modified.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_network_acl_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an EC2 Network ACL modification EC2_NACL_MODIFIED_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_ec2_route_table_modified.py b/rules/aws_cloudtrail_rules/aws_ec2_route_table_modified.py index ac0dab914..6fc37c392 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_route_table_modified.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_route_table_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an EC2 Route Table modification EC2_RT_MODIFIED_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_ec2_security_group_modified.py b/rules/aws_cloudtrail_rules/aws_ec2_security_group_modified.py index 8ffa631d7..c37264e8c 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_security_group_modified.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_security_group_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an EC2 SecurityGroup modification EC2_SG_MODIFIED_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py b/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py index fd74dbd82..ce7100700 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py b/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py index cdb5c6e35..92a065a04 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_stopinstances.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py b/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py index 44c84a59b..bf4ce6cab 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_ec2_vpc_modified.py b/rules/aws_cloudtrail_rules/aws_ec2_vpc_modified.py index 8399815af..aa577280a 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_vpc_modified.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_vpc_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of an EC2 VPC modification EC2_VPC_MODIFIED_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_ec2_vulnerable_xz_image_launched.py b/rules/aws_cloudtrail_rules/aws_ec2_vulnerable_xz_image_launched.py index 7e11bdd80..3d7c96ff2 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_vulnerable_xz_image_launched.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_vulnerable_xz_image_launched.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context from panther_iocs import XZ_AMIS # AMIs published by Fedora between 2024-03-26 and 2024-04-02 diff --git a/rules/aws_cloudtrail_rules/aws_ecr_crud.py b/rules/aws_cloudtrail_rules/aws_ecr_crud.py index f5b7c6a81..cae7aa21d 100644 --- a/rules/aws_cloudtrail_rules/aws_ecr_crud.py +++ b/rules/aws_cloudtrail_rules/aws_ecr_crud.py @@ -1,6 +1,6 @@ from fnmatch import fnmatch -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context ECR_CRUD_EVENTS = { "BatchCheckLayerAvailability", diff --git a/rules/aws_cloudtrail_rules/aws_ecr_events.py b/rules/aws_cloudtrail_rules/aws_ecr_events.py index 2932d2c70..1c474a544 100644 --- a/rules/aws_cloudtrail_rules/aws_ecr_events.py +++ b/rules/aws_cloudtrail_rules/aws_ecr_events.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context # CONFIGURATION REQUIRED: Update with your expected AWS Accounts/Regions AWS_ACCOUNTS_AND_REGIONS = { diff --git a/rules/aws_cloudtrail_rules/aws_iam_anything_changed.py b/rules/aws_cloudtrail_rules/aws_iam_anything_changed.py index 275c68b3c..dbd6d9a8c 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_anything_changed.py +++ b/rules/aws_cloudtrail_rules/aws_iam_anything_changed.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context IAM_CHANGE_ACTIONS = [ "Add", diff --git a/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py b/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py index ccb2a25c9..81cb0dfb7 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py +++ b/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # This is a list of role ARNs that should not be assumed by users in normal operations ASSUME_ROLE_BLOCKLIST = [ diff --git a/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py b/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py index 91f6db9ee..02c0e300d 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py +++ b/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py @@ -1,7 +1,6 @@ import re -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # The role dedicated for IAM administration IAM_ADMIN_ROLES = { diff --git a/rules/aws_cloudtrail_rules/aws_iam_group_read_only_events.py b/rules/aws_cloudtrail_rules/aws_iam_group_read_only_events.py index 331dd89b7..6594e5318 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_group_read_only_events.py +++ b/rules/aws_cloudtrail_rules/aws_iam_group_read_only_events.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context # arn allow list to suppress alerts ARN_ALLOW_LIST = [] diff --git a/rules/aws_cloudtrail_rules/aws_iam_policy_modified.py b/rules/aws_cloudtrail_rules/aws_iam_policy_modified.py index b6717a0f9..77d016557 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_policy_modified.py +++ b/rules/aws_cloudtrail_rules/aws_iam_policy_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of IAM Policy changes POLICY_CHANGE_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py b/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py index afeaf695d..35495aed2 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py +++ b/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py b/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py index 77ffe49b1..56ad99954 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py +++ b/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py @@ -1,7 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import aws_rule_context -from panther_default import lookup_aws_account_name +from panther_aws_helpers import aws_rule_context, lookup_aws_account_name # service/event patterns to monitor RECON_ACTIONS = { diff --git a/rules/aws_cloudtrail_rules/aws_ipset_modified.py b/rules/aws_cloudtrail_rules/aws_ipset_modified.py index e1fd69d0d..8037d9f6b 100644 --- a/rules/aws_cloudtrail_rules/aws_ipset_modified.py +++ b/rules/aws_cloudtrail_rules/aws_ipset_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context IPSET_ACTIONS = ["CreateIPSet", "UpdateIPSet"] diff --git a/rules/aws_cloudtrail_rules/aws_key_compromised.py b/rules/aws_cloudtrail_rules/aws_key_compromised.py index 04035f957..4fb3265bd 100644 --- a/rules/aws_cloudtrail_rules/aws_key_compromised.py +++ b/rules/aws_cloudtrail_rules/aws_key_compromised.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context EXPOSED_CRED_POLICY = "AWSExposedCredentialPolicy_DO_NOT_REMOVE" diff --git a/rules/aws_cloudtrail_rules/aws_kms_cmk_loss.py b/rules/aws_cloudtrail_rules/aws_kms_cmk_loss.py index 5aeb18c53..79525aa16 100644 --- a/rules/aws_cloudtrail_rules/aws_kms_cmk_loss.py +++ b/rules/aws_cloudtrail_rules/aws_kms_cmk_loss.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of KMS CMK Deletion KMS_LOSS_EVENTS = {"DisableKey", "ScheduleKeyDeletion"} diff --git a/rules/aws_cloudtrail_rules/aws_lambda_crud.py b/rules/aws_cloudtrail_rules/aws_lambda_crud.py index 3a5f5ab10..6b7317c04 100644 --- a/rules/aws_cloudtrail_rules/aws_lambda_crud.py +++ b/rules/aws_cloudtrail_rules/aws_lambda_crud.py @@ -1,6 +1,6 @@ from fnmatch import fnmatch -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context LAMBDA_CRUD_EVENTS = { "AddPermission", diff --git a/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py b/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py index 9ff9b29c0..67151118c 100644 --- a/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py +++ b/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.py b/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.py index 4261f41f3..fd537d284 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.py +++ b/rules/aws_cloudtrail_rules/aws_rds_manual_snapshot_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py b/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py index 3a09a26d3..08890e270 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py +++ b/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py b/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py index b76853904..70ed4e9ec 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py +++ b/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_resource_made_public.py b/rules/aws_cloudtrail_rules/aws_resource_made_public.py index 9290d820d..bd2071a93 100644 --- a/rules/aws_cloudtrail_rules/aws_resource_made_public.py +++ b/rules/aws_cloudtrail_rules/aws_resource_made_public.py @@ -1,7 +1,7 @@ import json -from panther_base_helpers import aws_rule_context, deep_get -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context +from panther_base_helpers import deep_get from policyuniverse.policy import Policy diff --git a/rules/aws_cloudtrail_rules/aws_root_access_key_created.py b/rules/aws_cloudtrail_rules/aws_root_access_key_created.py index f359799b4..9cc184fa9 100644 --- a/rules/aws_cloudtrail_rules/aws_root_access_key_created.py +++ b/rules/aws_cloudtrail_rules/aws_root_access_key_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_root_activity.py b/rules/aws_cloudtrail_rules/aws_root_activity.py index 5439f6a90..3e2daea43 100644 --- a/rules/aws_cloudtrail_rules/aws_root_activity.py +++ b/rules/aws_cloudtrail_rules/aws_root_activity.py @@ -1,4 +1,4 @@ -from panther_default import aws_cloudtrail_success, lookup_aws_account_name +from panther_aws_helpers import aws_cloudtrail_success, lookup_aws_account_name EVENT_ALLOW_LIST = {"CreateServiceLinkedRole"} diff --git a/rules/aws_cloudtrail_rules/aws_root_password_changed.py b/rules/aws_cloudtrail_rules/aws_root_password_changed.py index 769a0ccc5..d855cc271 100644 --- a/rules/aws_cloudtrail_rules/aws_root_password_changed.py +++ b/rules/aws_cloudtrail_rules/aws_root_password_changed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py b/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py index 5e8ee9f11..53809a42e 100644 --- a/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py +++ b/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context def rule(event): diff --git a/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py b/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py index 49d92f7d6..00b20c01a 100644 --- a/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py +++ b/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context # API calls that are indicative of KMS CMK Deletion S3_POLICY_CHANGE_EVENTS = { diff --git a/rules/aws_cloudtrail_rules/aws_saml_activity.py b/rules/aws_cloudtrail_rules/aws_saml_activity.py index 2a43a1e12..2e54c7503 100644 --- a/rules/aws_cloudtrail_rules/aws_saml_activity.py +++ b/rules/aws_cloudtrail_rules/aws_saml_activity.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context SAML_ACTIONS = ["UpdateSAMLProvider", "CreateSAMLProvider", "DeleteSAMLProvider"] diff --git a/rules/aws_cloudtrail_rules/aws_security_configuration_change.py b/rules/aws_cloudtrail_rules/aws_security_configuration_change.py index 1a8e281fd..cc0c33354 100644 --- a/rules/aws_cloudtrail_rules/aws_security_configuration_change.py +++ b/rules/aws_cloudtrail_rules/aws_security_configuration_change.py @@ -2,8 +2,7 @@ from fnmatch import fnmatch from unittest.mock import MagicMock -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context SECURITY_CONFIG_ACTIONS = { "DeleteAccountPublicAccessBlock", diff --git a/rules/aws_cloudtrail_rules/aws_securityhub_finding_evasion.py b/rules/aws_cloudtrail_rules/aws_securityhub_finding_evasion.py index 6161bc997..a9bb6f551 100644 --- a/rules/aws_cloudtrail_rules/aws_securityhub_finding_evasion.py +++ b/rules/aws_cloudtrail_rules/aws_securityhub_finding_evasion.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context EVASION_OPERATIONS = ["BatchUpdateFindings", "DeleteInsight", "UpdateFindings", "UpdateInsight"] diff --git a/rules/aws_cloudtrail_rules/aws_snapshot_made_public.py b/rules/aws_cloudtrail_rules/aws_snapshot_made_public.py index 5b897b234..b06c9433d 100644 --- a/rules/aws_cloudtrail_rules/aws_snapshot_made_public.py +++ b/rules/aws_cloudtrail_rules/aws_snapshot_made_public.py @@ -1,7 +1,7 @@ from collections.abc import Mapping -from panther_base_helpers import aws_rule_context, deep_get -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context +from panther_base_helpers import deep_get IS_SINGLE_USER_SHARE = False # Used to adjust severity diff --git a/rules/aws_cloudtrail_rules/aws_software_discovery.py b/rules/aws_cloudtrail_rules/aws_software_discovery.py index f0b8ca712..69f1de38a 100644 --- a/rules/aws_cloudtrail_rules/aws_software_discovery.py +++ b/rules/aws_cloudtrail_rules/aws_software_discovery.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context DISCOVERY_EVENTS = [ "ListDocuments", diff --git a/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py b/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py index e62616c77..eaf419eb1 100644 --- a/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py +++ b/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py @@ -1,6 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context # Do not alert on these access denied errors for these events. # Events could be exceptions because they are particularly noisy and provide little to no value, diff --git a/rules/aws_cloudtrail_rules/aws_unused_region.py b/rules/aws_cloudtrail_rules/aws_unused_region.py index 3af9f15da..7ef61760a 100644 --- a/rules/aws_cloudtrail_rules/aws_unused_region.py +++ b/rules/aws_cloudtrail_rules/aws_unused_region.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context # Define a list of verboten or unused regions # Could modify to include expected user mappings: { "123456789012": { "us-west-1", "us-east-2" } } diff --git a/rules/aws_cloudtrail_rules/aws_update_credentials.py b/rules/aws_cloudtrail_rules/aws_update_credentials.py index 9e79e2113..dcd08a3cb 100644 --- a/rules/aws_cloudtrail_rules/aws_update_credentials.py +++ b/rules/aws_cloudtrail_rules/aws_update_credentials.py @@ -1,5 +1,4 @@ -from panther_base_helpers import aws_rule_context -from panther_default import aws_cloudtrail_success +from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context UPDATE_EVENTS = {"ChangePassword", "CreateAccessKey", "CreateLoginProfile", "CreateUser"} diff --git a/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py b/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py index e706c0597..39b494858 100644 --- a/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py +++ b/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_eks_rules/source_ip_multiple_403.py b/rules/aws_eks_rules/source_ip_multiple_403.py index 319ed9f70..30a28a8b9 100644 --- a/rules/aws_eks_rules/source_ip_multiple_403.py +++ b/rules/aws_eks_rules/source_ip_multiple_403.py @@ -1,6 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import eks_panther_obj_ref +from panther_aws_helpers import eks_panther_obj_ref # Alert if diff --git a/rules/aws_eks_rules/system_namespace_public_ip.py b/rules/aws_eks_rules/system_namespace_public_ip.py index 2095224b7..69618f1ad 100644 --- a/rules/aws_eks_rules/system_namespace_public_ip.py +++ b/rules/aws_eks_rules/system_namespace_public_ip.py @@ -1,6 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import eks_panther_obj_ref +from panther_aws_helpers import eks_panther_obj_ref # Explicitly ignore eks:node-manager and eks:addon-manager # which are run as Lambdas and originate from public IPs diff --git a/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py b/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py index 97434670f..fc8e59f5b 100644 --- a/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py +++ b/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_guardduty_context +from panther_aws_helpers import aws_guardduty_context def rule(event): diff --git a/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py b/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py index 086b05984..7031b5e93 100644 --- a/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py +++ b/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_guardduty_context +from panther_aws_helpers import aws_guardduty_context def rule(event): diff --git a/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py b/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py index 6931d9567..9643cd428 100644 --- a/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py +++ b/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_guardduty_context +from panther_aws_helpers import aws_guardduty_context def rule(event): diff --git a/rules/aws_s3_rules/aws_s3_access_error.py b/rules/aws_s3_rules/aws_s3_access_error.py index 8978097df..7e5fb9546 100644 --- a/rules/aws_s3_rules/aws_s3_access_error.py +++ b/rules/aws_s3_rules/aws_s3_access_error.py @@ -1,4 +1,5 @@ -from panther_base_helpers import aws_rule_context, pattern_match +from panther_aws_helpers import aws_rule_context +from panther_base_helpers import pattern_match # https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html HTTP_STATUS_CODES_TO_MONITOR = { diff --git a/rules/aws_s3_rules/aws_s3_access_ip_allowlist.py b/rules/aws_s3_rules/aws_s3_access_ip_allowlist.py index 9b4737549..7ceebb44f 100644 --- a/rules/aws_s3_rules/aws_s3_access_ip_allowlist.py +++ b/rules/aws_s3_rules/aws_s3_access_ip_allowlist.py @@ -1,6 +1,6 @@ from ipaddress import ip_network -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context BUCKETS_TO_MONITOR = { # Example bucket names to watch go here diff --git a/rules/aws_s3_rules/aws_s3_insecure_access.py b/rules/aws_s3_rules/aws_s3_insecure_access.py index b6402c615..8f510aa4c 100644 --- a/rules/aws_s3_rules/aws_s3_insecure_access.py +++ b/rules/aws_s3_rules/aws_s3_insecure_access.py @@ -1,4 +1,5 @@ -from panther_base_helpers import aws_rule_context, pattern_match +from panther_aws_helpers import aws_rule_context +from panther_base_helpers import pattern_match def rule(event): diff --git a/rules/aws_s3_rules/aws_s3_unauthenticated_access.py b/rules/aws_s3_rules/aws_s3_unauthenticated_access.py index 359f26c8c..00722860d 100644 --- a/rules/aws_s3_rules/aws_s3_unauthenticated_access.py +++ b/rules/aws_s3_rules/aws_s3_unauthenticated_access.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context # A list of buckets where authenticated access is expected AUTH_BUCKETS = {"example-bucket"} diff --git a/rules/aws_s3_rules/aws_s3_unknown_requester_get_object.py b/rules/aws_s3_rules/aws_s3_unknown_requester_get_object.py index 6e0a62617..0fd796315 100644 --- a/rules/aws_s3_rules/aws_s3_unknown_requester_get_object.py +++ b/rules/aws_s3_rules/aws_s3_unknown_requester_get_object.py @@ -1,6 +1,6 @@ from fnmatch import fnmatch -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context # pylint: disable=line-too-long BUCKET_ROLE_MAPPING = { diff --git a/rules/aws_vpc_flow_rules/aws_vpc_healthy_log_status.py b/rules/aws_vpc_flow_rules/aws_vpc_healthy_log_status.py index cda033337..617ff6dfa 100644 --- a/rules/aws_vpc_flow_rules/aws_vpc_healthy_log_status.py +++ b/rules/aws_vpc_flow_rules/aws_vpc_healthy_log_status.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context def rule(event): diff --git a/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_allowlist.py b/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_allowlist.py index b64bad67e..30129d5c9 100644 --- a/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_allowlist.py +++ b/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_allowlist.py @@ -1,6 +1,6 @@ from ipaddress import ip_network -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context APPROVED_PORTS = { 80, diff --git a/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_blocklist.py b/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_blocklist.py index b1f331514..990dabb25 100644 --- a/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_blocklist.py +++ b/rules/aws_vpc_flow_rules/aws_vpc_inbound_traffic_port_blocklist.py @@ -1,6 +1,6 @@ from ipaddress import ip_network -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context CONTROLLED_PORTS = { 22, diff --git a/rules/aws_vpc_flow_rules/aws_vpc_unapproved_outbound_dns.py b/rules/aws_vpc_flow_rules/aws_vpc_unapproved_outbound_dns.py index d7d462be6..b546d109a 100644 --- a/rules/aws_vpc_flow_rules/aws_vpc_unapproved_outbound_dns.py +++ b/rules/aws_vpc_flow_rules/aws_vpc_unapproved_outbound_dns.py @@ -1,6 +1,6 @@ from ipaddress import ip_network -from panther_base_helpers import aws_rule_context +from panther_aws_helpers import aws_rule_context APPROVED_DNS_SERVERS = { "1.1.1.1", # CloudFlare DNS diff --git a/rules/box_rules/box_anomalous_download.py b/rules/box_rules/box_anomalous_download.py index 6e96785f0..dadff717f 100644 --- a/rules/box_rules/box_anomalous_download.py +++ b/rules/box_rules/box_anomalous_download.py @@ -1,4 +1,5 @@ -from panther_base_helpers import box_parse_additional_details, deep_get +from panther_base_helpers import deep_get +from panther_box_helpers import box_parse_additional_details def rule(event): diff --git a/rules/box_rules/box_malicious_content.py b/rules/box_rules/box_malicious_content.py index ab040fa5b..c30e39d52 100644 --- a/rules/box_rules/box_malicious_content.py +++ b/rules/box_rules/box_malicious_content.py @@ -1,4 +1,5 @@ -from panther_base_helpers import box_parse_additional_details, deep_get +from panther_base_helpers import deep_get +from panther_box_helpers import box_parse_additional_details def rule(event): diff --git a/rules/box_rules/box_suspicious_login_or_session.py b/rules/box_rules/box_suspicious_login_or_session.py index 638526399..598e19da6 100644 --- a/rules/box_rules/box_suspicious_login_or_session.py +++ b/rules/box_rules/box_suspicious_login_or_session.py @@ -1,4 +1,5 @@ -from panther_base_helpers import box_parse_additional_details, deep_get +from panther_base_helpers import deep_get +from panther_box_helpers import box_parse_additional_details SUSPICIOUS_EVENT_TYPES = { "Suspicious Locations", diff --git a/rules/crowdstrike_rules/crowdstrike_base64_encoded_args.py b/rules/crowdstrike_rules/crowdstrike_base64_encoded_args.py index feb82d4a0..c730bd0fd 100644 --- a/rules/crowdstrike_rules/crowdstrike_base64_encoded_args.py +++ b/rules/crowdstrike_rules/crowdstrike_base64_encoded_args.py @@ -1,4 +1,5 @@ -from panther_base_helpers import crowdstrike_process_alert_context, is_base64 +from panther_base_helpers import is_base64 +from panther_crowdstrike_fdr_helpers import crowdstrike_process_alert_context DECODED = "" diff --git a/rules/crowdstrike_rules/crowdstrike_connection_to_embargoed_country.py b/rules/crowdstrike_rules/crowdstrike_connection_to_embargoed_country.py index 61fcd29de..12012a511 100644 --- a/rules/crowdstrike_rules/crowdstrike_connection_to_embargoed_country.py +++ b/rules/crowdstrike_rules/crowdstrike_connection_to_embargoed_country.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_network_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_network_detection_alert_context # U.S. Gov Sanctioned Destinations EMBARGO_COUNTRY_CODES = { diff --git a/rules/crowdstrike_rules/crowdstrike_credential_dumping_tool.py b/rules/crowdstrike_rules/crowdstrike_credential_dumping_tool.py index 4c53f89b9..ef454a7fb 100644 --- a/rules/crowdstrike_rules/crowdstrike_credential_dumping_tool.py +++ b/rules/crowdstrike_rules/crowdstrike_credential_dumping_tool.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context CREDENTIAL_DUMPING_TOOLS = { "mimikatz.exe", diff --git a/rules/crowdstrike_rules/crowdstrike_cryptomining_tools.py b/rules/crowdstrike_rules/crowdstrike_cryptomining_tools.py index 4cc13a606..b5031cf28 100644 --- a/rules/crowdstrike_rules/crowdstrike_cryptomining_tools.py +++ b/rules/crowdstrike_rules/crowdstrike_cryptomining_tools.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context CRYPTOCURRENCY_MINING_TOOLS = { "xmrig.exe", diff --git a/rules/crowdstrike_rules/crowdstrike_detection_passthrough.py b/rules/crowdstrike_rules/crowdstrike_detection_passthrough.py index 9fd8bf89d..9f3410f9f 100644 --- a/rules/crowdstrike_rules/crowdstrike_detection_passthrough.py +++ b/rules/crowdstrike_rules/crowdstrike_detection_passthrough.py @@ -1,4 +1,7 @@ -from panther_base_helpers import crowdstrike_detection_alert_context, get_crowdstrike_field +from panther_crowdstrike_fdr_helpers import ( + crowdstrike_detection_alert_context, + get_crowdstrike_field, +) def rule(event): diff --git a/rules/crowdstrike_rules/crowdstrike_dns_request.py b/rules/crowdstrike_rules/crowdstrike_dns_request.py index ec02a053b..199bfccd9 100644 --- a/rules/crowdstrike_rules/crowdstrike_dns_request.py +++ b/rules/crowdstrike_rules/crowdstrike_dns_request.py @@ -1,4 +1,4 @@ -from panther_base_helpers import filter_crowdstrike_fdr_event_type, get_crowdstrike_field +from panther_crowdstrike_fdr_helpers import filter_crowdstrike_fdr_event_type, get_crowdstrike_field # baddomain.com is present for testing purposes. Add domains you wish to be alerted on to this list DENYLIST = ["baddomain.com"] diff --git a/rules/crowdstrike_rules/crowdstrike_lolbas.py b/rules/crowdstrike_rules/crowdstrike_lolbas.py index 4e3bcfdf6..db080256e 100644 --- a/rules/crowdstrike_rules/crowdstrike_lolbas.py +++ b/rules/crowdstrike_rules/crowdstrike_lolbas.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_process_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_process_alert_context LOLBAS_EXE = { "AppInstaller.exe", diff --git a/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py b/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py index 8dfda439a..d3844bc24 100644 --- a/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py +++ b/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_process_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_process_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py b/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py index c8a2b03a9..229c61654 100644 --- a/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py +++ b/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_process_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_process_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py b/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py index e49f2e312..fbd736225 100644 --- a/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py +++ b/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_process_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_process_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/crowdstrike_real_time_response_session.py b/rules/crowdstrike_rules/crowdstrike_real_time_response_session.py index e9802d032..5dbba37e9 100644 --- a/rules/crowdstrike_rules/crowdstrike_real_time_response_session.py +++ b/rules/crowdstrike_rules/crowdstrike_real_time_response_session.py @@ -1,4 +1,4 @@ -from panther_base_helpers import get_crowdstrike_field +from panther_crowdstrike_fdr_helpers import get_crowdstrike_field def rule(event): diff --git a/rules/crowdstrike_rules/crowdstrike_remote_access_tool_execution.py b/rules/crowdstrike_rules/crowdstrike_remote_access_tool_execution.py index 867c00315..ec16f2443 100644 --- a/rules/crowdstrike_rules/crowdstrike_remote_access_tool_execution.py +++ b/rules/crowdstrike_rules/crowdstrike_remote_access_tool_execution.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context REMOTE_ACCESS_EXECUTABLES = { "teamviewer_service.exe", diff --git a/rules/crowdstrike_rules/crowdstrike_reverse_shell_tool_executed.py b/rules/crowdstrike_rules/crowdstrike_reverse_shell_tool_executed.py index 98b06b1fb..1c284c1f4 100644 --- a/rules/crowdstrike_rules/crowdstrike_reverse_shell_tool_executed.py +++ b/rules/crowdstrike_rules/crowdstrike_reverse_shell_tool_executed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context REMOTE_SHELL_TOOLS = { # process name: reverse shell signature diff --git a/rules/crowdstrike_rules/crowdstrike_systemlog_tampering.py b/rules/crowdstrike_rules/crowdstrike_systemlog_tampering.py index 9cf355e1e..ff27729e7 100644 --- a/rules/crowdstrike_rules/crowdstrike_systemlog_tampering.py +++ b/rules/crowdstrike_rules/crowdstrike_systemlog_tampering.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context CLEARING_SYSTEM_LOG_TOOLS = { "wevtutil.exe": ["cl", "clear-log"], diff --git a/rules/crowdstrike_rules/crowdstrike_unusual_parent_child_processes.py b/rules/crowdstrike_rules/crowdstrike_unusual_parent_child_processes.py index f81291349..a7182ab0f 100644 --- a/rules/crowdstrike_rules/crowdstrike_unusual_parent_child_processes.py +++ b/rules/crowdstrike_rules/crowdstrike_unusual_parent_child_processes.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context SUSPICIOUS_PARENT_CHILD_COMBINATIONS_WINDOWS = { ("svchost.exe", "cmd.exe"), diff --git a/rules/crowdstrike_rules/crowdstrike_wmi_query_detection.py b/rules/crowdstrike_rules/crowdstrike_wmi_query_detection.py index 7ceae7370..b4108e161 100644 --- a/rules/crowdstrike_rules/crowdstrike_wmi_query_detection.py +++ b/rules/crowdstrike_rules/crowdstrike_wmi_query_detection.py @@ -1,4 +1,4 @@ -from panther_base_helpers import crowdstrike_detection_alert_context +from panther_crowdstrike_fdr_helpers import crowdstrike_detection_alert_context WMIC_SIGNATURES = ["get", "list", "process call create", "cmd.exe", "powershell.exe", "command.exe"] diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_admin_role_assigned.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_admin_role_assigned.py index 2b2f52848..c88888198 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_admin_role_assigned.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_admin_role_assigned.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context +from panther_crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context # List of priviledged roles. # IMPORTANT: YOU MUST ADD ANY CUSTOM ADMIN ROLES YOURSELF diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_allowlist_removed.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_allowlist_removed.py index 5ad356e9d..d4ccffed1 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_allowlist_removed.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_allowlist_removed.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context +from panther_crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_created.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_created.py index 63b41dc51..f4ce8d652 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_created.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_created.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import cs_alert_context +from panther_crowdstrike_event_streams_helpers import cs_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_deleted.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_deleted.py index f508e0b1a..20f7d7917 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_deleted.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_api_key_deleted.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import cs_alert_context +from panther_crowdstrike_event_streams_helpers import cs_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_ip_allowlist_changed.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_ip_allowlist_changed.py index 7df13f7af..bb38a0ba6 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_ip_allowlist_changed.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_ip_allowlist_changed.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context, str_to_list +from panther_crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context, str_to_list def rule(event): diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_new_user_created.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_new_user_created.py index 0b8b44ef1..a9dd16d74 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_new_user_created.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_new_user_created.py @@ -1,5 +1,5 @@ -from crowdstrike_event_streams_helpers import cs_alert_context from panther_base_helpers import key_value_list_to_dict +from panther_crowdstrike_event_streams_helpers import cs_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_password_change.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_password_change.py index 0a63373c7..3f0515ca4 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_password_change.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_password_change.py @@ -1,5 +1,5 @@ -from crowdstrike_event_streams_helpers import cs_alert_context from panther_base_helpers import key_value_list_to_dict +from panther_crowdstrike_event_streams_helpers import cs_alert_context def rule(event): diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_single_ip_allowlisted.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_single_ip_allowlisted.py index 755ee3b78..7646416fc 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_single_ip_allowlisted.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_single_ip_allowlisted.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context, str_to_list +from panther_crowdstrike_event_streams_helpers import audit_keys_dict, cs_alert_context, str_to_list def get_single_ips(event, fieldname="cidrs") -> list[str]: diff --git a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_user_deleted.py b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_user_deleted.py index 46d66c689..58c02173c 100644 --- a/rules/crowdstrike_rules/event_stream_rules/crowdstrike_user_deleted.py +++ b/rules/crowdstrike_rules/event_stream_rules/crowdstrike_user_deleted.py @@ -1,4 +1,4 @@ -from crowdstrike_event_streams_helpers import cs_alert_context +from panther_crowdstrike_event_streams_helpers import cs_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_cloud_run_service_created.py b/rules/gcp_audit_rules/gcp_cloud_run_service_created.py index 2a29a4ff8..a0c0d0443 100644 --- a/rules/gcp_audit_rules/gcp_cloud_run_service_created.py +++ b/rules/gcp_audit_rules/gcp_cloud_run_service_created.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py index 48b21472f..cf36ab14e 100644 --- a/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py +++ b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py b/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py index d6dd3a946..951264ef7 100644 --- a/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py index d46bbbef7..b082cab40 100644 --- a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py +++ b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py index c72a2535f..499c10cc0 100644 --- a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py +++ b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py index f37ae5de5..efd28e7b5 100644 --- a/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context REQUIRED_PERMISSIONS = [ "compute.disks.create", diff --git a/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py b/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py index edde9df60..f854c2e77 100644 --- a/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py +++ b/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_firewall_rule_created.py b/rules/gcp_audit_rules/gcp_firewall_rule_created.py index 80a17c711..c207e8db6 100644 --- a/rules/gcp_audit_rules/gcp_firewall_rule_created.py +++ b/rules/gcp_audit_rules/gcp_firewall_rule_created.py @@ -1,6 +1,6 @@ import re -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py b/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py index 90eb7001e..92da5aecb 100644 --- a/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py +++ b/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py @@ -1,6 +1,6 @@ import re -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_firewall_rule_modified.py b/rules/gcp_audit_rules/gcp_firewall_rule_modified.py index a0eb6b774..6fb309758 100644 --- a/rules/gcp_audit_rules/gcp_firewall_rule_modified.py +++ b/rules/gcp_audit_rules/gcp_firewall_rule_modified.py @@ -1,6 +1,6 @@ import re -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py b/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py index f367a0739..33cae2af2 100644 --- a/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py b/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py index 7997caf25..df01c5ebb 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py +++ b/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py b/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py index d35bcc1d7..354efc087 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py b/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py index 285ac3b1a..92b4985d5 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py +++ b/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py b/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py index 7a5403075..e4f1ea220 100644 --- a/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py +++ b/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py b/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py index e378e3665..e605c348e 100644 --- a/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py +++ b/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py @@ -1,6 +1,6 @@ import re -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_logging_sink_modified.py b/rules/gcp_audit_rules/gcp_logging_sink_modified.py index e230a4030..ea17c47df 100644 --- a/rules/gcp_audit_rules/gcp_logging_sink_modified.py +++ b/rules/gcp_audit_rules/gcp_logging_sink_modified.py @@ -1,6 +1,6 @@ import re -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py b/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py index 16c15b8d2..06d27d41f 100644 --- a/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py +++ b/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_service_account_access_denied.py b/rules/gcp_audit_rules/gcp_service_account_access_denied.py index f40e0044f..d14f285e7 100644 --- a/rules/gcp_audit_rules/gcp_service_account_access_denied.py +++ b/rules/gcp_audit_rules/gcp_service_account_access_denied.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py b/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py index 1380e3e73..d90aedb7a 100644 --- a/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py b/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py index 4c0b5e7ad..244437122 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py +++ b/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py b/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py index 19329a987..773db8a15 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py +++ b/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py @@ -1,9 +1,41 @@ -from gcp_base_helpers import get_k8s_info -from gcp_environment import PRODUCTION_PROJECT_IDS, rule_exceptions +import json +from unittest.mock import MagicMock + from panther_base_helpers import deep_walk +from panther_config import config +from panther_gcp_helpers import get_k8s_info + +GCP_PRODUCTION_PROJECT_IDS = config.GCP_PRODUCTION_PROJECT_IDS + +# This is a list of principals that are allowed to exec into pods +# in various namespaces and projects. +ALLOW_LIST = [ + { + # If empty, then no principals + "principals": [ + # "system:serviceaccount:example-namespace:example-namespace-service-account", + ], + # If empty, then all namespaces + "namespaces": [], + # If projects empty then all projects + "projects": [], + }, + # Add more allowed principals here + # { + # "principals": [], + # "namespaces": [], + # "projects": [], + # }, +] def rule(event): + # pylint: disable=not-callable + # pylint: disable=global-statement + global ALLOW_LIST + if isinstance(ALLOW_LIST, MagicMock): + ALLOW_LIST = json.loads(ALLOW_LIST()) + # Defaults to False (no alert) unless method is exec and principal not allowed if not all( [ @@ -19,9 +51,7 @@ def rule(event): project_id = deep_walk(k8s_info, "project_id", default="") # rule_exceptions that are allowed temporarily are defined in gcp_environment.py # Some execs have principal which is long numerical UUID, appears to be k8s internals - for allowed_principal in deep_walk( - rule_exceptions, "gcp_k8s_exec_into_pod", "allowed_principals", default=[] - ): + for allowed_principal in ALLOW_LIST: allowed_principals = deep_walk(allowed_principal, "principals", default=[]) allowed_namespaces = deep_walk(allowed_principal, "namespaces", default=[]) allowed_project_ids = deep_walk(allowed_principal, "projects", default=[]) @@ -37,7 +67,7 @@ def rule(event): def severity(event): project_id = deep_walk(get_k8s_info(event), "project_id", default="") - if project_id in PRODUCTION_PROJECT_IDS: + if project_id in GCP_PRODUCTION_PROJECT_IDS: return "high" return "info" diff --git a/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.yml b/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.yml index 507de5b7a..b82095f25 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.yml +++ b/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.yml @@ -18,6 +18,16 @@ Reference: https://cloud.google.com/migrate/containers/docs/troubleshooting/exec Tests: - Name: Allowed User ExpectedResult: false + Mocks: + - objectName: ALLOW_LIST + returnValue: >- + [ + { + "principals": ["system:serviceaccount:example-namespace:example-namespace-service-account"], + "namespaces": [], + "projects": [] + } + ] Log: { "protoPayload": diff --git a/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py b/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py index 26c42242d..c182f16a6 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py +++ b/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py b/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py index ee6b070ec..940e62324 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py +++ b/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py b/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py index d505c524f..fd9a427b5 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py index 3ab845673..98b1b50c6 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context SUSPICIOUS_PATHS = [ "/var/run/docker.sock", diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py b/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py index f40254b72..6c417c285 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context METHODS_TO_CHECK = [ "io.k8s.core.v1.pods.create", diff --git a/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py index d626f87c7..4db8c3cfb 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py +++ b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py @@ -1,5 +1,5 @@ -from gcp_base_helpers import gcp_alert_context from panther_base_helpers import deep_get +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py b/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py index 9e85761c9..ec6ed5709 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py +++ b/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py @@ -1,4 +1,4 @@ -from gcp_base_helpers import gcp_alert_context +from panther_gcp_helpers import gcp_alert_context def rule(event): diff --git a/rules/github_rules/github_action_failed.py b/rules/github_rules/github_action_failed.py index 5911fbeda..3654ca9b0 100644 --- a/rules/github_rules/github_action_failed.py +++ b/rules/github_rules/github_action_failed.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context # The keys for MONITORED_ACTIONS are gh_org/repo_name # The values for MONITORED_ACTIONS are a list of ["action_names"] diff --git a/rules/github_rules/github_advanced_security_change.py b/rules/github_rules/github_advanced_security_change.py index f2041e4db..a3f8467db 100644 --- a/rules/github_rules/github_advanced_security_change.py +++ b/rules/github_rules/github_advanced_security_change.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context # List of actions in markdown format # pylint: disable=line-too-long diff --git a/rules/github_rules/github_org_moderators_add.py b/rules/github_rules/github_org_moderators_add.py index 3b3e39928..afcf60642 100644 --- a/rules/github_rules/github_org_moderators_add.py +++ b/rules/github_rules/github_org_moderators_add.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context def rule(event): diff --git a/rules/github_rules/github_organization_app_integration_installed.py b/rules/github_rules/github_organization_app_integration_installed.py index 8503fcebc..d2710e550 100644 --- a/rules/github_rules/github_organization_app_integration_installed.py +++ b/rules/github_rules/github_organization_app_integration_installed.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context def rule(event): diff --git a/rules/github_rules/github_public_repository_created.py b/rules/github_rules/github_public_repository_created.py index 4d11cab84..e910c45d6 100644 --- a/rules/github_rules/github_public_repository_created.py +++ b/rules/github_rules/github_public_repository_created.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context def rule(event): diff --git a/rules/github_rules/github_repo_vulnerability_dismissed.py b/rules/github_rules/github_repo_vulnerability_dismissed.py index c81ea1446..e6b5f42bb 100644 --- a/rules/github_rules/github_repo_vulnerability_dismissed.py +++ b/rules/github_rules/github_repo_vulnerability_dismissed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context def rule(event): diff --git a/rules/github_rules/github_repository_transfer.py b/rules/github_rules/github_repository_transfer.py index 75ba3a06b..4782bb263 100644 --- a/rules/github_rules/github_repository_transfer.py +++ b/rules/github_rules/github_repository_transfer.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context def rule(event): diff --git a/rules/github_rules/github_webhook_modified.py b/rules/github_rules/github_webhook_modified.py index 18ce1f259..d1947b55f 100644 --- a/rules/github_rules/github_webhook_modified.py +++ b/rules/github_rules/github_webhook_modified.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import github_alert_context +from panther_github_helpers import github_alert_context def rule(event): diff --git a/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py b/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py index b45cb0ac3..68c13c0d8 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py +++ b/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py @@ -1,5 +1,5 @@ -from panther_base_helpers import gsuite_details_lookup as details_lookup -from panther_base_helpers import gsuite_parameter_lookup as param_lookup +from panther_gsuite_helpers import gsuite_details_lookup as details_lookup +from panther_gsuite_helpers import gsuite_parameter_lookup as param_lookup RESOURCE_CHANGE_EVENTS = { "create", diff --git a/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py b/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py index 75bf1902b..8671fafe0 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py +++ b/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py @@ -1,7 +1,7 @@ import json from unittest.mock import MagicMock -from panther_base_helpers import gsuite_parameter_lookup as param_lookup +from panther_gsuite_helpers import gsuite_parameter_lookup as param_lookup # Add any domain name(s) that you expect to share documents with in the ALLOWED_DOMAINS set ALLOWED_DOMAINS = set() diff --git a/rules/indicator_creation_rules/new_aws_account_logging.py b/rules/indicator_creation_rules/new_aws_account_logging.py index 96b9de3e2..251f6c545 100644 --- a/rules/indicator_creation_rules/new_aws_account_logging.py +++ b/rules/indicator_creation_rules/new_aws_account_logging.py @@ -2,8 +2,8 @@ from datetime import timedelta import panther_event_type_helpers as event_type +from panther_base_helpers import resolve_timestamp_string from panther_detection_helpers.caching import put_string_set -from panther_oss_helpers import resolve_timestamp_string # Days an account is considered new TTL = timedelta(days=3) diff --git a/rules/indicator_creation_rules/new_user_account_logging.py b/rules/indicator_creation_rules/new_user_account_logging.py index 9f7d563c7..8d6ce7487 100644 --- a/rules/indicator_creation_rules/new_user_account_logging.py +++ b/rules/indicator_creation_rules/new_user_account_logging.py @@ -1,8 +1,8 @@ from datetime import timedelta import panther_event_type_helpers as event_type +from panther_base_helpers import resolve_timestamp_string from panther_detection_helpers.caching import put_string_set -from panther_oss_helpers import resolve_timestamp_string # Days an account is considered new TTL = timedelta(days=3) diff --git a/rules/microsoft_rules/microsoft365_brute_force_login_by_user.py b/rules/microsoft_rules/microsoft365_brute_force_login_by_user.py index b9f5abe70..8453dbf0e 100644 --- a/rules/microsoft_rules/microsoft365_brute_force_login_by_user.py +++ b/rules/microsoft_rules/microsoft365_brute_force_login_by_user.py @@ -1,4 +1,4 @@ -from panther_base_helpers import m365_alert_context +from panther_msft_helpers import m365_alert_context def rule(event): diff --git a/rules/microsoft_rules/microsoft365_external_sharing.py b/rules/microsoft_rules/microsoft365_external_sharing.py index 860386eef..259f109b2 100644 --- a/rules/microsoft_rules/microsoft365_external_sharing.py +++ b/rules/microsoft_rules/microsoft365_external_sharing.py @@ -1,7 +1,7 @@ import re from fnmatch import fnmatch -from panther_base_helpers import m365_alert_context +from panther_msft_helpers import m365_alert_context email_regex = re.compile(r"([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+") diff --git a/rules/microsoft_rules/microsoft365_mfa_disabled.py b/rules/microsoft_rules/microsoft365_mfa_disabled.py index 3e71265c6..3f7eb866f 100644 --- a/rules/microsoft_rules/microsoft365_mfa_disabled.py +++ b/rules/microsoft_rules/microsoft365_mfa_disabled.py @@ -1,6 +1,6 @@ import json -from panther_base_helpers import m365_alert_context +from panther_msft_helpers import m365_alert_context def rule(event): diff --git a/rules/microsoft_rules/microsoft_graph_passthrough.py b/rules/microsoft_rules/microsoft_graph_passthrough.py index 64c07d111..8cde460b9 100644 --- a/rules/microsoft_rules/microsoft_graph_passthrough.py +++ b/rules/microsoft_rules/microsoft_graph_passthrough.py @@ -1,4 +1,4 @@ -from panther_base_helpers import msft_graph_alert_context +from panther_msft_helpers import msft_graph_alert_context def rule(event): diff --git a/rules/okta_rules/okta_admin_role_assigned.py b/rules/okta_rules/okta_admin_role_assigned.py index 4d21017e0..17500f9d4 100644 --- a/rules/okta_rules/okta_admin_role_assigned.py +++ b/rules/okta_rules/okta_admin_role_assigned.py @@ -1,6 +1,6 @@ import re -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context ADMIN_PATTERN = re.compile(r"[aA]dministrator") diff --git a/rules/okta_rules/okta_anonymizing_vpn_login.py b/rules/okta_rules/okta_anonymizing_vpn_login.py index 48cc62ca2..189b2ee0d 100644 --- a/rules/okta_rules/okta_anonymizing_vpn_login.py +++ b/rules/okta_rules/okta_anonymizing_vpn_login.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_api_key_created.py b/rules/okta_rules/okta_api_key_created.py index a4532ed01..4a6302b68 100644 --- a/rules/okta_rules/okta_api_key_created.py +++ b/rules/okta_rules/okta_api_key_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_api_key_revoked.py b/rules/okta_rules/okta_api_key_revoked.py index a24d47ef0..e9ab87dbe 100644 --- a/rules/okta_rules/okta_api_key_revoked.py +++ b/rules/okta_rules/okta_api_key_revoked.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_app_refresh_access_token_reuse.py b/rules/okta_rules/okta_app_refresh_access_token_reuse.py index a285b0260..2e136ad29 100644 --- a/rules/okta_rules/okta_app_refresh_access_token_reuse.py +++ b/rules/okta_rules/okta_app_refresh_access_token_reuse.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_app_unauthorized_access_attempt.py b/rules/okta_rules/okta_app_unauthorized_access_attempt.py index 57c8d9c04..2e7ebcf32 100644 --- a/rules/okta_rules/okta_app_unauthorized_access_attempt.py +++ b/rules/okta_rules/okta_app_unauthorized_access_attempt.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_group_admin_role_assigned.py b/rules/okta_rules/okta_group_admin_role_assigned.py index 2050c903c..bd986925b 100644 --- a/rules/okta_rules/okta_group_admin_role_assigned.py +++ b/rules/okta_rules/okta_group_admin_role_assigned.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_idp_create_modify.py b/rules/okta_rules/okta_idp_create_modify.py index 93902cfdd..6f8da716d 100644 --- a/rules/okta_rules/okta_idp_create_modify.py +++ b/rules/okta_rules/okta_idp_create_modify.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_idp_signin.py b/rules/okta_rules/okta_idp_signin.py index d9de91002..e9f5a10e5 100644 --- a/rules/okta_rules/okta_idp_signin.py +++ b/rules/okta_rules/okta_idp_signin.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_new_behavior_accessing_admin_console.py b/rules/okta_rules/okta_new_behavior_accessing_admin_console.py index 40def84b8..416b14539 100644 --- a/rules/okta_rules/okta_new_behavior_accessing_admin_console.py +++ b/rules/okta_rules/okta_new_behavior_accessing_admin_console.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_org2org_creation_modification.py b/rules/okta_rules/okta_org2org_creation_modification.py index 0605bad09..a55d56b31 100644 --- a/rules/okta_rules/okta_org2org_creation_modification.py +++ b/rules/okta_rules/okta_org2org_creation_modification.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context APP_LIFECYCLE_EVENTS = ( "application.lifecycle.update", diff --git a/rules/okta_rules/okta_password_extraction_via_scim.py b/rules/okta_rules/okta_password_extraction_via_scim.py index b15d77c02..1f72f2978 100644 --- a/rules/okta_rules/okta_password_extraction_via_scim.py +++ b/rules/okta_rules/okta_password_extraction_via_scim.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py b/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py index 57834e399..c97a1d20c 100644 --- a/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py +++ b/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_potentially_stolen_session.py b/rules/okta_rules/okta_potentially_stolen_session.py index b2eb15d78..3aca82cb1 100644 --- a/rules/okta_rules/okta_potentially_stolen_session.py +++ b/rules/okta_rules/okta_potentially_stolen_session.py @@ -2,8 +2,8 @@ from datetime import timedelta from difflib import SequenceMatcher -from panther_base_helpers import okta_alert_context from panther_detection_helpers.caching import get_string_set, put_string_set +from panther_okta_helpers import okta_alert_context FUZZ_RATIO_MIN = 0.95 PREVIOUS_SESSION = {} diff --git a/rules/okta_rules/okta_rate_limits.py b/rules/okta_rules/okta_rate_limits.py index 74e33730f..13c1b828e 100644 --- a/rules/okta_rules/okta_rate_limits.py +++ b/rules/okta_rules/okta_rate_limits.py @@ -1,6 +1,6 @@ from fnmatch import fnmatch -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context DETECTION_EVENTS = [ "app.oauth2.client_id_rate_limit_warning", diff --git a/rules/okta_rules/okta_support_reset.py b/rules/okta_rules/okta_support_reset.py index a3737e201..afb1461c2 100644 --- a/rules/okta_rules/okta_support_reset.py +++ b/rules/okta_rules/okta_support_reset.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context OKTA_SUPPORT_RESET_EVENTS = [ "user.account.reset_password", diff --git a/rules/okta_rules/okta_threatinsight_security_threat_detected.py b/rules/okta_rules/okta_threatinsight_security_threat_detected.py index a0abf16aa..44a01ce81 100644 --- a/rules/okta_rules/okta_threatinsight_security_threat_detected.py +++ b/rules/okta_rules/okta_threatinsight_security_threat_detected.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def severity_from_threat_string(threat_detection): diff --git a/rules/okta_rules/okta_user_account_locked.py b/rules/okta_rules/okta_user_account_locked.py index 31e23873a..b5f21d187 100644 --- a/rules/okta_rules/okta_user_account_locked.py +++ b/rules/okta_rules/okta_user_account_locked.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_user_mfa_factor_suspend.py b/rules/okta_rules/okta_user_mfa_factor_suspend.py index 4fecc3878..8d8d5ea17 100644 --- a/rules/okta_rules/okta_user_mfa_factor_suspend.py +++ b/rules/okta_rules/okta_user_mfa_factor_suspend.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_user_mfa_reset.py b/rules/okta_rules/okta_user_mfa_reset.py index 979859934..d8bbe1a49 100644 --- a/rules/okta_rules/okta_user_mfa_reset.py +++ b/rules/okta_rules/okta_user_mfa_reset.py @@ -1,5 +1,5 @@ import panther_event_type_helpers as event_type -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_user_mfa_reset_all.py b/rules/okta_rules/okta_user_mfa_reset_all.py index 71d95eae6..f99b20c57 100644 --- a/rules/okta_rules/okta_user_mfa_reset_all.py +++ b/rules/okta_rules/okta_user_mfa_reset_all.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/okta_rules/okta_user_reported_suspicious_activity.py b/rules/okta_rules/okta_user_reported_suspicious_activity.py index 36871b624..576f0d82a 100644 --- a/rules/okta_rules/okta_user_reported_suspicious_activity.py +++ b/rules/okta_rules/okta_user_reported_suspicious_activity.py @@ -1,4 +1,4 @@ -from panther_base_helpers import okta_alert_context +from panther_okta_helpers import okta_alert_context def rule(event): diff --git a/rules/slack_rules/slack_app_access_expanded.py b/rules/slack_rules/slack_app_access_expanded.py index c93d9dd01..11477855b 100644 --- a/rules/slack_rules/slack_app_access_expanded.py +++ b/rules/slack_rules/slack_app_access_expanded.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context ACCESS_EXPANDED_ACTIONS = [ "app_scopes_expanded", diff --git a/rules/slack_rules/slack_app_added.py b/rules/slack_rules/slack_app_added.py index d57357ff6..0289fe0e2 100644 --- a/rules/slack_rules/slack_app_added.py +++ b/rules/slack_rules/slack_app_added.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context APP_ADDED_ACTIONS = [ "app_approved", diff --git a/rules/slack_rules/slack_app_removed.py b/rules/slack_rules/slack_app_removed.py index 7583404f9..d1576d137 100644 --- a/rules/slack_rules/slack_app_removed.py +++ b/rules/slack_rules/slack_app_removed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context APP_REMOVED_ACTIONS = [ "app_restricted", diff --git a/rules/slack_rules/slack_application_dos.py b/rules/slack_rules/slack_application_dos.py index ca37d82a2..3f6403394 100644 --- a/rules/slack_rules/slack_application_dos.py +++ b/rules/slack_rules/slack_application_dos.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context DENIAL_OF_SERVICE_ACTIONS = [ "bulk_session_reset_by_admin", diff --git a/rules/slack_rules/slack_dlp_modified.py b/rules/slack_rules/slack_dlp_modified.py index f277a149f..43856f8d5 100644 --- a/rules/slack_rules/slack_dlp_modified.py +++ b/rules/slack_rules/slack_dlp_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context DLP_ACTIONS = [ "native_dlp_rule_deactivated", diff --git a/rules/slack_rules/slack_ekm_config_changed.py b/rules/slack_rules/slack_ekm_config_changed.py index 996f245b3..8ba1c8cdb 100644 --- a/rules/slack_rules/slack_ekm_config_changed.py +++ b/rules/slack_rules/slack_ekm_config_changed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_ekm_slackbot_unenrolled.py b/rules/slack_rules/slack_ekm_slackbot_unenrolled.py index 71459da86..6f3684a56 100644 --- a/rules/slack_rules/slack_ekm_slackbot_unenrolled.py +++ b/rules/slack_rules/slack_ekm_slackbot_unenrolled.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_ekm_unenrolled.py b/rules/slack_rules/slack_ekm_unenrolled.py index 7b5d8e1a4..8d70f0b53 100644 --- a/rules/slack_rules/slack_ekm_unenrolled.py +++ b/rules/slack_rules/slack_ekm_unenrolled.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_idp_configuration_change.py b/rules/slack_rules/slack_idp_configuration_change.py index 62bc69312..a2b776ada 100644 --- a/rules/slack_rules/slack_idp_configuration_change.py +++ b/rules/slack_rules/slack_idp_configuration_change.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context IDP_CHANGE_ACTIONS = { "idp_configuration_added": "Slack IDP Configuration Added", diff --git a/rules/slack_rules/slack_information_barrier_modified.py b/rules/slack_rules/slack_information_barrier_modified.py index ee1a45a77..97d3277f2 100644 --- a/rules/slack_rules/slack_information_barrier_modified.py +++ b/rules/slack_rules/slack_information_barrier_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context INFORMATION_BARRIER_ACTIONS = { "barrier_deleted": "Slack Information Barrier Deleted", diff --git a/rules/slack_rules/slack_intune_mdm_disabled.py b/rules/slack_rules/slack_intune_mdm_disabled.py index ed6187d13..180561851 100644 --- a/rules/slack_rules/slack_intune_mdm_disabled.py +++ b/rules/slack_rules/slack_intune_mdm_disabled.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_legal_hold_policy_modified.py b/rules/slack_rules/slack_legal_hold_policy_modified.py index afb4f401f..03c1e0525 100644 --- a/rules/slack_rules/slack_legal_hold_policy_modified.py +++ b/rules/slack_rules/slack_legal_hold_policy_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context LEGAL_HOLD_POLICY_ACTIONS = { "legal_hold_policy_entities_deleted": "Slack Legal Hold Policy Entities Deleted", diff --git a/rules/slack_rules/slack_mfa_settings_changed.py b/rules/slack_rules/slack_mfa_settings_changed.py index 5f4c4213d..d51bc7ef9 100644 --- a/rules/slack_rules/slack_mfa_settings_changed.py +++ b/rules/slack_rules/slack_mfa_settings_changed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_org_created.py b/rules/slack_rules/slack_org_created.py index 4dff39c6b..f7d2da568 100644 --- a/rules/slack_rules/slack_org_created.py +++ b/rules/slack_rules/slack_org_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_org_deleted.py b/rules/slack_rules/slack_org_deleted.py index ba6557b94..949a70244 100644 --- a/rules/slack_rules/slack_org_deleted.py +++ b/rules/slack_rules/slack_org_deleted.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_passthrough_anomaly.py b/rules/slack_rules/slack_passthrough_anomaly.py index a49e7e32c..6984cb386 100644 --- a/rules/slack_rules/slack_passthrough_anomaly.py +++ b/rules/slack_rules/slack_passthrough_anomaly.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context ELEVATED_ANOMALIES = {"excessive_malware_uploads", "session_fingerprint", "unexpected_admin_action"} diff --git a/rules/slack_rules/slack_potentially_malicious_file_shared.py b/rules/slack_rules/slack_potentially_malicious_file_shared.py index 5e80e9df4..33434ef65 100644 --- a/rules/slack_rules/slack_potentially_malicious_file_shared.py +++ b/rules/slack_rules/slack_potentially_malicious_file_shared.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_private_channel_made_public.py b/rules/slack_rules/slack_private_channel_made_public.py index 02796c3d7..ff7ee7223 100644 --- a/rules/slack_rules/slack_private_channel_made_public.py +++ b/rules/slack_rules/slack_private_channel_made_public.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_privilege_changed_to_user.py b/rules/slack_rules/slack_privilege_changed_to_user.py index 44e52a976..4ebb7c810 100644 --- a/rules/slack_rules/slack_privilege_changed_to_user.py +++ b/rules/slack_rules/slack_privilege_changed_to_user.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_service_owner_transferred.py b/rules/slack_rules/slack_service_owner_transferred.py index 08ca6e875..082a2a808 100644 --- a/rules/slack_rules/slack_service_owner_transferred.py +++ b/rules/slack_rules/slack_service_owner_transferred.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_sso_settings_changed.py b/rules/slack_rules/slack_sso_settings_changed.py index fe2eb4236..21eaf2e4d 100644 --- a/rules/slack_rules/slack_sso_settings_changed.py +++ b/rules/slack_rules/slack_sso_settings_changed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context def rule(event): diff --git a/rules/slack_rules/slack_user_privilege_escalation.py b/rules/slack_rules/slack_user_privilege_escalation.py index 09ae231b4..ff8b8550d 100644 --- a/rules/slack_rules/slack_user_privilege_escalation.py +++ b/rules/slack_rules/slack_user_privilege_escalation.py @@ -1,4 +1,4 @@ -from panther_base_helpers import slack_alert_context +from panther_slack_helpers import slack_alert_context USER_PRIV_ESC_ACTIONS = { "owner_transferred": "Slack Owner Transferred", diff --git a/rules/standard_rules/brute_force_by_ip.py b/rules/standard_rules/brute_force_by_ip.py index 144bea770..29de37ee2 100644 --- a/rules/standard_rules/brute_force_by_ip.py +++ b/rules/standard_rules/brute_force_by_ip.py @@ -1,9 +1,9 @@ from json import loads import panther_event_type_helpers as event_type -from panther_default import lookup_aws_account_name +from panther_aws_helpers import lookup_aws_account_name +from panther_base_helpers import add_parse_delay from panther_ipinfo_helpers import PantherIPInfoException, geoinfo_from_ip -from panther_oss_helpers import add_parse_delay def rule(event): diff --git a/rules/standard_rules/impossible_travel_login.py b/rules/standard_rules/impossible_travel_login.py index f5b2694e2..29661e17f 100644 --- a/rules/standard_rules/impossible_travel_login.py +++ b/rules/standard_rules/impossible_travel_login.py @@ -2,10 +2,10 @@ from json import dumps, loads import panther_event_type_helpers as event_type -from panther_base_helpers import deep_get +from panther_base_helpers import deep_get, resolve_timestamp_string from panther_detection_helpers.caching import get_string_set, put_string_set +from panther_ipinfo_helpers import km_between_ipinfo_loc from panther_lookuptable_helpers import LookupTableMatches -from panther_oss_helpers import km_between_ipinfo_loc, resolve_timestamp_string # pylint: disable=global-variable-undefined diff --git a/rules/standard_rules/malicious_sso_dns_lookup.py b/rules/standard_rules/malicious_sso_dns_lookup.py index a6b88d9ae..692804807 100644 --- a/rules/standard_rules/malicious_sso_dns_lookup.py +++ b/rules/standard_rules/malicious_sso_dns_lookup.py @@ -11,7 +11,7 @@ 5. Run a Data Replay test to identify unknown domains that should be in ALLOWED_DOMAINS """ -from panther_base_helpers import filter_crowdstrike_fdr_event_type +from panther_crowdstrike_fdr_helpers import filter_crowdstrike_fdr_event_type # *** Change this to match your company name *** COMPANY_NAME = "company_name_here" diff --git a/rules/zendesk_rules/zendesk_mobile_app_access.py b/rules/zendesk_rules/zendesk_mobile_app_access.py index 783e3ff1d..78eda3864 100644 --- a/rules/zendesk_rules/zendesk_mobile_app_access.py +++ b/rules/zendesk_rules/zendesk_mobile_app_access.py @@ -1,4 +1,4 @@ -from panther_base_helpers import ZENDESK_CHANGE_DESCRIPTION +from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION MOBILE_APP_ACTIONS = {"create", "update"} diff --git a/rules/zendesk_rules/zendesk_new_owner.py b/rules/zendesk_rules/zendesk_new_owner.py index 052544422..42714c2c0 100644 --- a/rules/zendesk_rules/zendesk_new_owner.py +++ b/rules/zendesk_rules/zendesk_new_owner.py @@ -1,6 +1,6 @@ import re -from panther_base_helpers import ZENDESK_CHANGE_DESCRIPTION +from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION ZENDESK_OWNER_CHANGED = re.compile( r"Owner changed from (?P.+) to (?P[^$]+)", re.IGNORECASE diff --git a/rules/zendesk_rules/zendesk_sensitive_data_redaction.py b/rules/zendesk_rules/zendesk_sensitive_data_redaction.py index 850e51c2d..c75bc8b0e 100644 --- a/rules/zendesk_rules/zendesk_sensitive_data_redaction.py +++ b/rules/zendesk_rules/zendesk_sensitive_data_redaction.py @@ -1,4 +1,4 @@ -from panther_base_helpers import ZENDESK_CHANGE_DESCRIPTION +from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION REDACTION_ACTIONS = { "create", diff --git a/rules/zendesk_rules/zendesk_user_role.py b/rules/zendesk_rules/zendesk_user_role.py index bcd319db5..c7efa038f 100644 --- a/rules/zendesk_rules/zendesk_user_role.py +++ b/rules/zendesk_rules/zendesk_user_role.py @@ -1,5 +1,5 @@ import panther_event_type_helpers as event_type -from panther_base_helpers import zendesk_get_roles +from panther_zendesk_helpers import zendesk_get_roles def rule(event): diff --git a/rules/zendesk_rules/zendesk_user_suspension.py b/rules/zendesk_rules/zendesk_user_suspension.py index cb056ef2a..724d8495f 100644 --- a/rules/zendesk_rules/zendesk_user_suspension.py +++ b/rules/zendesk_rules/zendesk_user_suspension.py @@ -1,4 +1,4 @@ -from panther_base_helpers import ZENDESK_CHANGE_DESCRIPTION +from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION USER_SUSPENSION_ACTIONS = { "create", From f64ef48102952382e386d6498eeebd809abd3699 Mon Sep 17 00:00:00 2001 From: akozlovets098 <95437895+akozlovets098@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:49:25 +0300 Subject: [PATCH 2/8] THREAT-395 Correlation Rule Style Guide in repo (#1376) --- style_guides/CORRELATION_RULES_STYLE_GUIDE.md | 69 +++++++++++++++++++ STYLE_GUIDE.md => style_guides/STYLE_GUIDE.md | 1 + 2 files changed, 70 insertions(+) create mode 100644 style_guides/CORRELATION_RULES_STYLE_GUIDE.md rename STYLE_GUIDE.md => style_guides/STYLE_GUIDE.md (95%) diff --git a/style_guides/CORRELATION_RULES_STYLE_GUIDE.md b/style_guides/CORRELATION_RULES_STYLE_GUIDE.md new file mode 100644 index 000000000..947bdd102 --- /dev/null +++ b/style_guides/CORRELATION_RULES_STYLE_GUIDE.md @@ -0,0 +1,69 @@ +# panther-analysis Correlation Rule Style Guide and Best Practices + +This style guide highlights essential best practices for writing correlation rules (CRs). For a more detailed guide, visit [Correlation Rules](https://docs.panther.com/detections/correlation-rules) in the Panther documentation. +This guide provides specialized guidelines on writing CRs, which build upon the general detection writing best practices outlined in [STYLE_GUIDE](https://github.com/panther-labs/panther-analysis/style_guides/STYLE_GUIDE.md) + +## General guidelines + +- In a rule ID or transition ID, when describing a transition, put that portion of the ID in all-caps + - e.g. `"GitHub Advanced Security Change NOT FOLLOWED BY repo archived"` +- Boilerplate comments from the CR template in the UI should be removed before committing to PA + - e.g. remove `# Create a list of rules to correlate` +- Sequence, group, and transition IDs should have meaningful names + - e.g. `- ID: GHASChange` instead of `- ID: TR.1` +- Check for Signal rules that already exist + - e.g. we do not want duplicates of `AWS Console Login` rules for every CR that relies on that signal + - for the guide on Signal rules, please visit [How to create a rule that only produces signals](https://docs.panther.com/detections/correlation-rules/signals#signal-use-cases:~:text=Data%20Explorer.-,How%20to%20create%20a%20rule%20that%20only%20produces%20signals,-To%20create%20a) +- Correlation rules go in `correlation_rules` directory, subrules and signals go in the appropriate logtype directory + +## Correlation rule fields + +### MinMatchCount + +- If you are setting `MinMatchCount` to `1`, you can omit it completely, as `1` is the default value. + +### LookbackWindowMinutes + +- For ease of understanding, it's valuable to include `LookbackWindowMinutes` even if you are setting it to `15`, which is the default value. +- `LookbackWindowMinutes` should be *at least* 1.5x `RateMinutes` + +### WithinTimeFrameMinutes + +- `WithinTimeFrameMinutes` is not required and defaults to `LookbackWindowMinutes` +- Omit `WithinTimeFrameMinutes` in sequences with only two rules + +### Match On + +- `Match On` fields are not required to be `p_` fields. Especially for CRs where each subrule is for the same LogType, use the regular field instead of the alert context field + - e.g. `- On: repo` instead of `- On: p_alert_context.repo` +- You cannot match on fields that are JSON objects + - For rules that span multiple LogTypes, where fields must be transformed, add the transformed fields to `p_alert_context` + - List on list `Match On` criteria works, using an intersection, so if you want to match if RuleA field with RuleB list, map it in alert_context to `[RuleA.value]` , `[RuleB.value1, RuleB.value2]` +- You can match on different field names across multiple rules/transitions, but they all must share the same value. You cannot match on different values across multiple rules/transitions + + ```yaml + # This is supported + 1: { fieldA: val1 } + 2: { fieldA: val1, fieldB: val1 } + 3: { fieldB: val1} + ``` + ```yaml + # This is not supported + 1: { fieldA: val1 } + 2: { fieldA: val1, fieldB: val2 } + 3: { fieldB: val2 } + ``` + +## Before using correlation rules + +### Testing + +To test your correlation rules before uploading them, run `pat validate` against a live Panther instance. Running `pat test` is not sufficient. + +### Placing CRs in packs + +- If a CR only references one LogType store it in that LogType’s pack + - If a CR spans multiple LogTypes, put it in the multi-logtype pack + - Customers are advised not to enable multi-LogType packs, and to instead enable the individual detections within them that they have log sources set up for + - A pack with CRs should also contain the subrules referenced by those CRs + - AND any globals, data models, etc. that the subrules reference diff --git a/STYLE_GUIDE.md b/style_guides/STYLE_GUIDE.md similarity index 95% rename from STYLE_GUIDE.md rename to style_guides/STYLE_GUIDE.md index 85605b873..9a93b1391 100644 --- a/STYLE_GUIDE.md +++ b/style_guides/STYLE_GUIDE.md @@ -1,6 +1,7 @@ # panther-analysis Style Guide This style guide highlights essential best practices for writing python rules and alert metadata. For a more detailed guide, visit [Writing Python Detections](https://docs.panther.com/detections/rules/python) in the Panther documentation. +For specialized guidelines on writing correlation rules, see [CORRELATION_RULES_STYLE_GUIDE](https://github.com/panther-labs/panther-analysis/blob/style_guides/CORRELATION_RULES_STYLE_GUIDE.md) ## Metadata best practices From dec3aea81c452b000602b3c51d775afce17a83ef Mon Sep 17 00:00:00 2001 From: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:56:55 -0600 Subject: [PATCH 3/8] ThinkstCanary Rules (#1391) Co-authored-by: akozlovets098 <95437895+akozlovets098@users.noreply.github.com> Co-authored-by: Jack Naglieri Co-authored-by: Panos Sakkos --- .../panther_thinkstcanary_helpers.py | 3 ++ .../panther_thinkstcanary_helpers.yml | 5 +++ packs/thinkstcanary.yml | 11 +++++ .../thinkst_canary_dcrc.py | 12 +++++ .../thinkst_canary_dcrc.yml | 25 +++++++++++ .../thinkst_canary_incident.py | 13 ++++++ .../thinkst_canary_incident.yml | 39 ++++++++++++++++ .../thinkst_canarytoken_incident.py | 13 ++++++ .../thinkst_canarytoken_incident.yml | 45 +++++++++++++++++++ 9 files changed, 166 insertions(+) create mode 100644 global_helpers/panther_thinkstcanary_helpers.py create mode 100644 global_helpers/panther_thinkstcanary_helpers.yml create mode 100644 packs/thinkstcanary.yml create mode 100644 rules/thinkstcanary_rules/thinkst_canary_dcrc.py create mode 100644 rules/thinkstcanary_rules/thinkst_canary_dcrc.yml create mode 100644 rules/thinkstcanary_rules/thinkst_canary_incident.py create mode 100644 rules/thinkstcanary_rules/thinkst_canary_incident.yml create mode 100644 rules/thinkstcanary_rules/thinkst_canarytoken_incident.py create mode 100644 rules/thinkstcanary_rules/thinkst_canarytoken_incident.yml diff --git a/global_helpers/panther_thinkstcanary_helpers.py b/global_helpers/panther_thinkstcanary_helpers.py new file mode 100644 index 000000000..7cbf679dc --- /dev/null +++ b/global_helpers/panther_thinkstcanary_helpers.py @@ -0,0 +1,3 @@ +def additional_details(event): + details = event.get("AdditionalDetails", []) + return {detail[0]: detail[-1] for detail in details} diff --git a/global_helpers/panther_thinkstcanary_helpers.yml b/global_helpers/panther_thinkstcanary_helpers.yml new file mode 100644 index 000000000..d58eec697 --- /dev/null +++ b/global_helpers/panther_thinkstcanary_helpers.yml @@ -0,0 +1,5 @@ +AnalysisType: global +Filename: panther_thinkstcanary_helpers.py +GlobalID: "panther_thinkstcanary_helpers" +Description: > + Global helpers for ThinkstCanary detections diff --git a/packs/thinkstcanary.yml b/packs/thinkstcanary.yml new file mode 100644 index 000000000..e719a07ed --- /dev/null +++ b/packs/thinkstcanary.yml @@ -0,0 +1,11 @@ +AnalysisType: pack +PackID: PantherManaged.ThinkstCanary +Description: Group of all ThinkstCanary detections +PackDefinition: + IDs: + - Thinkst.CanaryDCRC + - Thinkst.CanaryIncident + - Thinkst.CanaryTokenIncident + # Globals used in these detections + - panther_thinkstcanary_helpers +DisplayName: "Panther ThinkstCanary Pack" diff --git a/rules/thinkstcanary_rules/thinkst_canary_dcrc.py b/rules/thinkstcanary_rules/thinkst_canary_dcrc.py new file mode 100644 index 000000000..657dc1c17 --- /dev/null +++ b/rules/thinkstcanary_rules/thinkst_canary_dcrc.py @@ -0,0 +1,12 @@ +def rule(event): + return any(keyword in event.get("Intro", "") for keyword in ["disconnected", "reconnected"]) + + +def title(event): + return event.get("Intro", "Canary Disconnected/Reconnected") + + +def severity(event): + if "reconnected" in event.get("Intro", ""): + return "Low" + return "Default" diff --git a/rules/thinkstcanary_rules/thinkst_canary_dcrc.yml b/rules/thinkstcanary_rules/thinkst_canary_dcrc.yml new file mode 100644 index 000000000..4bda06fa8 --- /dev/null +++ b/rules/thinkstcanary_rules/thinkst_canary_dcrc.yml @@ -0,0 +1,25 @@ +AnalysisType: rule +Filename: thinkst_canary_dcrc.py +DisplayName: Thinkst Canary DCRC +RuleID: Thinkst.CanaryDCRC +Description: "A Canary has disconnected/reconnected." +Enabled: true +Severity: High +LogTypes: + - ThinkstCanary.Alert +DedupPeriodMinutes: 60 +Threshold: 1 +Tests: + - ExpectedResult: true + Name: Canary Disconnected + Log: + { + "CanaryID": "00029666d14d454f", + "CanaryIP": "192.168.20.101", + "CanaryName": "FS01", + "Description": "Canary Disconnected", + "IncidentKey": "incident:devicedied:3b04b62c54dcbb64d17131be::1718794923", + "Intro": "One of your Canaries (FS01) previously at 192.168.20.101 has disconnected.", + "MatchedAnnotations": {}, + "Timestamp": "2024-06-19 11:02:03 (UTC)", + } diff --git a/rules/thinkstcanary_rules/thinkst_canary_incident.py b/rules/thinkstcanary_rules/thinkst_canary_incident.py new file mode 100644 index 000000000..f46407825 --- /dev/null +++ b/rules/thinkstcanary_rules/thinkst_canary_incident.py @@ -0,0 +1,13 @@ +from panther_thinkstcanary_helpers import additional_details + + +def rule(event): + return event.get("AlertType") == "CanaryIncident" + + +def title(event): + return event.get("Intro", "Canary Incident") + + +def alert_context(event): + return additional_details(event) diff --git a/rules/thinkstcanary_rules/thinkst_canary_incident.yml b/rules/thinkstcanary_rules/thinkst_canary_incident.yml new file mode 100644 index 000000000..9e81e1f82 --- /dev/null +++ b/rules/thinkstcanary_rules/thinkst_canary_incident.yml @@ -0,0 +1,39 @@ +AnalysisType: rule +Filename: thinkst_canary_incident.py +DisplayName: Thinkst Canary Incident +RuleID: Thinkst.CanaryIncident +Description: "A Canary incident has been detected." +Enabled: true +Severity: High +LogTypes: + - ThinkstCanary.Alert +DedupPeriodMinutes: 60 +Threshold: 1 +Tests: + - ExpectedResult: true + Name: Canary Incident + Log: + { + "AdditionalDetails": + [ + ["User", "guest"], + ["Filename", "IT/Default Windows Desktop Configuration.docx"], + [ + "Background Context", + "You have had 2 incidents from 192.168.110.14 previously.", + ], + ], + "AlertType": "CanaryIncident", + "CanaryID": "000222326791e1e8", + "CanaryIP": "192.168.110.27", + "CanaryLocation": "Server room A", + "CanaryName": "VirtualCanary-unnamed", + "CanaryPort": 445, + "Description": "Shared File Opened", + "IncidentHash": "f78b692a7716d0d668012bc0eb65c367", + "IncidentKey": "incident:smbfileopen:89d38322e4e764e202b42bbb:192.168.110.14:1717059335", + "Intro": "Shared File Opened has been detected against one of your Canaries (VirtualCanary-unnamed) at 192.168.110.27.", + "ReverseDNS": "", + "SourceIP": "192.168.110.14", + "Timestamp": "2024-05-30 08:55:35 (UTC)", + } diff --git a/rules/thinkstcanary_rules/thinkst_canarytoken_incident.py b/rules/thinkstcanary_rules/thinkst_canarytoken_incident.py new file mode 100644 index 000000000..6d911b1f1 --- /dev/null +++ b/rules/thinkstcanary_rules/thinkst_canarytoken_incident.py @@ -0,0 +1,13 @@ +from panther_thinkstcanary_helpers import additional_details + + +def rule(event): + return event.get("AlertType") == "CanarytokenIncident" + + +def title(event): + return event.get("Intro", "Canary Token Incident") + + +def alert_context(event): + return additional_details(event) diff --git a/rules/thinkstcanary_rules/thinkst_canarytoken_incident.yml b/rules/thinkstcanary_rules/thinkst_canarytoken_incident.yml new file mode 100644 index 000000000..008f35cb2 --- /dev/null +++ b/rules/thinkstcanary_rules/thinkst_canarytoken_incident.yml @@ -0,0 +1,45 @@ +AnalysisType: rule +Filename: thinkst_canarytoken_incident.py +DisplayName: Thinkst Canarytoken Incident +RuleID: Thinkst.CanaryTokenIncident +Description: "A Canarytoken incident has been detected." +Enabled: true +Severity: High +LogTypes: + - ThinkstCanary.Alert +DedupPeriodMinutes: 60 +Threshold: 1 +Tests: + - ExpectedResult: true + Name: Canarytoken Incident + Log: + { + "AdditionalDetails": + [ + [ + "Background Context", + "You have had 4 incidents from 123.123.123.123 previously.", + ], + ["Dst Port", 80], + ["Event Name", "GetCallerIdentity"], + ["User-Agent", "TruffleHog"], + ], + "AlertType": "CanarytokenIncident", + "Description": "AWS API Key Canarytoken triggered", + "IncidentHash": "79cb967bde35e3b2d3b346844c16c4bf", + "IncidentKey": "incident:canarytoken:94e08d45e5f2c8c13e7b99ae:123.123.123.123:1718797361", + "Intro": "An AWS API Key Canarytoken was triggered by '123.123.123.123'.", + "MatchedAnnotations": + { + "trufflehog_scan": + [ + "This looks like a TruffleHog scan.", + "https://help.canary.tools/hc/en-gb/articles/18185364902813-Alert-Annotation-TruffleHog-Scan", + ], + }, + "Reminder": "aws api key inside keepass", + "SourceIP": "123.123.123.123", + "Timestamp": "2024-06-19 11:42:41 (UTC)", + "Token": "jf15ldk2jeaooi8dhlc6rgt9g", + "Triggered": "2", + } From 3c0205f0ea11bc649e78d3db803612540e08e224 Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:09:35 -0500 Subject: [PATCH 4/8] Migrate `AthenaQuery` and `SnowflakeQuery` to just `Query` (#1392) Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .../cloudtrail_password_spraying_query.yml | 5 +--- .../ec2_crud_activity_by_role_query.yml | 5 +--- .../ec2_crud_activity_by_useragent_query.yml | 5 +--- .../aws_queries/vpc_dns_tunneling_query.yml | 5 +--- queries/okta_queries/okta_activity_audit.yml | 12 +-------- .../okta_admin_access_granted.yml | 25 +------------------ .../okta_mfa_password_reset_audit.yml | 10 +------- .../okta_queries/okta_session_id_audit.yml | 19 +------------- queries/okta_queries/okta_support_access.yml | 22 +--------------- ...lake_0108977_configuration_drift_query.yml | 2 +- ...977_configuration_drift_threat_hunting.yml | 2 +- .../snowflake_0108977_ip_query.yml | 2 +- .../snowflake_0108977_ip_threat_hunting.yml | 2 +- ...ke_0108977_suspected_user_access_query.yml | 2 +- ...7_suspected_user_access_threat_hunting.yml | 2 +- ...suspected_user_activity_threat_hunting.yml | 2 +- ...snowflake_account_admin_assigned_query.yml | 5 +--- .../snowflake_brute_force_ip_query.yml | 5 +--- .../snowflake_brute_force_username_query.yml | 5 +--- .../snowflake_external_shares_query.yml | 5 +--- ...nowflake_key_user_password_login_query.yml | 5 +--- .../snowflake_login_without_mfa_query.yml | 5 +--- ...ailed_logins_followed_by_success_query.yml | 5 +--- .../snowflake_user_created_query.yml | 5 +--- .../snowflake_user_enabled_query.yml | 5 +--- templates/example_scheduled_query.yml | 7 +----- 26 files changed, 26 insertions(+), 148 deletions(-) diff --git a/queries/aws_queries/cloudtrail_password_spraying_query.yml b/queries/aws_queries/cloudtrail_password_spraying_query.yml index 5d0e40ebf..094f67214 100644 --- a/queries/aws_queries/cloudtrail_password_spraying_query.yml +++ b/queries/aws_queries/cloudtrail_password_spraying_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.CloudTrail.Password.Spraying" Enabled: false Description: > Detect password spraying in cloudtrail logs -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | SELECT -- this information will be in the alert events awsRegion as region, diff --git a/queries/aws_queries/ec2_crud_activity_by_role_query.yml b/queries/aws_queries/ec2_crud_activity_by_role_query.yml index 23ae912a7..7f64e9739 100644 --- a/queries/aws_queries/ec2_crud_activity_by_role_query.yml +++ b/queries/aws_queries/ec2_crud_activity_by_role_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.EC2.CRUD.Activity.Role" Enabled: false Description: > This query searches for CRUD activity in EC2 by role arn. Activities from a role outside typical deployment processes may warrant investigation. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | SELECT count(*) as num_logs, recipientAccountId, diff --git a/queries/aws_queries/ec2_crud_activity_by_useragent_query.yml b/queries/aws_queries/ec2_crud_activity_by_useragent_query.yml index 9ccdf906d..cbbb78739 100644 --- a/queries/aws_queries/ec2_crud_activity_by_useragent_query.yml +++ b/queries/aws_queries/ec2_crud_activity_by_useragent_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.EC2.CRUD.Activity.Useragent" Enabled: false Description: > This query searches for CRUD activity in EC2 by userAgent. A low count or previously unseen useragent may indicate that the action was not performed by an automated process. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | SELECT count(*) as num_logs, recipientAccountId, diff --git a/queries/aws_queries/vpc_dns_tunneling_query.yml b/queries/aws_queries/vpc_dns_tunneling_query.yml index 4d35cb318..012611c20 100644 --- a/queries/aws_queries/vpc_dns_tunneling_query.yml +++ b/queries/aws_queries/vpc_dns_tunneling_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.VPC.DNS.Tunneling" Enabled: false Description: > Detect activity similar to DNS tunneling traffic in AWS VPC Logs -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | SELECT account_id, region, diff --git a/queries/okta_queries/okta_activity_audit.yml b/queries/okta_queries/okta_activity_audit.yml index 12dbcda8f..bab9d0cc5 100644 --- a/queries/okta_queries/okta_activity_audit.yml +++ b/queries/okta_queries/okta_activity_audit.yml @@ -3,17 +3,7 @@ QueryName: "Okta Investigate User Activity" Enabled: false Description: > Audit user activity across your environment. Customize to filter on specific users, time ranges, etc -AthenaQuery: | - SELECT actor.displayName AS actor_name, actor.alternateId AS actor_email, eventType, COUNT(*) AS activity_count - FROM panther_logs.okta_systemlog - WHERE p_occurs_since('7 days') - AND actor.type = 'User' - -- Uncomment lines below to filter by user email and/or eventType - -- and actor_email = '' - -- and eventType = '' - GROUP BY actor.displayName, actor.alternateId, eventType - ORDER BY actor_name, activity_count DESC -SnowflakeQuery: | +Query: | SELECT actor:displayName AS actor_name, actor:alternateId AS actor_email, eventType, COUNT(*) AS activity_count FROM panther_logs.public.okta_systemlog WHERE p_occurs_since('7 days') diff --git a/queries/okta_queries/okta_admin_access_granted.yml b/queries/okta_queries/okta_admin_access_granted.yml index dbd7068df..9b21ed05e 100644 --- a/queries/okta_queries/okta_admin_access_granted.yml +++ b/queries/okta_queries/okta_admin_access_granted.yml @@ -3,30 +3,7 @@ QueryName: "Okta Admin Access Granted" Enabled: false Description: > Audit instances of admin access granted in your okta tenant -AthenaQuery: | - SELECT - p_event_time as event_time, - actor.alternateid as actor_email, - actor.displayName as actor_name, - displayMessage, - eventType, - json_extract(debugcontext.debugdata, '$.privilegeGranted') as priv_granted, - target as target_name, - client.ipAddress as src_ip, - client.geographicalContext.city as city, - client.geographicalContext.country as country, - client.useragent.rawUserAgent as user_agent - FROM panther_logs.okta_systemlog - WHERE - ( - eventType = 'user.account.privilege.grant' OR - eventType = 'group.privilege.grant' AND - cast(json_extract(debugcontext.debugdata, '$.privilegeGranted') as varchar) LIKE '%Admin%' - ) AND - p_occurs_between('2022-01-14','2022-03-22') - ORDER BY - event_time desc -SnowflakeQuery: | +Query: | SELECT p_event_time as event_time, actor:alternateId as actor_email, diff --git a/queries/okta_queries/okta_mfa_password_reset_audit.yml b/queries/okta_queries/okta_mfa_password_reset_audit.yml index cecbd0a84..818cb05e6 100644 --- a/queries/okta_queries/okta_mfa_password_reset_audit.yml +++ b/queries/okta_queries/okta_mfa_password_reset_audit.yml @@ -3,15 +3,7 @@ QueryName: "Okta Investigate MFA and Password resets" Enabled: false Description: > Investigate Password and MFA resets for the last 7 days -AthenaQuery: | - SELECT p_event_time,actor.alternateId as actor_user,target[1].alternateId as target_user, eventType,client.ipAddress as ip_address - FROM panther_logs.okta_systemlog - WHERE eventType IN ('user.mfa.factor.reset_all', 'user.mfa.factor.deactivate', 'user.mfa.factor.suspend', 'user.account.reset_password', 'user.account.update_password') - and p_occurs_since('7 days') - -- If you wish to investigate an individual user , uncomment this line and add their email here - -- and actor:alternateId = '' - ORDER by p_event_time DESC -SnowflakeQuery: | +Query: | SELECT p_event_time,actor:alternateId as actor_user,target[0]:alternateId as target_user, eventType,client:ipAddress as ip_address FROM panther_logs.public.okta_systemlog WHERE eventType IN ('user.mfa.factor.reset_all', 'user.mfa.factor.deactivate', 'user.mfa.factor.suspend', 'user.account.reset_password', 'user.account.update_password','user.mfa.factor.update') diff --git a/queries/okta_queries/okta_session_id_audit.yml b/queries/okta_queries/okta_session_id_audit.yml index fd95312a9..cdb99cde4 100644 --- a/queries/okta_queries/okta_session_id_audit.yml +++ b/queries/okta_queries/okta_session_id_audit.yml @@ -3,24 +3,7 @@ QueryName: "Okta Investigate Session ID Activity" Enabled: false Description: > Search for activity related to a specific SessionID in Okta panther_logs.okta_systemlog -AthenaQuery: | - SELECT - p_event_time as event_time, - actor.alternateId as actor_email, - actor.displayName as actor_name, - authenticationContext.externalSessionId as sessionId, - displayMessage, - eventType, - client.ipAddress as src_ip, - client.geographicalContext.city as city, - client.geographicalContext.country as country, - client.userAgent.rawUserAgent as user_agent - FROM panther_logs.okta_systemlog - WHERE p_occurs_since('7 days') - -- Uncomment the line below and replace 'sessionId' with the sessionId you are investigating - -- and authenticationContext:externalSessionId = '' - ORDER BY event_time DESC -SnowflakeQuery: | +Query: | SELECT p_event_time as event_time, actor:alternateId as actor_email, diff --git a/queries/okta_queries/okta_support_access.yml b/queries/okta_queries/okta_support_access.yml index c61ae9da6..d52e7d93c 100644 --- a/queries/okta_queries/okta_support_access.yml +++ b/queries/okta_queries/okta_support_access.yml @@ -3,27 +3,7 @@ QueryName: "Okta Support Access" Enabled: false Description: > Show instances that Okta support was granted to your account -AthenaQuery: | - SELECT - p_event_time as event_time, - actor.alternateid as actor_email, - actor.displayName as actor_name, - displayMessage, - eventType, - client.ipAddress as src_ip, - client.geographicalContext.city as city, - client.geographicalContext.country as country, - client.useragent.rawUserAgent as user_agent - FROM panther_logs.okta_systemlog - WHERE - ( - eventType = 'user.session.impersonation.grant' OR - eventType = 'user.session.impersonation.initiate' - ) and - p_occurs_between('2022-01-14','2022-03-22') - ORDER BY - event_time desc -SnowflakeQuery: | +Query: | SELECT p_event_time as event_time, actor:alternateId as actor_email, diff --git a/queries/snowflake_queries/snowflake_0108977_configuration_drift_query.yml b/queries/snowflake_queries/snowflake_0108977_configuration_drift_query.yml index 504a90dc8..7b6950eaa 100644 --- a/queries/snowflake_queries/snowflake_0108977_configuration_drift_query.yml +++ b/queries/snowflake_queries/snowflake_0108977_configuration_drift_query.yml @@ -5,7 +5,7 @@ Description: > Monitor for configuration drift made by malicious actors as part of ongoing cyber threat activity reported May 31st, 2024 Tags: - Configuration Required -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information -- adjust query/limit to narrow as necessary diff --git a/queries/snowflake_queries/snowflake_0108977_configuration_drift_threat_hunting.yml b/queries/snowflake_queries/snowflake_0108977_configuration_drift_threat_hunting.yml index 3a8cbe5ac..12ff2b173 100644 --- a/queries/snowflake_queries/snowflake_0108977_configuration_drift_threat_hunting.yml +++ b/queries/snowflake_queries/snowflake_0108977_configuration_drift_threat_hunting.yml @@ -2,7 +2,7 @@ AnalysisType: saved_query QueryName: "Query.Snowflake.ThreatHunting.ConfigurationDrift" Description: > Monitor for configuration drift made by malicious actors as part of ongoing cyber threat activity reported May 31st, 2024 -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information -- adjust query/limit to narrow as necessary diff --git a/queries/snowflake_queries/snowflake_0108977_ip_query.yml b/queries/snowflake_queries/snowflake_0108977_ip_query.yml index 13c5e60c1..9fea2afd3 100644 --- a/queries/snowflake_queries/snowflake_0108977_ip_query.yml +++ b/queries/snowflake_queries/snowflake_0108977_ip_query.yml @@ -3,7 +3,7 @@ Enabled: false QueryName: "Query.Snowflake.ClientIp" Description: > Monitor for malicious IPs interacting with Snowflake as part of ongoing cyber threat activity reported May 31st, 2024 -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information SELECT diff --git a/queries/snowflake_queries/snowflake_0108977_ip_threat_hunting.yml b/queries/snowflake_queries/snowflake_0108977_ip_threat_hunting.yml index 7d9e1dccb..0825365a8 100644 --- a/queries/snowflake_queries/snowflake_0108977_ip_threat_hunting.yml +++ b/queries/snowflake_queries/snowflake_0108977_ip_threat_hunting.yml @@ -2,7 +2,7 @@ AnalysisType: saved_query QueryName: "Query.Snowflake.ThreatHunting.ClientIp" Description: > Monitor for malicious IPs interacting with Snowflake as part of ongoing cyber threat activity reported May 31st, 2024 -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information SELECT diff --git a/queries/snowflake_queries/snowflake_0108977_suspected_user_access_query.yml b/queries/snowflake_queries/snowflake_0108977_suspected_user_access_query.yml index 9399f6982..bb05100cc 100644 --- a/queries/snowflake_queries/snowflake_0108977_suspected_user_access_query.yml +++ b/queries/snowflake_queries/snowflake_0108977_suspected_user_access_query.yml @@ -3,7 +3,7 @@ Enabled: false QueryName: "Query.Snowflake.SuspectedUserAccess" Description: > Return sessions of suspected clients as part of ongoing cyber threat activity reported May 31st, 2024 -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information SELECT diff --git a/queries/snowflake_queries/snowflake_0108977_suspected_user_access_threat_hunting.yml b/queries/snowflake_queries/snowflake_0108977_suspected_user_access_threat_hunting.yml index d118bd80b..2a60e83e5 100644 --- a/queries/snowflake_queries/snowflake_0108977_suspected_user_access_threat_hunting.yml +++ b/queries/snowflake_queries/snowflake_0108977_suspected_user_access_threat_hunting.yml @@ -2,7 +2,7 @@ AnalysisType: saved_query QueryName: "Query.Snowflake.ThreatHunting.SuspectedUserAccess" Description: > Return sessions of suspected clients as part of ongoing cyber threat activity reported May 31st, 2024 -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information SELECT diff --git a/queries/snowflake_queries/snowflake_0108977_suspected_user_activity_threat_hunting.yml b/queries/snowflake_queries/snowflake_0108977_suspected_user_activity_threat_hunting.yml index 11f62856b..fbbe5efeb 100644 --- a/queries/snowflake_queries/snowflake_0108977_suspected_user_activity_threat_hunting.yml +++ b/queries/snowflake_queries/snowflake_0108977_suspected_user_activity_threat_hunting.yml @@ -2,7 +2,7 @@ AnalysisType: saved_query QueryName: "Query.Snowflake.ThreatHunting.SuspectedUserActivity" Description: > Return actions/queries made by suspected users as part of ongoing cyber threat activity reported May 31st, 2024 -SnowflakeQuery: | +Query: | -- https://community.snowflake.com/s/article/Communication-ID-0108977-Additional-Information -- replace with actual user name diff --git a/queries/snowflake_queries/snowflake_account_admin_assigned_query.yml b/queries/snowflake_queries/snowflake_account_admin_assigned_query.yml index 2fb40dc7e..ba76e0c68 100644 --- a/queries/snowflake_queries/snowflake_account_admin_assigned_query.yml +++ b/queries/snowflake_queries/snowflake_account_admin_assigned_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.AccountAdminGranted" Enabled: false Description: > Monitor and detect granting account admin role. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return instances where active (not deleted) roles are granted within the last 24 hours --this was adapted from a Security Feature Checklist query diff --git a/queries/snowflake_queries/snowflake_brute_force_ip_query.yml b/queries/snowflake_queries/snowflake_brute_force_ip_query.yml index d0465a2c7..f5fb6faad 100644 --- a/queries/snowflake_queries/snowflake_brute_force_ip_query.yml +++ b/queries/snowflake_queries/snowflake_brute_force_ip_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.BruteForceByIp" Enabled: false Description: > Detect brute force attempts by monitoring for failed logins to snowflake. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return IPs with more than 5 failed logins in the previous 24 hours --this was adapted from a SnowAlert query diff --git a/queries/snowflake_queries/snowflake_brute_force_username_query.yml b/queries/snowflake_queries/snowflake_brute_force_username_query.yml index 337d0601c..5121e354a 100644 --- a/queries/snowflake_queries/snowflake_brute_force_username_query.yml +++ b/queries/snowflake_queries/snowflake_brute_force_username_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.BruteForceByUsername" Enabled: false Description: > Detect brute force attempts by monitoring for failed logins to snowflake. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return users with more than 5 failed logins in the previous 24 hours --this was adapted from a SnowAlert query diff --git a/queries/snowflake_queries/snowflake_external_shares_query.yml b/queries/snowflake_queries/snowflake_external_shares_query.yml index 9eeec4ead..97fc5c5bb 100644 --- a/queries/snowflake_queries/snowflake_external_shares_query.yml +++ b/queries/snowflake_queries/snowflake_external_shares_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.External.Shares" Enabled: false Description: > Monitor for external shares from one cloud source to another. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return external shares --this was adapted from a Security Feature Checklist query diff --git a/queries/snowflake_queries/snowflake_key_user_password_login_query.yml b/queries/snowflake_queries/snowflake_key_user_password_login_query.yml index d0967503a..69785689d 100644 --- a/queries/snowflake_queries/snowflake_key_user_password_login_query.yml +++ b/queries/snowflake_queries/snowflake_key_user_password_login_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.KeyUserPasswordLogin" Enabled: false Description: > Detects when a user with a configured RSA key logs in with a password -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return instances where a user who has key-based login configured logs in with a password --this was adapted from a Security Feature Checklist query diff --git a/queries/snowflake_queries/snowflake_login_without_mfa_query.yml b/queries/snowflake_queries/snowflake_login_without_mfa_query.yml index 365c71f9b..e95f13e54 100644 --- a/queries/snowflake_queries/snowflake_login_without_mfa_query.yml +++ b/queries/snowflake_queries/snowflake_login_without_mfa_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.MFALogin" Enabled: false Description: > Monitor logins that are not using MFA. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return instances where a user logs in without MFA --this was adapted from a Security Feature Checklist query diff --git a/queries/snowflake_queries/snowflake_multiple_failed_logins_followed_by_success_query.yml b/queries/snowflake_queries/snowflake_multiple_failed_logins_followed_by_success_query.yml index 6fbbeaae8..fe0743c59 100644 --- a/queries/snowflake_queries/snowflake_multiple_failed_logins_followed_by_success_query.yml +++ b/queries/snowflake_queries/snowflake_multiple_failed_logins_followed_by_success_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.Multiple.Logins.Followed.By.Success" Enabled: false Description: > Monitor for brute force user activity. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return multiple failed logins followed by a success WITH login_attempts as ( diff --git a/queries/snowflake_queries/snowflake_user_created_query.yml b/queries/snowflake_queries/snowflake_user_created_query.yml index 241752664..da6194c90 100644 --- a/queries/snowflake_queries/snowflake_user_created_query.yml +++ b/queries/snowflake_queries/snowflake_user_created_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.UserCreated" Enabled: false Description: > Monitor for new users. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return create user events --this was adapted from a Security Feature Checklist query diff --git a/queries/snowflake_queries/snowflake_user_enabled_query.yml b/queries/snowflake_queries/snowflake_user_enabled_query.yml index 2ad8b61c1..6c8d2e4d0 100644 --- a/queries/snowflake_queries/snowflake_user_enabled_query.yml +++ b/queries/snowflake_queries/snowflake_user_enabled_query.yml @@ -3,10 +3,7 @@ QueryName: "Query.Snowflake.UserEnabled" Enabled: false Description: > Monitor for users that are being re-enabled. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | +Query: | --return enable user events --this was adapted from a Security Feature Checklist query diff --git a/templates/example_scheduled_query.yml b/templates/example_scheduled_query.yml index c1e8512ae..45cd6e8d7 100644 --- a/templates/example_scheduled_query.yml +++ b/templates/example_scheduled_query.yml @@ -6,12 +6,7 @@ Tags: - Tags Description: > An optional Description -# Note: Use this parameter if the query includes generic syntax -Query: "SELECT * FROM panther_logs.aws_cloudtrail LIMIT 10" -# Note: Use this parameter if the query includes Snowflake-specific syntax -SnowflakeQuery: "SELECT * FROM panther_logs.public.aws_cloudtrail LIMIT 10" -# Note: Use this parameter if the query includes Athena-specific syntax -AthenaQuery: "SELECT * FROM panther_logs.aws_cloudtrail LIMIT 10" +Query: "SELECT * FROM panther_logs.public.aws_cloudtrail LIMIT 10" Schedule: # Note: CronExpression and RateMinutes are mutually exclusive, only # configure one or the other From 09d2d706e50e4f4b3fe0bec1aa40c237ecab5f1d Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:21:40 -0500 Subject: [PATCH 5/8] Allow 'applicationName=login` for `GSuite.ExternalMailForwarding` (#1395) --- .../gsuite_external_forwarding.py | 2 +- .../gsuite_external_forwarding.yml | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py b/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py index c7f3b0c8c..3db4dc6c0 100644 --- a/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py +++ b/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py @@ -2,7 +2,7 @@ def rule(event): - if event.deep_get("id", "applicationName") != "user_accounts": + if event.deep_get("id", "applicationName") not in ("user_accounts", "login"): return False if event.get("name") == "email_forwarding_out_of_domain": diff --git a/rules/gsuite_activityevent_rules/gsuite_external_forwarding.yml b/rules/gsuite_activityevent_rules/gsuite_external_forwarding.yml index ffdef7f2e..ac59594a6 100644 --- a/rules/gsuite_activityevent_rules/gsuite_external_forwarding.yml +++ b/rules/gsuite_activityevent_rules/gsuite_external_forwarding.yml @@ -21,7 +21,7 @@ Runbook: > SummaryAttributes: - p_any_emails Tests: - - Name: Forwarding to External Address + - Name: Forwarding to External Address - applicationName = user_accounts ExpectedResult: true Log: { @@ -33,6 +33,17 @@ Tests: { "email_forwarding_destination_address": "HSimpson@gmail.com" }, } + - Name: Forwarding to External Address - applicationName = login + ExpectedResult: true + Log: + { + "id": { "applicationName": "login", "customerId": "D12345" }, + "actor": { "email": "homer.simpson@springfield.io" }, + "type": "email_forwarding_change", + "name": "email_forwarding_out_of_domain", + "parameters": { "email_forwarding_destination_address": "HSimpsone@gmail.com" } + } + - Name: Forwarding to External Address - Allowed Domain ExpectedResult: false Log: From 69caf976e09592b208366c47ec52443153c9b36d Mon Sep 17 00:00:00 2001 From: akozlovets098 <95437895+akozlovets098@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:26:50 +0300 Subject: [PATCH 6/8] ASK-833 `GSuite.Drive.ExternalFileShare` sender-receiver pairs in EXCEPTION_PATTERN (#1394) Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .../gsuite_drive_external_share.py | 59 ++++++++++++++----- .../gsuite_drive_external_share.yml | 54 +++++++++++++++-- 2 files changed, 93 insertions(+), 20 deletions(-) diff --git a/rules/gsuite_reports_rules/gsuite_drive_external_share.py b/rules/gsuite_reports_rules/gsuite_drive_external_share.py index 0004c8ebb..03c2476ac 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_external_share.py +++ b/rules/gsuite_reports_rules/gsuite_drive_external_share.py @@ -5,9 +5,8 @@ COMPANY_DOMAIN = "your-company-name.com" EXCEPTION_PATTERNS = { # The glob pattern for the document title (lowercased) - "document title p*": { - # All actors allowed to receive the file share - "allowed_for": { + "1 document title p*": { # allow any title "all" + "allowed_to_send": { "alice@acme.com", "samuel@acme.com", "nathan@acme.com", @@ -17,6 +16,26 @@ # Allow any user in a specific domain # "*@acme.com" }, + "allowed_to_receive": { + "alice@abc.com", + "samuel@abc.com", + "nathan@abc.com", + "barry@abc.com", + # Allow any user + # "all" + # Allow any user in a specific domain + # "*@acme.com" + }, + # The time limit for how long the file share stays valid + "allowed_until": datetime.datetime(year=2030, month=6, day=2), + }, + "2 document title p*": { + "allowed_to_send": { + "alice@abc.com", + }, + "allowed_to_receive": { + "*@acme.com", + }, # The time limit for how long the file share stays valid "allowed_until": datetime.datetime(year=2030, month=6, day=2), }, @@ -32,7 +51,7 @@ def _check_acl_change_event(actor_email, acl_change_event): doc_title = parameters.get("doc_title", "TITLE_UNKNOWN") old_visibility = parameters.get("old_visibility", "OLD_VISIBILITY_UNKNOWN") new_visibility = parameters.get("visibility", "NEW_VISIBILITY_UNKNOWN") - target_user = parameters.get("target_user", "USER_UNKNOWN") + target_user = parameters.get("target_user") or parameters.get("target_domain") or "USER_UNKNOWN" current_time = datetime.datetime.now() if ( @@ -41,24 +60,32 @@ def _check_acl_change_event(actor_email, acl_change_event): and not target_user.endswith(f"@{COMPANY_DOMAIN}") ): # This is a dangerous share, check exceptions: + for pattern, details in EXCEPTION_PATTERNS.items(): - doc_title_match = pattern_match(doc_title.lower(), pattern) - allowed_for_match = pattern_match_list(actor_email, details.get("allowed_for")) - allowed_for_all_match = details.get("allowed_for") == {"all"} + proper_title = pattern_match(doc_title.lower(), pattern) or pattern == "all" + + proper_sender = pattern_match_list( + actor_email, details.get("allowed_to_send") + ) or details.get("allowed_to_send") == {"all"} + + proper_receiver = pattern_match_list( + target_user, details.get("allowed_to_receive") + ) or details.get("allowed_to_receive") == {"all"} if ( - doc_title_match - and (allowed_for_match or allowed_for_all_match) + proper_title + and proper_sender + and proper_receiver and current_time < details.get("allowed_until") ): return False - # No exceptions match. - # Return the event summary (which is True) to alert & use in title. - return { - "actor": actor_email, - "doc_title": doc_title, - "target_user": target_user, - } + # No exceptions match. + # Return the event summary (which is True) to alert & use in title. + return { + "actor": actor_email, + "doc_title": doc_title, + "target_user": target_user, + } return False diff --git a/rules/gsuite_reports_rules/gsuite_drive_external_share.yml b/rules/gsuite_reports_rules/gsuite_drive_external_share.yml index 325891f52..2ead0a1b3 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_external_share.yml +++ b/rules/gsuite_reports_rules/gsuite_drive_external_share.yml @@ -48,7 +48,7 @@ Tests: { "name": "old_visibility", "value": "private" }, { "name": "doc_id", "value": "1111111111111111111" }, { "name": "doc_type", "value": "document" }, - { "name": "doc_title", "value": "Document Title Primary" }, + { "name": "doc_title", "value": "1 Document Title Primary" }, { "name": "visibility", "value": "shared_externally" }, { "name": "originating_app_id", @@ -86,7 +86,7 @@ Tests: [ { "name": "primary_event", "boolValue": true }, { "name": "visibility_change", "value": "external" }, - { "name": "target_user", "value": "alice@external.com" }, + { "name": "target_domain", "value": "external.com" }, { "name": "old_visibility", "value": "private" }, { "name": "doc_id", "value": "1111111111111111111" }, { "name": "doc_type", "value": "document" }, @@ -129,11 +129,11 @@ Tests: { "name": "primary_event", "boolValue": true }, { "name": "billable", "boolValue": true }, { "name": "visibility_change", "value": "external" }, - { "name": "target_domain", "value": "acme.com" }, + { "name": "target_user", "value": "samuel@abc.com" }, { "name": "old_visibility", "value": "private" }, { "name": "doc_id", "value": "1111111111111111111" }, { "name": "doc_type", "value": "document" }, - { "name": "doc_title", "value": "Document Title Pattern" }, + { "name": "doc_title", "value": "1 Document Title Pattern" }, { "name": "visibility", "value": "shared_externally" }, { "name": "originating_app_id", @@ -150,3 +150,49 @@ Tests: }, ], } + - Name: Share Allowed by Exception - 2 + LogType: GSuite.Reports + ExpectedResult: false + Log: + { + "kind": "admin#reports#activity", + "id": + { + "time": "2020-07-07T15:50:49.617Z", + "uniqueQualifier": "1111111111111111111", + "applicationName": "drive", + "customerId": "C010qxghg", + }, + "actor": + { "email": "alice@abc.com", "profileId": "1111111111111111111" }, + "events": + [ + { + "type": "acl_change", + "name": "change_user_access", + "parameters": + [ + { "name": "primary_event", "boolValue": true }, + { "name": "billable", "boolValue": true }, + { "name": "visibility_change", "value": "external" }, + { "name": "target_user", "value": "samuel@acme.com" }, + { "name": "old_visibility", "value": "private" }, + { "name": "doc_id", "value": "1111111111111111111" }, + { "name": "doc_type", "value": "document" }, + { "name": "doc_title", "value": "2 Document Title Pattern" }, + { "name": "visibility", "value": "shared_externally" }, + { + "name": "originating_app_id", + "value": "1111111111111111111", + }, + { "name": "owner_is_shared_drive", "boolValue": false }, + { "name": "owner_is_team_drive", "boolValue": false }, + { "name": "old_value", "multiValue": [ "none" ] }, + { + "name": "new_value", + "multiValue": [ "people_within_domain_with_link" ], + }, + ], + }, + ], + } From b4460b6f137ef2f92377122d7141b5b621dc6259 Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:42:14 -0500 Subject: [PATCH 7/8] Update Internal Automations (#1396) Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .github/workflows/check-deprecated.yml | 2 ++ .github/workflows/check-packs.yml | 7 +++++-- .github/workflows/upload.yml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index ec1f2e6c9..d1c4a1e1e 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -1,5 +1,7 @@ on: pull_request: + branches: + - develop permissions: contents: read diff --git a/.github/workflows/check-packs.yml b/.github/workflows/check-packs.yml index 3c3bc664d..2aa3e98cb 100644 --- a/.github/workflows/check-packs.yml +++ b/.github/workflows/check-packs.yml @@ -1,5 +1,8 @@ on: + workflow_dispatch: pull_request: + branches: + - develop permissions: contents: read @@ -52,7 +55,7 @@ jobs: ```diff ${{ steps.check-packs.outputs.errors }} ``` - comment_tag: check-packs + comment-tag: check-packs - name: Delete comment uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 if: ${{ !steps.check-packs.outputs.errors }} @@ -64,4 +67,4 @@ jobs: ```diff ${{ steps.check-packs.outputs.errors }} ``` - comment_tag: check-packs \ No newline at end of file + comment-tag: check-packs \ No newline at end of file diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml index 5bef43d8f..47b38c62a 100644 --- a/.github/workflows/upload.yml +++ b/.github/workflows/upload.yml @@ -1,7 +1,7 @@ on: push: branches: - - release + - develop permissions: contents: read From 30c461fb71d661bf00439484732a4f1879a20318 Mon Sep 17 00:00:00 2001 From: Bharat <43651837+bcpenta@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:53:47 -0400 Subject: [PATCH 8/8] Add AWS WAF Logging Configured Policy (#1393) Co-authored-by: Bharat Chandra Penta Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> Co-authored-by: Ariel --- packs/aws.yml | 1 + .../aws_waf_logging_configured.py | 29 ++++++ .../aws_waf_logging_configured.yml | 91 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 policies/aws_waf_policies/aws_waf_logging_configured.py create mode 100644 policies/aws_waf_policies/aws_waf_logging_configured.yml diff --git a/packs/aws.yml b/packs/aws.yml index d224547cc..dac64c888 100644 --- a/packs/aws.yml +++ b/packs/aws.yml @@ -132,6 +132,7 @@ PackDefinition: - AWS.VPC.FlowLogs - AWS.WAF.Disassociation - AWS.WAF.HasXSSPredicate + - AWS.WAF.LoggingConfigured # Other rules - AWS.CloudTrail.Account.Discovery - AWS.CloudTrail.CloudWatchLogs diff --git a/policies/aws_waf_policies/aws_waf_logging_configured.py b/policies/aws_waf_policies/aws_waf_logging_configured.py new file mode 100644 index 000000000..0ce0efbcd --- /dev/null +++ b/policies/aws_waf_policies/aws_waf_logging_configured.py @@ -0,0 +1,29 @@ +def is_valid_arn(arn, service): + if service == "logs": + return arn.startswith("arn:aws:logs:") and ":log-group:" in arn + if service == "s3": + return arn.startswith("arn:aws:s3:::") and len(arn.split(":")) == 6 + if service == "firehose": + return arn.startswith("arn:aws:firehose:") and ":deliverystream/" in arn + return False + + +def policy(resource): + # Check if WAF logging configuration exists + logging_config = resource.get("LoggingConfiguration") + if not logging_config: + return False + + # Get the logging destinations + destinations = logging_config.get("LogDestinationConfigs", []) + + # Validate the ARNs for CloudWatch Logs, S3, or Kinesis Firehose + for destination in destinations: + if ( + is_valid_arn(destination, "logs") + or is_valid_arn(destination, "s3") + or is_valid_arn(destination, "firehose") + ): + return True + + return False diff --git a/policies/aws_waf_policies/aws_waf_logging_configured.yml b/policies/aws_waf_policies/aws_waf_logging_configured.yml new file mode 100644 index 000000000..809e96816 --- /dev/null +++ b/policies/aws_waf_policies/aws_waf_logging_configured.yml @@ -0,0 +1,91 @@ +AnalysisType: policy +Filename: aws_waf_logging_configured.py +PolicyID: "AWS.WAF.LoggingConfigured" +DisplayName: "AWS WAF Logging Configured" +Enabled: true +ResourceTypes: + - AWS.WAF.Regional.WebACL + - AWS.WAF.WebACL +Tags: + - AWS + - Monitoring + - Logging + - Security Control + - Defense Evasion:Impair Defenses +Reports: + PCI: + - 10.5.5 + MITRE ATT&CK: + - TA0005:T1562 +Severity: High +Description: > + Ensures that AWS WAF logging is enabled and that the logs are being sent to a valid destination (S3, CloudWatch, or Kinesis Firehose). Without logging, visibility into WAF activity is severely limited, increasing the risk of undetected attacks. +Runbook: > + Ensure AWS WAF logging is configured to at least one valid destination such as an Amazon S3 bucket, Amazon CloudWatch Logs, or Amazon Kinesis Data Firehose. Refer to the AWS WAF logging documentation for setup instructions. +Reference: https://docs.aws.amazon.com/waf/latest/developerguide/logging.html +Tests: + - Name: WAF Logging Configured to CloudWatch Logs + ExpectedResult: true + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:logs:us-west-2:123456789012:log-group:example-log-group" + RedactedFields: null + + - Name: WAF Logging Configured to S3 + ExpectedResult: true + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:s3:::example-bucket/waf-logs/" + RedactedFields: null + + - Name: WAF Logging Configured to Kinesis Firehose + ExpectedResult: true + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:firehose:us-west-2:123456789012:deliverystream/example-firehose" + RedactedFields: null + + - Name: WAF Logging Not Configured + ExpectedResult: false + Resource: + LoggingConfiguration: null + + # Edge Case 1: Malformed CloudWatch Logs ARN + - Name: WAF Logging Configured with Malformed CloudWatch Logs ARN + ExpectedResult: false + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:logs:us-west-2:123456789012" # Incorrect ARN format + RedactedFields: null + + # Edge Case 2: Malformed S3 ARN + - Name: WAF Logging Configured with Malformed S3 ARN + ExpectedResult: false + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:s3::example-bucket-wrong-format" # Incorrect ARN format + RedactedFields: null + + # Edge Case 3: Multiple Valid Logging Destinations (S3 and Kinesis Firehose) + - Name: WAF Logging Configured to Both S3 and Kinesis Firehose + ExpectedResult: true + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:s3:::example-bucket/waf-logs/" + - "arn:aws:firehose:us-west-2:123456789012:deliverystream/example-firehose" + RedactedFields: null + + # Edge Case 4: Malformed Kinesis Firehose ARN + - Name: WAF Logging Configured with Malformed Kinesis Firehose ARN + ExpectedResult: false + Resource: + LoggingConfiguration: + LogDestinationConfigs: + - "arn:aws:firehose:us-west-2" # Incorrect ARN format + RedactedFields: null