From f63dbdee4cba7cee14d1a55ca5d03fa3491421fb Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Thu, 26 Sep 2024 12:47:41 -0500 Subject: [PATCH 01/13] remove deprecated rules and add to list --- ...otentially_compromised_service_role_cr.yml | 55 -- decomissioned_rules.txt | 34 ++ packs/aws.yml | 1 - .../aws_dynamodb_table_encryption.py | 5 - .../aws_dynamodb_table_encryption.yml | 164 ------ ...ws_security_group_unused_security_group.py | 9 - ...s_security_group_unused_security_group.yml | 16 - .../abnormally_high_event_volume.py | 106 ---- .../abnormally_high_event_volume.yml | 36 -- .../aws_console_login_failed.py | 20 - .../aws_console_login_failed.yml | 145 ----- .../aws_root_console_login.py | 18 - .../aws_root_console_login.yml | 150 ----- .../aws_root_failed_console_login.py | 18 - .../aws_root_failed_console_login.yml | 151 ------ .../aws_s3_activity_greynoise.py | 77 --- .../aws_s3_activity_greynoise.yml | 451 --------------- .../aws_snapshot_backup_exfiltration.py | 21 - .../aws_snapshot_backup_exfiltration.yml | 107 ---- rules/box_rules/box_brute_force_login.py | 12 - rules/box_rules/box_brute_force_login.yml | 52 -- ...are_firewall_high_volume_events_blocked.py | 27 - ...re_firewall_high_volume_events_blocked.yml | 90 --- ...ll_high_volume_events_blocked_greynoise.py | 52 -- ...l_high_volume_events_blocked_greynoise.yml | 250 --------- ...are_firewall_suspicious_event_greynoise.py | 50 -- ...re_firewall_suspicious_event_greynoise.yml | 251 --------- ...flare_httpreq_bot_high_volume_greynoise.py | 56 -- ...lare_httpreq_bot_high_volume_greynoise.yml | 312 ----------- .../gcp_iam_admin_role_assigned.py | 31 -- .../gcp_iam_admin_role_assigned.yml | 494 ----------------- .../gsuite_brute_force_login.py | 17 - .../gsuite_brute_force_login.yml | 43 -- .../gsuite_permissions_delegated.py | 26 - .../gsuite_permissions_delegated.yml | 46 -- .../notion_account_changed_after_login.py | 80 --- .../notion_account_changed_after_login.yml | 366 ------------- .../notion_page_view_impossible_travel.py | 192 ------- .../notion_page_view_impossible_travel.yml | 171 ------ rules/okta_rules/okta_brute_force_logins.py | 20 - rules/okta_rules/okta_brute_force_logins.yml | 62 --- .../okta_rules/okta_geo_improbable_access.py | 128 ----- .../okta_rules/okta_geo_improbable_access.yml | 488 ----------------- .../onelogin_admin_role_assigned.py | 11 - .../onelogin_admin_role_assigned.yml | 32 -- .../onelogin_brute_force_by_ip.py | 7 - .../onelogin_brute_force_by_ip.yml | 45 -- .../onelogin_brute_force_by_username.py | 10 - .../onelogin_brute_force_by_username.yml | 44 -- .../onelogin_high_risk_login.py | 38 -- .../onelogin_high_risk_login.yml | 30 - .../onelogin_rules/onelogin_unusual_login.py | 73 --- .../onelogin_rules/onelogin_unusual_login.yml | 97 ---- .../atlassian_confluence_ip_iocs.py | 12 - .../atlassian_confluence_ip_iocs.yml | 48 -- rules/panther_ioc_rules/log4j_exploit_iocs.py | 14 - .../panther_ioc_rules/log4j_exploit_iocs.yml | 51 -- rules/panther_ioc_rules/log4j_ip_iocs.py | 12 - rules/panther_ioc_rules/log4j_ip_iocs.yml | 47 -- rules/panther_ioc_rules/sunburst_fqdn_iocs.py | 12 - .../panther_ioc_rules/sunburst_fqdn_iocs.yml | 54 -- rules/panther_ioc_rules/sunburst_ip_iocs.py | 12 - rules/panther_ioc_rules/sunburst_ip_iocs.yml | 44 -- .../panther_ioc_rules/sunburst_sha256_iocs.py | 12 - .../sunburst_sha256_iocs.yml | 53 -- .../unusual_login_deprecated.py | 145 ----- .../unusual_login_deprecated.yml | 512 ------------------ ...operation_user_granted_admin_deprecated.py | 16 - ...peration_user_granted_admin_deprecated.yml | 40 -- 69 files changed, 34 insertions(+), 6337 deletions(-) delete mode 100644 correlation_rules/aws_potentially_compromised_service_role_cr.yml create mode 100644 decomissioned_rules.txt delete mode 100644 policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.py delete mode 100644 policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.yml delete mode 100644 policies/aws_vpc_policies/aws_security_group_unused_security_group.py delete mode 100644 policies/aws_vpc_policies/aws_security_group_unused_security_group.yml delete mode 100644 queries/aws_queries/abnormally_high_event_volume.py delete mode 100644 queries/aws_queries/abnormally_high_event_volume.yml delete mode 100644 rules/aws_cloudtrail_rules/aws_console_login_failed.py delete mode 100644 rules/aws_cloudtrail_rules/aws_console_login_failed.yml delete mode 100644 rules/aws_cloudtrail_rules/aws_root_console_login.py delete mode 100644 rules/aws_cloudtrail_rules/aws_root_console_login.yml delete mode 100644 rules/aws_cloudtrail_rules/aws_root_failed_console_login.py delete mode 100644 rules/aws_cloudtrail_rules/aws_root_failed_console_login.yml delete mode 100644 rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.py delete mode 100644 rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.yml delete mode 100644 rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.py delete mode 100644 rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.yml delete mode 100644 rules/box_rules/box_brute_force_login.py delete mode 100644 rules/box_rules/box_brute_force_login.yml delete mode 100644 rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.py delete mode 100644 rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.yml delete mode 100644 rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.py delete mode 100644 rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.yml delete mode 100644 rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.py delete mode 100644 rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.yml delete mode 100644 rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.py delete mode 100644 rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.yml delete mode 100644 rules/gcp_audit_rules/gcp_iam_admin_role_assigned.py delete mode 100644 rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml delete mode 100644 rules/gsuite_activityevent_rules/gsuite_brute_force_login.py delete mode 100644 rules/gsuite_activityevent_rules/gsuite_brute_force_login.yml delete mode 100644 rules/gsuite_activityevent_rules/gsuite_permissions_delegated.py delete mode 100644 rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml delete mode 100644 rules/notion_rules/notion_account_changed_after_login.py delete mode 100644 rules/notion_rules/notion_account_changed_after_login.yml delete mode 100644 rules/notion_rules/notion_page_view_impossible_travel.py delete mode 100644 rules/notion_rules/notion_page_view_impossible_travel.yml delete mode 100644 rules/okta_rules/okta_brute_force_logins.py delete mode 100644 rules/okta_rules/okta_brute_force_logins.yml delete mode 100644 rules/okta_rules/okta_geo_improbable_access.py delete mode 100644 rules/okta_rules/okta_geo_improbable_access.yml delete mode 100644 rules/onelogin_rules/onelogin_admin_role_assigned.py delete mode 100644 rules/onelogin_rules/onelogin_admin_role_assigned.yml delete mode 100644 rules/onelogin_rules/onelogin_brute_force_by_ip.py delete mode 100644 rules/onelogin_rules/onelogin_brute_force_by_ip.yml delete mode 100644 rules/onelogin_rules/onelogin_brute_force_by_username.py delete mode 100644 rules/onelogin_rules/onelogin_brute_force_by_username.yml delete mode 100644 rules/onelogin_rules/onelogin_high_risk_login.py delete mode 100644 rules/onelogin_rules/onelogin_high_risk_login.yml delete mode 100644 rules/onelogin_rules/onelogin_unusual_login.py delete mode 100644 rules/onelogin_rules/onelogin_unusual_login.yml delete mode 100644 rules/panther_ioc_rules/atlassian_confluence_ip_iocs.py delete mode 100644 rules/panther_ioc_rules/atlassian_confluence_ip_iocs.yml delete mode 100644 rules/panther_ioc_rules/log4j_exploit_iocs.py delete mode 100644 rules/panther_ioc_rules/log4j_exploit_iocs.yml delete mode 100644 rules/panther_ioc_rules/log4j_ip_iocs.py delete mode 100644 rules/panther_ioc_rules/log4j_ip_iocs.yml delete mode 100644 rules/panther_ioc_rules/sunburst_fqdn_iocs.py delete mode 100644 rules/panther_ioc_rules/sunburst_fqdn_iocs.yml delete mode 100644 rules/panther_ioc_rules/sunburst_ip_iocs.py delete mode 100644 rules/panther_ioc_rules/sunburst_ip_iocs.yml delete mode 100644 rules/panther_ioc_rules/sunburst_sha256_iocs.py delete mode 100644 rules/panther_ioc_rules/sunburst_sha256_iocs.yml delete mode 100644 rules/standard_rules/unusual_login_deprecated.py delete mode 100644 rules/standard_rules/unusual_login_deprecated.yml delete mode 100644 rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.py delete mode 100644 rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.yml diff --git a/correlation_rules/aws_potentially_compromised_service_role_cr.yml b/correlation_rules/aws_potentially_compromised_service_role_cr.yml deleted file mode 100644 index dc9291462..000000000 --- a/correlation_rules/aws_potentially_compromised_service_role_cr.yml +++ /dev/null @@ -1,55 +0,0 @@ -AnalysisType: correlation_rule -RuleID: "AWS.Potentially.Stolen.Service.Role" -DisplayName: "DEPRECATED - AWS Potentially Stolen Service Role CR" -Enabled: false -Tags: - - AWS - - DEPRECATED -Severity: Info -Reports: - MITRE ATT&CK: - - TA0006:T1528 # Steal Application Access Token -Description: A role was assumed by an AWS service, followed by a user within 24 hours. This could indicate a stolen or compromised AWS service role. -Detection: - - Sequence: - - ID: Role Assumed by Service - RuleID: Role.Assumed.by.AWS.Service - - ID: Role Assumed by User - RuleID: Role.Assumed.by.User - Transitions: - - ID: Role Assumed by Service TO Role Assumed by User ON username - From: Role Assumed by Service - To: Role Assumed by User - Match: - - On: requestParameters.roleArn - Schedule: - RateMinutes: 1440 - TimeoutMinutes: 20 - LookbackWindowMinutes: 15 -Tests: - - Name: Role Assumed By Service, Followed By Different Role Assumed By User - ExpectedResult: false - RuleOutputs: - - ID: Role Assumed by Service - Matches: - requestParameters.roleArn: - FAKE_ROLE_ARN: - - 0 - - ID: Role Assumed by User - Matches: - requestParameters.roleArn: - OTHER_ROLE_ARN: - - 2 - - Name: Role Assumed By Service, Followed By Role Assumed By User - ExpectedResult: true - RuleOutputs: - - ID: Role Assumed by Service - Matches: - requestParameters.roleArn: - FAKE_ROLE_ARN: - - 0 - - ID: Role Assumed by User - Matches: - requestParameters.roleArn: - FAKE_ROLE_ARN: - - 2 \ No newline at end of file diff --git a/decomissioned_rules.txt b/decomissioned_rules.txt new file mode 100644 index 000000000..95642d3a0 --- /dev/null +++ b/decomissioned_rules.txt @@ -0,0 +1,34 @@ +Abnormally.High.Event.Volume +AWS.SecurityGroup.UnusedSecurityGroup +AWS.DynamoDB.TableEncryption +AWS.Potentially.Stolen.Service.Role +Standard.UnusualLogin +OneLogin.HighRiskLogin +OneLogin.UnusualLogin +OneLogin.AdminRoleAssigned +OneLogin.BruteForceByIP +OneLogin.BruteForceByUsername +Box.Brute.Force.Login +Zoom.UserGrantedAdmin +GCP.IAM.AdminRoleAssigned +Notion.PageViews.ImpossibleTravel +Notion.AccountChangedAfterLogin +IOC.Log4JIPs +IOC.SunburstIPIOCs +IOC.Log4jExploit +IOC.SunburstFQDNIOCs +IOC.SunburstSHA256IOCs +Confluence.0DayIPs +Cloudflare.Firewall.HighVolumeEventsBlockedGreyNoise +Cloudflare.Firewall.HighVolumeEventsBlocked +Cloudflare.Firewall.SuspiciousEventGreyNoise +Cloudflare.HttpRequest.BotHighVolumeGreyNoise +GSuite.PermisssionsDelegated +GSuite.BruteForceLogin +AWS.Console.LoginFailed +AWS.Snapshot.Backup.Exfiltration +AWS.CloudTrail.RootFailedConsoleLogin +AWS.S3.GreyNoiseActivity +AWS.CloudTrail.RootConsoleLogin +Okta.GeographicallyImprobableAccess +Okta.BruteForceLogins \ No newline at end of file diff --git a/packs/aws.yml b/packs/aws.yml index aef920d52..fed6cf780 100644 --- a/packs/aws.yml +++ b/packs/aws.yml @@ -163,7 +163,6 @@ PackDefinition: - VPC.DNS.Tunneling - VPCFlow.Port.Scanning # Correlation Rules - - AWS.Potentially.Stolen.Service.Role - AWS.Privilege.Escalation.Via.User.Compromise - AWS.User.Takeover.Via.Password.Reset # Signal Rules diff --git a/policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.py b/policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.py deleted file mode 100644 index eecf2d181..000000000 --- a/policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.py +++ /dev/null @@ -1,5 +0,0 @@ -from panther_base_helpers import deep_get - - -def policy(resource): - return deep_get(resource, "SSEDescription", "Status") == "ENABLED" diff --git a/policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.yml b/policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.yml deleted file mode 100644 index 1730d950c..000000000 --- a/policies/aws_dynamodb_policies/aws_dynamodb_table_encryption.yml +++ /dev/null @@ -1,164 +0,0 @@ -AnalysisType: policy -Filename: aws_dynamodb_table_encryption.py -PolicyID: "AWS.DynamoDB.TableEncryption" -DisplayName: "[DEPRECATED] AWS DynamoDB Table Encryption" -Enabled: false -ResourceTypes: - - AWS.DynamoDB.Table -Tags: - - AWS - - Data Protection - - Collection:Data From Cloud Storage Object -Reports: - PCI: - - 3.4 - MITRE ATT&CK: - - TA0009:T1530 -Severity: High -Description: > - Table encryption provides an additional layer of data protection by securing from - unauthorized access to the underlying storage. - DEPRECATED: AWS now encrypts dynamodb tables by default. -Runbook: > - https://docs.runpanther.io/alert-runbooks/built-in-policies/aws-dynamodb-table-has-encryption-enabled -Reference: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/EncryptionAtRest.html -Tests: - - Name: Encryption Disabled - ExpectedResult: false - Resource: - { - "AutoScalingDescriptions": null, - "AttributeDefinitions": - [ - { "AttributeName": "attribute_one", "AttributeType": "S" }, - { "AttributeName": "attribute_two", "AttributeType": "S" }, - ], - "BillingModeSummary": - { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "2019-01-01T00:00:00Z", - }, - "CreationDateTime": "2019-01-01T100:00:00Z", - "GlobalSecondaryIndexes": null, - "ItemCount": 42, - "KeySchema": - [ - { "AttributeName": "attribute_one", "KeyType": "HASH" }, - { "AttributeName": "attribute_two", "KeyType": "RANGE" }, - ], - "LatestStreamArn": null, - "LatestStreamLabel": null, - "LocalSecondaryIndexes": null, - "ProvisionedThroughput": - { - "LastDecreaseDateTime": null, - "LastIncreaseDateTime": null, - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0, - }, - "RestoreSummary": null, - "SSEDescription": - { - "KMSMasterKeyArn": "arn:aws:kms:us-west-2:123456789012:key/111222333", - "SSEType": "KMS", - "Status": "DISABLED", - }, - "StreamSpecification": null, - "TableArn": "arn:aws:dynamodb:us-west-2:123456789012:table/example", - "TableId": "aaaaaaaa-bbbb-1111-2222-1111222233334444", - "Name": "example", - "TableSizeBytes": 20000, - "TableStatus": "ACTIVE", - } - - Name: Encryption Enabled - ExpectedResult: true - Resource: - { - "AutoScalingDescriptions": null, - "AttributeDefinitions": - [ - { "AttributeName": "attribute_one", "AttributeType": "S" }, - { "AttributeName": "attribute_two", "AttributeType": "S" }, - ], - "BillingModeSummary": - { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "2019-01-01T00:00:00Z", - }, - "CreationDateTime": "2019-01-01T100:00:00Z", - "GlobalSecondaryIndexes": null, - "ItemCount": 42, - "KeySchema": - [ - { "AttributeName": "attribute_one", "KeyType": "HASH" }, - { "AttributeName": "attribute_two", "KeyType": "RANGE" }, - ], - "LatestStreamArn": null, - "LatestStreamLabel": null, - "LocalSecondaryIndexes": null, - "ProvisionedThroughput": - { - "LastDecreaseDateTime": null, - "LastIncreaseDateTime": null, - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0, - }, - "RestoreSummary": null, - "SSEDescription": - { - "KMSMasterKeyArn": "arn:aws:kms:us-west-2:123456789012:key/111222333", - "SSEType": "KMS", - "Status": "ENABLED", - }, - "StreamSpecification": null, - "TableArn": "arn:aws:dynamodb:us-west-2:123456789012:table/example", - "TableId": "aaaaaaaa-bbbb-1111-2222-1111222233334444", - "Name": "example", - "TableSizeBytes": 20000, - "TableStatus": "ACTIVE", - } - - Name: Encryption Not Set - ExpectedResult: false - Resource: - { - "AutoScalingDescriptions": null, - "AttributeDefinitions": - [ - { "AttributeName": "attribute_one", "AttributeType": "S" }, - { "AttributeName": "attribute_two", "AttributeType": "S" }, - ], - "BillingModeSummary": - { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "2019-01-01T00:00:00Z", - }, - "CreationDateTime": "2019-01-01T100:00:00Z", - "GlobalSecondaryIndexes": null, - "ItemCount": 42, - "KeySchema": - [ - { "AttributeName": "attribute_one", "KeyType": "HASH" }, - { "AttributeName": "attribute_two", "KeyType": "RANGE" }, - ], - "LatestStreamArn": null, - "LatestStreamLabel": null, - "LocalSecondaryIndexes": null, - "ProvisionedThroughput": - { - "LastDecreaseDateTime": null, - "LastIncreaseDateTime": null, - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0, - }, - "RestoreSummary": null, - "SSEDescription": null, - "StreamSpecification": null, - "TableArn": "arn:aws:dynamodb:us-west-2:123456789012:table/example", - "TableId": "aaaaaaaa-bbbb-1111-2222-1111222233334444", - "Name": "example", - "TableSizeBytes": 20000, - "TableStatus": "ACTIVE", - } diff --git a/policies/aws_vpc_policies/aws_security_group_unused_security_group.py b/policies/aws_vpc_policies/aws_security_group_unused_security_group.py deleted file mode 100644 index a8a85b7b3..000000000 --- a/policies/aws_vpc_policies/aws_security_group_unused_security_group.py +++ /dev/null @@ -1,9 +0,0 @@ -from panther_audit import build_client - - -def policy(resource): - client = build_client(resource, "ec2", resource["Region"]) - results = client.describe_network_interfaces( - Filters=[{"Name": "group-id", "Values": [resource["Id"]]}] - ) - return bool(results.get("NetworkInterfaces")) diff --git a/policies/aws_vpc_policies/aws_security_group_unused_security_group.yml b/policies/aws_vpc_policies/aws_security_group_unused_security_group.yml deleted file mode 100644 index 4f2cecd2b..000000000 --- a/policies/aws_vpc_policies/aws_security_group_unused_security_group.yml +++ /dev/null @@ -1,16 +0,0 @@ -AnalysisType: policy -Filename: aws_security_group_unused_security_group.py -PolicyID: "AWS.SecurityGroup.UnusedSecurityGroup" -DisplayName: "--DEPRECATED-- AWS Security Group Used" -Enabled: false -ResourceTypes: - - AWS.EC2.SecurityGroup -Tags: - - AWS - - Deprecated -Severity: Low -Description: > - [DEPRECATED] This policy validates that all Security Groups are in use. Test needs live resource information to make second call. -Runbook: > - Delete unused Security Groups. -Reference: https://docs.aws.amazon.com/cli/latest/userguide/cli-services-ec2-sg.html diff --git a/queries/aws_queries/abnormally_high_event_volume.py b/queries/aws_queries/abnormally_high_event_volume.py deleted file mode 100644 index e51b3bcf2..000000000 --- a/queries/aws_queries/abnormally_high_event_volume.py +++ /dev/null @@ -1,106 +0,0 @@ -from datetime import datetime -from json import dumps, loads -from statistics import mean - -from panther_detection_helpers.caching import get_string_set, put_string_set - -# AVERAGE_THRESHOLD defines the factor by which the log count must exceed the rolling_ledger average -AVERAGE_THRESHOLD = 10 - -# ROLLING_LEDGER_SIZE defines the length of the rolling_ledger list -ROLLING_LEDGER_SIZE = 30 - - -def rule(event): - # Generate the DynamoDB key - key = get_key(event) - - # Get the current number of events, store in num_logs - num_logs = event.get("num_logs", 0) - - # Get the count ledger from DynamoDB (if there is one) - count_ledger = get_count_ledger(key) - - # If there is no count_ledger, we start one and store it in DynamoDB - if not count_ledger: - new_ledger = { - "rolling_ledger": [num_logs], - "highest_counts": {str(datetime.now()): num_logs}, - } - - put_count_ledger(key, new_ledger) - return False - - # Calculate an average of all previous log counts - average_count = mean(count_ledger["rolling_ledger"]) - - # If list length exceeds ROLLING_LEDGER_SIZE, then prune first item on the list - if len(count_ledger["rolling_ledger"]) == ROLLING_LEDGER_SIZE: - count_ledger["rolling_ledger"].pop(0) - - # Append the current count to the list (after the average is calculated) - count_ledger["rolling_ledger"].append(num_logs) - - # Find the highest count in the ledger - highest_count = max(count_ledger["highest_counts"].values()) - - # Assume there is not a new - new_highest_count = False - - # Store a new highest count if found - if num_logs >= highest_count: - count_ledger["highest_counts"][str(datetime.now())] = num_logs - new_highest_count = True - - # Store the updated count ledger in DynamoDB - put_count_ledger(key, count_ledger) - - # Determine if num_logs exceeds average_count by a factor of AVERAGE_THRESHOLD or greater - crossed_average_threshold = num_logs / average_count >= AVERAGE_THRESHOLD - - # Alert only when AVERAGE_THRESHOLD is crossed && there is new highest_count value - return crossed_average_threshold and new_highest_count - - -def title(event): - return f"Abnormally high event volume detected in [{event.get('p_log_type')}]" - - -def alert_context(event): - key = get_key(event) - count_ledger = get_count_ledger(key) - - context = {} - context["Log Type"] = event.get("p_log_type") - context["Average Threshold"] = AVERAGE_THRESHOLD - context["Rolling Ledger"] = count_ledger["rolling_ledger"] - context["Highest Counts"] = count_ledger["highest_counts"] - context["Rolling Ledger Average"] = mean(count_ledger["rolling_ledger"]) - - # If an alert has fired, we reset the highest_counts dict - count_ledger["highest_counts"] = {} - put_count_ledger(key, count_ledger) - - return context - - -def put_count_ledger(key, count_ledger): - put_string_set(key, [dumps(count_ledger)]) - - -def get_count_ledger(key): - count_ledger = get_string_set(key) - - # Handle Unit Tests with mock overrides - if isinstance(count_ledger, str): - return {"rolling_ledger": [40, 10, 20], "highest_counts": {str(datetime.now()): 40}} - - # Since DynamoDB returns a string set, we need to deserialize into a dict - if count_ledger: - return loads(count_ledger.pop()) - - return None - - -def get_key(event): - return str(event.get("p_log_type")) + __name__ diff --git a/queries/aws_queries/abnormally_high_event_volume.yml b/queries/aws_queries/abnormally_high_event_volume.yml deleted file mode 100644 index a34f8f559..000000000 --- a/queries/aws_queries/abnormally_high_event_volume.yml +++ /dev/null @@ -1,36 +0,0 @@ -AnalysisType: scheduled_rule -Description: This detection works with a Scheduled Query to store a rolling count of events for given Log Types. A count average is calculated from the list and then compared with the most recent count. If that count exceeds a given threshold, the volume is considered abnormally high. This could represent a DoS attack. -DisplayName: "--DEPRECATED-- Abnormally High Event Volume" -Enabled: false -Filename: abnormally_high_event_volume.py -Reports: - MITRE ATT&CK: - - TA0040:T1499 -Reference: https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html -Severity: Medium -Tests: - - ExpectedResult: false - Log: - num_logs: 5 - p_log_type: AWS.CloudTrail - Mocks: - - objectName: get_string_set - returnValue: "True" - - objectName: put_string_set - returnValue: "True" - Name: Abnormal = False - - ExpectedResult: true - Log: - num_logs: 5e+06 - p_log_type: AWS.CloudTrail - Mocks: - - objectName: get_string_set - returnValue: "True" - - objectName: put_string_set - returnValue: "True" - Name: Abnormal = True -DedupPeriodMinutes: 30 -RuleID: "Abnormally.High.Event.Volume" -Threshold: 1 -ScheduledQueries: - - AWS CloudTrail 2-minute count diff --git a/rules/aws_cloudtrail_rules/aws_console_login_failed.py b/rules/aws_cloudtrail_rules/aws_console_login_failed.py deleted file mode 100644 index 9cbe7dab7..000000000 --- a/rules/aws_cloudtrail_rules/aws_console_login_failed.py +++ /dev/null @@ -1,20 +0,0 @@ -from panther_base_helpers import aws_rule_context, deep_get -from panther_default import lookup_aws_account_name - - -def rule(event): - return ( - event.get("eventName") == "ConsoleLogin" - and deep_get(event, "userIdentity", "type") == "IAMUser" - and deep_get(event, "responseElements", "ConsoleLogin") == "Failure" - ) - - -def title(event): - return ( - f"AWS logins failed in account [{lookup_aws_account_name(event.get('recipientAccountId'))}]" - ) - - -def alert_context(event): - return aws_rule_context(event) diff --git a/rules/aws_cloudtrail_rules/aws_console_login_failed.yml b/rules/aws_cloudtrail_rules/aws_console_login_failed.yml deleted file mode 100644 index a48a0fa1e..000000000 --- a/rules/aws_cloudtrail_rules/aws_console_login_failed.yml +++ /dev/null @@ -1,145 +0,0 @@ -AnalysisType: rule -Filename: aws_console_login_failed.py -RuleID: "AWS.Console.LoginFailed" -DisplayName: "--DEPRECATED-- Failed Console Login" -DedupPeriodMinutes: 60 -Enabled: false -LogTypes: - - AWS.CloudTrail -Tags: - - AWS - - Identity & Access Management - - Authentication - - Credential Access:Brute Force -Threshold: 5 -Reports: - CIS: - - 3.6 - MITRE ATT&CK: - - TA0006:T1110 -Severity: Low -Description: > - A console login failed. -Runbook: https://docs.runpanther.io/alert-runbooks/built-in-rules/aws-console-login-failed -Reference: https://amzn.to/3aMSmTd -SummaryAttributes: - - userAgent - - sourceIpAddress - - recipientAccountId - - p_any_aws_arns -Tests: - - Name: Failed Login - ExpectedResult: true - Log: - { - "eventVersion": "1.05", - "userIdentity": - { - "type": "IAMUser", - "principalId": "1111", - "arn": "arn:aws:iam::123456789012:user/tester", - "accountId": "123456789012", - "userName": "tester", - }, - "eventTime": "2019-01-01T00:00:00Z", - "eventSource": "signin.amazonaws.com", - "eventName": "ConsoleLogin", - "awsRegion": "us-east-1", - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Failure" }, - "additionalEventData": - { - "LoginTo": "https://console.aws.amazon.com/console/", - "MobileVersion": "No", - "MFAUsed": "No", - }, - "eventID": "1", - "eventType": "AwsConsoleSignIn", - "recipientAccountId": "123456789012", - } - - Name: Successful Login - ExpectedResult: false - Log: - { - "eventVersion": "1.05", - "userIdentity": - { - "type": "IAMUser", - "principalId": "1111", - "arn": "arn:aws:iam::123456789012:user/tester", - "accountId": "123456789012", - "userName": "tester", - }, - "eventTime": "2019-01-01T00:00:00Z", - "eventSource": "signin.amazonaws.com", - "eventName": "ConsoleLogin", - "awsRegion": "us-east-1", - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "additionalEventData": - { - "LoginTo": "https://console.aws.amazon.com/console/", - "MobileVersion": "No", - "MFAUsed": "No", - }, - "eventID": "1", - "eventType": "AwsConsoleSignIn", - "recipientAccountId": "123456789012", - } - - Name: Non Login Event - ExpectedResult: false - Log: - { - "eventVersion": "1.06", - "userIdentity": - { - "type": "AssumedRole", - "principalId": "1111:tester", - "arn": "arn:aws:sts::123456789012:user/tester", - "accountId": "123456789012", - "accessKeyId": "1", - "sessionContext": - { - "sessionIssuer": - { - "type": "Role", - "principalId": "1111", - "arn": "arn:aws:iam::123456789012:user/tester", - "accountId": "123456789012", - "userName": "tester", - }, - "attributes": - { - "creationDate": "2019-01-01T00:00:00Z", - "mfaAuthenticated": "true", - }, - }, - }, - "eventTime": "2019-01-01T00:00:00Z", - "eventSource": "dynamodb.amazonaws.com", - "eventName": "DescribeTable", - "awsRegion": "us-west-2", - "sourceIPAddress": "111.111.111.111", - "userAgent": "console.amazonaws.com", - "requestParameters": { "tableName": "table" }, - "responseElements": null, - "requestID": "1", - "eventID": "1", - "readOnly": true, - "resources": - [ - { - "accountId": "123456789012", - "type": "AWS::DynamoDB::Table", - "ARN": "arn::::table/table", - }, - ], - "eventType": "AwsApiCall", - "apiVersion": "2012-08-10", - "managementEvent": true, - "recipientAccountId": "123456789012", - } diff --git a/rules/aws_cloudtrail_rules/aws_root_console_login.py b/rules/aws_cloudtrail_rules/aws_root_console_login.py deleted file mode 100644 index cbfd686ad..000000000 --- a/rules/aws_cloudtrail_rules/aws_root_console_login.py +++ /dev/null @@ -1,18 +0,0 @@ -from panther_base_helpers import aws_rule_context, deep_get - - -def rule(event): - # Only check console logins - if event.get("eventName") != "ConsoleLogin": - return False - - # Only check root activity - if deep_get(event, "userIdentity", "type") != "Root": - return False - - # Only alert if the login was a success - return deep_get(event, "responseElements", "ConsoleLogin") == "Success" - - -def alert_context(event): - return aws_rule_context(event) diff --git a/rules/aws_cloudtrail_rules/aws_root_console_login.yml b/rules/aws_cloudtrail_rules/aws_root_console_login.yml deleted file mode 100644 index d95819297..000000000 --- a/rules/aws_cloudtrail_rules/aws_root_console_login.yml +++ /dev/null @@ -1,150 +0,0 @@ -AnalysisType: rule -Filename: aws_root_console_login.py -RuleID: "AWS.CloudTrail.RootConsoleLogin" -DisplayName: "--DEPRECATED-- Root Account Console Login" -Enabled: false -LogTypes: - - AWS.CloudTrail -Tags: - - AWS - - Identity and Access Management - - Initial Access:Valid Accounts -Severity: High -Reports: - MITRE ATT&CK: - - TA0001:T1078 -Description: Deprecated. Please see AWS.Console.RootLogin instead. -Runbook: > - Verify that the root login was authorized. If not, investigate the root activity and ensure no malicious activity was performed. Change the root password. -Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html -SummaryAttributes: - - eventSource - - userAgent - - sourceIpAddress - - recipientAccountId - - p_any_aws_arns -Tests: - - Name: Root Console Login - ExpectedResult: true - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "ConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:root", - "principalId": "123456789012", - "type": "Root", - }, - } - - Name: Root Non Console Login - ExpectedResult: false - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "NonConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:root", - "principalId": "123456789012", - "type": "Root", - }, - } - - Name: Non Root Console Login - ExpectedResult: false - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "ConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:user/bob", - "principalId": "123456789012", - "type": "IAMUser", - }, - } - - Name: Root Failed Console Login - ExpectedResult: false - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "ConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Failure" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:root", - "principalId": "123456789012", - "type": "Root", - }, - } diff --git a/rules/aws_cloudtrail_rules/aws_root_failed_console_login.py b/rules/aws_cloudtrail_rules/aws_root_failed_console_login.py deleted file mode 100644 index 4435c25cc..000000000 --- a/rules/aws_cloudtrail_rules/aws_root_failed_console_login.py +++ /dev/null @@ -1,18 +0,0 @@ -from panther_base_helpers import aws_rule_context, deep_get - - -def rule(event): - # Only check console logins - if event.get("eventName") != "ConsoleLogin": - return False - - # Only check root activity - if deep_get(event, "userIdentity", "type") != "Root": - return False - - # Only alert if the login was a failure - return deep_get(event, "responseElements", "ConsoleLogin") != "Success" - - -def alert_context(event): - return aws_rule_context(event) diff --git a/rules/aws_cloudtrail_rules/aws_root_failed_console_login.yml b/rules/aws_cloudtrail_rules/aws_root_failed_console_login.yml deleted file mode 100644 index 989d60b60..000000000 --- a/rules/aws_cloudtrail_rules/aws_root_failed_console_login.yml +++ /dev/null @@ -1,151 +0,0 @@ -AnalysisType: rule -Filename: aws_root_failed_console_login.py -RuleID: "AWS.CloudTrail.RootFailedConsoleLogin" -DisplayName: "--DEPRECATED-- Root Account Failed Console Login" -Enabled: false -LogTypes: - - AWS.CloudTrail -Tags: - - AWS - - Identity and Access Management - - Initial Access:Valid Accounts -Severity: High -Reports: - MITRE ATT&CK: - - TA0001:T1078 -Description: > - Deprecated. Please see AWS.Console.RootLoginFailed instead. -Runbook: > - Verify that the root login attempt was authorized. If not, investigate the root account failed logins to see if there is a pattern. -Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html -SummaryAttributes: - - eventName - - userAgent - - sourceIpAddress - - recipientAccountId - - p_any_aws_arns -Tests: - - Name: Root Console Login - ExpectedResult: false - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "ConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:root", - "principalId": "123456789012", - "type": "Root", - }, - } - - Name: Root Non Console Login - ExpectedResult: false - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "NonConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:root", - "principalId": "123456789012", - "type": "Root", - }, - } - - Name: Non Root Console Login - ExpectedResult: false - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "ConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Success" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:user/bob", - "principalId": "123456789012", - "type": "IAMUser", - }, - } - - Name: Root Failed Console Login - ExpectedResult: true - Log: - { - "additionalEventData": - { - "LoginTo": "https://us-west-2.console.aws.amazon.com/console/home?region=us-west-2", - "MFAUsed": "Yes", - "MobileVersion": "No", - }, - "awsRegion": "us-east-1", - "eventID": "111", - "eventName": "ConsoleLogin", - "eventSource": "signin.amazonaws.com", - "eventTime": "2019-01-01T00:00:00Z", - "eventType": "AwsConsoleSignIn", - "eventVersion": "1.05", - "recipientAccountId": "123456789012", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Failure" }, - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla/5.0 Ti83", - "userIdentity": - { - "accessKeyId": "", - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:root", - "principalId": "123456789012", - "type": "Root", - }, - } diff --git a/rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.py b/rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.py deleted file mode 100644 index 1be740fc3..000000000 --- a/rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.py +++ /dev/null @@ -1,77 +0,0 @@ -from ipaddress import ip_address - -from panther_base_helpers import deep_get, pattern_match_list -from panther_greynoise_helpers import GetGreyNoiseObject, GetGreyNoiseRiotObject - -# pylint: disable=too-many-return-statements,invalid-name,unused-argument,global-at-module-level,global-variable-undefined - -# Monitor for GetObject events from S3. -# Also check ListBucket to reveal object enumeration. -_S3_EVENT_LIST = ( - "ListBucket*", - "GetObject*", -) - -# Some AWS IP addresses listed as "malicious" are stale -# This enables allowing specific roles where this may occur -_ALLOWED_ROLES = ( - "*PantherAuditRole-*", - "*PantherLogProcessingRole-*", -) - - -def rule(event): - # pylint: disable=too-complex - # Filter: Non-S3 events - if event.get("eventSource") != "s3.amazonaws.com": - return False - # Filter: Errors - if event.get("errorCode"): - return False - # Filter: Internal AWS - if deep_get(event, "userIdentity", "type") in ("AWSAccount", "AWSService"): - return False - # Filter: Non "Get" events - if not pattern_match_list(event.get("eventName"), _S3_EVENT_LIST): - return False - - # Validate the IP is actually an IP (sometimes it's a string) - try: - ip_address(event.get("sourceIPAddress")) - except ValueError: - return False - - # Create GreyNoise Objects - global NOISE - NOISE = GetGreyNoiseObject(event) - riot = GetGreyNoiseRiotObject(event) - - # If IP is in RIOT dataset we can assume safe, do not alert - if riot.is_riot("sourceIPAddress"): - return False - - # Check that the IP is classified as 'malicious' - if NOISE.classification("sourceIPAddress") == "malicious": - # Filter: Roles that generate FP's if used from AWS IP Space - if pattern_match_list(deep_get(event, "userIdentity", "arn"), _ALLOWED_ROLES): - # Only Greynoise advanced provides AS organization info - if NOISE.subscription_level() == "advanced": - if NOISE.organization("sourceIPAddress") == "Amazon.com, Inc.": - return False - # return false if the role is seen and we are not able to validate the AS organization - else: - return False - return True - - return False - - -def title(event): - # Group by ip-arn combinations - ip = deep_get(event, "sourceIPAddress") - arn = deep_get(event, "userIdentity", "arn") - return f"GreyNoise malicious S3 events detected by {ip} from {arn}" - - -def alert_context(event): - return NOISE.context("sourceIPAddress") diff --git a/rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.yml b/rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.yml deleted file mode 100644 index 8559a052a..000000000 --- a/rules/aws_cloudtrail_rules/aws_s3_activity_greynoise.yml +++ /dev/null @@ -1,451 +0,0 @@ -AnalysisType: rule -Description: S3 operations from known malicious GreyNoise classifications. Note that this rule will only work with S3 object-level logging enabled for a given bucket. -DisplayName: "--DEPRECATED-- GreyNoise Malicious AWS S3 Get/List Object" -Enabled: false -Filename: aws_s3_activity_greynoise.py -Reference: https://attack.mitre.org/techniques/T1530/ -Reports: - MITRE ATT&CK: - - TA0009:T1530 -Runbook: Investigate all actions taken and validate that the ARN conducting the acitivty was not compromised -Severity: Info -CreateAlert: false -DedupPeriodMinutes: 60 -Threshold: 1 -SummaryAttributes: - - awsRegion - - eventName - - p_any_aws_arns - - recipientAccountId - - sourceIPAddress - - userAgent -LogTypes: - - AWS.CloudTrail -RuleID: "AWS.S3.GreyNoiseActivity" -Tags: - - AWS - - GreyNoise - - Collection:Data From Cloud Storage Object - - Deprecated -Tests: - - ExpectedResult: true - Name: GetObject from Malicious GreyNoise finding - Log: - { - "additionalEventData": - { - "AuthenticationMethod": "AuthHeader", - "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "SignatureVersion": "SigV4", - "bytesTransferredIn": 0, - "bytesTransferredOut": 2441, - }, - "awsRegion": "eu-central-1", - "eventID": "60858000-f093-465f-9d62-97655522ab1a", - "eventName": "GetObject", - "eventSource": "s3.amazonaws.com", - "eventTime": "2021-08-16 16:39:43", - "eventType": "AwsApiCall", - "eventVersion": "1.08", - "managementEvent": false, - "readOnly": true, - "recipientAccountId": "111122223333", - "requestID": "EFMWSVE492PYSCYN", - "requestParameters": { "bucketName": "panther" }, - "resources": [], - "sourceIPAddress": "142.93.204.250", - "userAgent": "[aws-sdk-go/1.40.21 (go1.16.3; linux; amd64) exec-env/AWS_Lambda_go1.x S3Manager]", - "userIdentity": - { - "accessKeyId": "AAAA22222XXXXXBBBBBBB", - "accountId": "111122223333", - "arn": "arn:aws:sts::111122223333:assumed-role/Panther/1629131846392631241", - "principalId": "AAAA22222XXXXXBBBBBBB:1629131846392631241", - "sessionContext": - { - "attributes": - { - "creationDate": "2021-08-16T16:37:26Z", - "mfaAuthenticated": "false", - }, - "sessionIssuer": - { - "accountId": "111122223333", - "arn": "arn:aws:iam::111122223333:role/Panther", - "principalId": "AAAA22222XXXXXBBBBBBB", - "type": "Role", - "userName": "Panther", - }, - }, - "type": "AssumedRole", - }, - "p_event_time": "2021-08-16 16:39:43", - "p_parse_time": "2021-08-16 16:44:29.47", - "p_log_type": "AWS.CloudTrail", - "p_enrichment": - { - "greynoise_noise_basic": - { - "sourceIPAddress": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": ["Mirai", "ZMap Client"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - "p_any_ip_addresses": ["142.93.204.250"], - "p_any_aws_account_ids": ["111122223333"], - "p_any_aws_arns": - [ - "arn:aws:iam::111122223333:role/Panther", - "arn:aws:sts::111122223333:assumed-role/Panther/1629131846392631241", - ], - } - - ExpectedResult: false - Name: GetBucketLocation Not Malicious - Log: - { - "additionalEventData": - { - "AuthenticationMethod": "AuthHeader", - "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "SignatureVersion": "SigV4", - "bytesTransferredIn": 0, - "bytesTransferredOut": 108, - }, - "awsRegion": "us-west-2", - "eventID": "dd79436a-fbe1-4f77-ad9d-7457801f0c08", - "eventName": "GetBucketLocation", - "eventSource": "s3.amazonaws.com", - "eventTime": "2021-05-29 14:59:57", - "eventType": "AwsApiCall", - "eventVersion": "1.08", - "managementEvent": true, - "readOnly": true, - "recipientAccountId": "111122223333", - "requestID": "109V6Z3EQ7VAPP76", - "requestParameters": - { - "Host": "s3.us-west-2.amazonaws.com", - "bucketName": "panther", - "location": "", - }, - "resources": - [ - { - "accountId": "111122223333", - "arn": "arn:aws:s3:::panther", - "type": "AWS::S3::Bucket", - }, - ], - "sourceIPAddress": "34.221.72.137", - "userAgent": "[aws-sdk-go/1.37.8 (go1.16.3; linux; amd64) exec-env/AWS_Lambda_go1.x]", - "userIdentity": - { - "accessKeyId": "AAAASSSSTTTTTVVVVVVV", - "accountId": "111122223333", - "arn": "arn:aws:sts::111122223333:assumed-role/Panther/1622300396286133103", - "principalId": "AAAASSSSTTTTTVVVVVVV", - "sessionContext": - { - "attributes": - { - "creationDate": "2021-05-29T14:59:56Z", - "mfaAuthenticated": "false", - }, - "sessionIssuer": - { - "accountId": "111122223333", - "arn": "arn:aws:iam::111122223333:role/Panther", - "principalId": "AAAASSSSTTTTTVVVVVVV", - "type": "Role", - "userName": "Panther", - }, - "webIdFederationData": {}, - }, - "type": "AssumedRole", - }, - "p_event_time": "2021-05-29 14:59:57", - "p_parse_time": "2021-05-29 15:00:43.237", - "p_log_type": "AWS.CloudTrail", - "p_source_label": "CloudTrail Test", - "p_any_ip_addresses": ["34.221.72.137"], - "p_any_aws_account_ids": ["111122223333"], - "p_any_aws_arns": - ["arn:aws:iam::111122223333:role/Panther", "arn:aws:s3:::panther"], - } - - ExpectedResult: false - Name: Malicious GreyNoise finding that is allowed based on role and AS Org - Log: - { - "additionalEventData": - { - "AuthenticationMethod": "AuthHeader", - "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "SignatureVersion": "SigV4", - "bytesTransferredIn": 0, - "bytesTransferredOut": 2441, - }, - "awsRegion": "eu-central-1", - "eventID": "60858000-f093-465f-9d62-97655522ab1a", - "eventName": "GetObject", - "eventSource": "s3.amazonaws.com", - "eventTime": "2021-08-16 16:39:43", - "eventType": "AwsApiCall", - "eventVersion": "1.08", - "managementEvent": false, - "readOnly": true, - "recipientAccountId": "111122223333", - "requestID": "EFMWSVE492PYSCYN", - "requestParameters": { "bucketName": "panther" }, - "resources": [], - "sourceIPAddress": "142.93.204.250", - "userAgent": "[aws-sdk-go/1.40.21 (go1.16.3; linux; amd64) exec-env/AWS_Lambda_go1.x S3Manager]", - "userIdentity": - { - "accessKeyId": "AAAA22222XXXXXBBBBBBB", - "accountId": "111122223333", - "arn": "arn:aws:sts::111122223333:assumed-role/PantherAuditRole-region/1629131846392631241", - "principalId": "AAAA22222XXXXXBBBBBBB:1629131846392631241", - "sessionContext": - { - "attributes": - { - "creationDate": "2021-08-16T16:37:26Z", - "mfaAuthenticated": "false", - }, - "sessionIssuer": - { - "accountId": "111122223333", - "arn": "arn:aws:iam::111122223333:role/Panther", - "principalId": "AAAA22222XXXXXBBBBBBB", - "type": "Role", - "userName": "Panther", - }, - }, - "type": "AssumedRole", - }, - "p_event_time": "2021-08-16 16:39:43", - "p_parse_time": "2021-08-16 16:44:29.47", - "p_log_type": "AWS.CloudTrail", - "p_enrichment": - { - "greynoise_noise_basic": - { - "sourceIPAddress": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "Amazon.com, Inc", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": ["Mirai", "ZMap Client"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - "p_any_ip_addresses": ["142.93.204.250"], - "p_any_aws_account_ids": ["111122223333"], - "p_any_aws_arns": - [ - "arn:aws:iam::111122223333:role/Panther", - "arn:aws:sts::111122223333:assumed-role/Panther/1629131846392631241", - ], - } - - ExpectedResult: false - Name: Malicious GreyNoise finding that is allowed based on role and AS Org and GreyNoise Advanced - Log: - { - "additionalEventData": - { - "AuthenticationMethod": "AuthHeader", - "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "SignatureVersion": "SigV4", - "bytesTransferredIn": 0, - "bytesTransferredOut": 5441, - "x-amz-id-2": "1234", - }, - "awsRegion": "eu-west-2", - "eventCategory": "Management", - "eventID": "aaaaaaaa-aaaa-4967-a2a6-d464e326891a", - "eventName": "ListBuckets", - "eventSource": "s3.amazonaws.com", - "eventTime": "2022-09-08 18:40:24.000000000", - "eventType": "AwsApiCall", - "eventVersion": "1.08", - "managementEvent": true, - "p_alert_creation_time": "2022-09-08 18:47:05.228700000", - "p_alert_id": "ffffffffffffffffffffffffffffffff", - "p_alert_severity": "HIGH", - "p_alert_update_time": "2022-09-08 18:47:05.228700000", - "p_any_aws_account_ids": ["123456789012"], - "p_any_aws_arns": - [ - "arn:aws:iam::123456789012:role/PantherAuditRole-region", - "arn:aws:sts::123456789012:assumed-role/PantherAuditRole-region/1111111111111111111", - ], - "p_any_ip_addresses": ["54.245.38.112"], - "p_enrichment": - { - "greynoise_noise_advanced": - { - "p_any_ip_addresses": - [ - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "first_seen": "2022-06-16 00:00:00.000000000", - "ip": "54.245.38.112", - "last_seen_timestamp": "2022-06-17 20:35:16.000000000", - "metadata": - { - "asn": "AS16509", - "category": "hosting", - "city": "Boardman", - "country": "United States", - "country_code": "US", - "organization": "Amazon.com, Inc.", - "os": "Windows 7/8", - "rdns": "ec2-54-245-38-112.us-west-2.compute.amazonaws.com", - "region": "Oregon", - "tor": false, - }, - "spoofable": false, - "tags": ["PHPMyAdmin Worm", "Web Crawler"], - "vpn": false, - "vpn_service": "N/A", - }, - ], - "sourceIPAddress": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "first_seen": "2022-06-16 00:00:00.000000000", - "ip": "54.245.38.112", - "last_seen_timestamp": "2022-06-17 20:35:16.000000000", - "metadata": - { - "asn": "AS16509", - "category": "hosting", - "city": "Boardman", - "country": "United States", - "country_code": "US", - "organization": "Amazon.com, Inc.", - "os": "Windows 7/8", - "rdns": "ec2-54-245-38-112.us-west-2.compute.amazonaws.com", - "region": "Oregon", - "tor": false, - }, - "spoofable": false, - "tags": ["PHPMyAdmin Worm", "Web Crawler"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - "p_event_time": "2022-09-08 18:40:24.000000000", - "p_log_type": "AWS.CloudTrail", - "p_parse_time": "2022-09-08 18:46:13.942177641", - "p_row_id": "abcdefabcdefabcdefabcdef13f4e402", - "p_rule_id": "AWS.S3.GreyNoiseActivity", - "p_rule_reports": { "MITRE ATT&CK": ["TA0009:T1530"] }, - "p_rule_severity": "HIGH", - "p_rule_tags": - ["AWS", "Collection:Data From Cloud Storage Object", "GreyNoise"], - "p_source_id": "12345678-abcd-1234-abcd-1234abcd1234", - "p_source_label": "Some Human Friendly Source", - "readOnly": true, - "recipientAccountId": "0121234512345", - "requestID": "HJHJHJHJHJHJHJHJ", - "requestParameters": { "Host": "s3.eu-west-2.amazonaws.com" }, - "sourceIPAddress": "54.245.38.112", - "tlsDetails": - { - "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "clientProvidedHostHeader": "s3.eu-west-2.amazonaws.com", - "tlsVersion": "TLSv1.2", - }, - "userAgent": "[aws-sdk-go/1.44.77 (go1.19; linux; arm64)]", - "userIdentity": - { - "accessKeyId": "ASIANNNNNNNNNNNNNNNN", - "accountId": "123456789012", - "arn": "arn:aws:sts::123456789012:assumed-role/PantherAuditRole-region/1111111111111111111", - "principalId": "AROANNNNNNNNNNNNNNNNN:1111111111111111111", - "sessionContext": - { - "attributes": - { - "creationDate": "2022-09-08T18:40:23Z", - "mfaAuthenticated": "false", - }, - "sessionIssuer": - { - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:role/PantherAuditRole-region", - "principalId": "AROAWYT7IVYHE7N6ZVSGR", - "type": "Role", - "userName": "PantherAuditRole-region", - }, - "webIdFederationData": {}, - }, - "type": "AssumedRole", - }, - } diff --git a/rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.py b/rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.py deleted file mode 100644 index 99172bbf6..000000000 --- a/rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.py +++ /dev/null @@ -1,21 +0,0 @@ -from panther_base_helpers import aws_rule_context, deep_get - - -def rule(event): - return ( - event.get("eventSource") == "ec2.amazonaws.com" - and event.get("eventName") == "ModifySnapshotAttribute" - ) - - -def title(event): - return ( - f"[{deep_get(event,'userIdentity','arn')}] " - "modified snapshot attributes for " - f"[{deep_get(event,'requestParameters','snapshotId')}] " - f"in [{event.get('recipientAccountId')}] - [{event.get('awsRegion')}]." - ) - - -def alert_context(event): - return aws_rule_context(event) diff --git a/rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.yml b/rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.yml deleted file mode 100644 index 4c40e8893..000000000 --- a/rules/aws_cloudtrail_rules/aws_snapshot_backup_exfiltration.yml +++ /dev/null @@ -1,107 +0,0 @@ -AnalysisType: rule -Description: Detects the modification of an EC2 snapshot's permissions to enable access from another account. -DisplayName: "--DEPRECATED-- Snapshot Backup Exfiltration" -Enabled: false -Filename: aws_snapshot_backup_exfiltration.py -Reports: - MITRE ATT&CK: - - TA0010:T1537 -Reference: https://docs.aws.amazon.com/prescriptive-guidance/latest/backup-recovery/ec2-backup.html -Severity: Medium -Tests: - - ExpectedResult: true - Log: - awsRegion: us-east-1 - eventCategory: Management - eventName: ModifySnapshotAttribute - eventSource: ec2.amazonaws.com - eventTime: "2022-09-29 22:28:40" - eventType: AwsApiCall - eventVersion: "1.08" - managementEvent: true - readOnly: false - recipientAccountId: "12345" - requestParameters: - attributeType: CREATE_VOLUME_PERMISSION - createVolumePermission: - add: - items: - - userId: "54321" - snapshotId: snap-12345 - responseElements: - _return: true - requestId: "12345" - sessionCredentialFromConsole: true - sourceIPAddress: AWS Internal - userAgent: AWS Internal - userIdentity: - accessKeyId: ABC123 - accountId: "12345" - arn: arn:aws:sts::12345:assumed-role/aa/b - principalId: aa/b - sessionContext: - attributes: - creationDate: "2022-09-29T22:22:46Z" - mfaAuthenticated: "true" - sessionIssuer: - accountId: "12345" - arn: arn:aws:iam::12345/aa/cc - principalId: "12345" - type: Role - userName: cc - webIdFederationData: {} - type: AssumedRole - Name: Modified Snapshot Attribute - - ExpectedResult: false - Log: - awsRegion: us-east-1 - eventCategory: Management - eventName: TerminateInstances - eventSource: ec2.amazonaws.com - eventTime: "2022-09-29 22:27:40" - eventType: AwsApiCall - eventVersion: "1.08" - managementEvent: true - readOnly: false - recipientAccountId: "12345" - requestParameters: - instancesSet: - items: - - instanceId: i-12345 - responseElements: - instancesSet: - items: - - currentState: - code: 32 - name: shutting-down - instanceId: i-12345 - previousState: - code: 16 - name: running - requestId: "12356" - sessionCredentialFromConsole: true - sourceIPAddress: AWS Internal - userAgent: AWS Internal - userIdentity: - accessKeyId: ABCD - accountId: "12345" - arn: arn:aws:sts::12345/aa - principalId: ABCD/12345 - sessionContext: - attributes: - creationDate: "2022-09-29T22:22:46Z" - mfaAuthenticated: "true" - sessionIssuer: - accountId: "927278427150" - arn: arn:aws:iam::ABCD/EFGH - principalId: ABCD/12345 - type: Role - userName: CCC - webIdFederationData: {} - type: AssumedRole - Name: other ec2 event -DedupPeriodMinutes: 60 -LogTypes: - - AWS.CloudTrail -RuleID: "AWS.Snapshot.Backup.Exfiltration" -Threshold: 1 diff --git a/rules/box_rules/box_brute_force_login.py b/rules/box_rules/box_brute_force_login.py deleted file mode 100644 index e2cb6994c..000000000 --- a/rules/box_rules/box_brute_force_login.py +++ /dev/null @@ -1,12 +0,0 @@ -from panther_base_helpers import deep_get - - -def rule(event): - return event.get("event_type") == "FAILED_LOGIN" - - -def title(event): - return ( - f"User [{deep_get(event, 'source', 'name', default='')}]" - f" has exceeded the failed login threshold." - ) diff --git a/rules/box_rules/box_brute_force_login.yml b/rules/box_rules/box_brute_force_login.yml deleted file mode 100644 index 494230e87..000000000 --- a/rules/box_rules/box_brute_force_login.yml +++ /dev/null @@ -1,52 +0,0 @@ -AnalysisType: rule -Filename: box_brute_force_login.py -RuleID: "Box.Brute.Force.Login" -DisplayName: "--DEPRECATED -- Box Brute Force Login" -Enabled: false -LogTypes: - - Box.Event -Tags: - - Box -Severity: Medium -Description: > - A Box user was denied access more times than the configured threshold. -Threshold: 10 -DedupPeriodMinutes: 10 -Reference: https://support.box.com/hc/en-us/articles/360043695174-Logging-in-to-Box -Runbook: > - Analyze the IP they came from, and other actions taken before/after. Check if this user eventually authenticated successfully. -SummaryAttributes: - - event_type - - ip_address -Tests: - - Name: Regular Event - ExpectedResult: false - Log: - { - "type": "event", - "additional_details": '{"key": "value"}', - "created_by": - { - "id": "12345678", - "type": "user", - "login": "cat@example", - "name": "Bob Cat", - }, - "event_type": "DELETE", - } - - Name: Login Failed - ExpectedResult: true - Log: - { - "type": "event", - "additional_details": '{"key": "value"}', - "created_by": - { - "id": "12345678", - "type": "user", - "login": "cat@example", - "name": "Bob Cat", - }, - "event_type": "FAILED_LOGIN", - "source": { "id": "12345678", "type": "user", "name": "Bob Cat" }, - } diff --git a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.py b/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.py deleted file mode 100644 index e0215fb73..000000000 --- a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.py +++ /dev/null @@ -1,27 +0,0 @@ -from global_filter_cloudflare import filter_include_event -from panther_cloudflare_helpers import cloudflare_fw_alert_context - - -def rule(event): - if not filter_include_event(event): - return False - return event.get("Action", "") == "block" - - -def title(event): - return ( - f"Cloudflare: High Volume of Block Actions - " - f"from [{event.get('ClientIP', '')}] " - f"to [{event.get('ClientRequestHost', '')}] " - ) - - -def dedup(event): - return ( - f"{event.get('ClientIP', '')}:" - f"{event.get('ClientRequestHost', '')}" - ) - - -def alert_context(event): - return cloudflare_fw_alert_context(event) diff --git a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.yml b/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.yml deleted file mode 100644 index 42f6b6c72..000000000 --- a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked.yml +++ /dev/null @@ -1,90 +0,0 @@ -AnalysisType: rule -Filename: cloudflare_firewall_high_volume_events_blocked.py -RuleID: "Cloudflare.Firewall.HighVolumeEventsBlocked" -DisplayName: "--DEPRECATED-- Cloudflare - High Volume Events Blocked" -Enabled: false -LogTypes: - - Cloudflare.Firewall -Tags: - - Cloudflare -Severity: Low -Description: Monitors high volume events blocked from the same IP -Runbook: Inspect and monitor internet-facing services for potential outages -Reference: https://developers.cloudflare.com/firewall/cf-firewall-rules/actions/ -DedupPeriodMinutes: 60 # 1 hour -Threshold: 200 -SummaryAttributes: - - ClientRequestUserAgent - - ClientRequestPath - - Action - - EdgeResponseStatus - - OriginResponseStatus -Tests: - - Name: Blocked Event - ExpectedResult: true - Log: - { - "Action": "block", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "127.0.0.1", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - } - - Name: Skip Event - ExpectedResult: false - Log: - { - "Action": "skip", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "127.0.0.1", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - } diff --git a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.py b/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.py deleted file mode 100644 index 705e893e8..000000000 --- a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.py +++ /dev/null @@ -1,52 +0,0 @@ -from ipaddress import ip_address - -from global_filter_cloudflare import filter_include_event -from panther_cloudflare_helpers import cloudflare_fw_alert_context -from panther_greynoise_helpers import GetGreyNoiseObject, GetGreyNoiseRiotObject - - -def rule(event): - if not filter_include_event(event): - return False - if event.get("Action") != "block": - return False - - # Validate the IP is actually an IP - try: - ip_address(event.get("ClientIP")) - except ValueError: - return False - - # If IP is in the RIOT dataset, we can assume safe - global NOISE # pylint: disable=global-variable-undefined - NOISE = GetGreyNoiseObject(event) - riot = GetGreyNoiseRiotObject(event) - if riot.is_riot("ClientIP"): - return False - - # Check if IP is classified as 'malicious' - if NOISE.classification("ClientIP") == "malicious": - return True - return False - - -def title(event): - return ( - f"Cloudflare: High Volume of Block Actions - " - f"from [{event.get('ClientIP', '')}] " - f"to [{event.get('ClientRequestHost', '')}] " - f" - GreyNoise identified IP as malicious" - ) - - -def dedup(event): - return ( - f"{event.get('ClientIP', '')}:" - f"{event.get('ClientRequestHost', '')}" - ) - - -def alert_context(event): - ctx = cloudflare_fw_alert_context(event) - ctx["GreyNoise"] = NOISE.context("ClientIP") - return ctx diff --git a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.yml b/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.yml deleted file mode 100644 index 2a965fbf9..000000000 --- a/rules/cloudflare_rules/cloudflare_firewall_high_volume_events_blocked_greynoise.yml +++ /dev/null @@ -1,250 +0,0 @@ -AnalysisType: rule -Filename: cloudflare_firewall_high_volume_events_blocked_greynoise.py -RuleID: "Cloudflare.Firewall.HighVolumeEventsBlockedGreyNoise" -DisplayName: "--DEPRECATED-- Cloudflare High Volume Events Blocked - GreyNoise" -Enabled: false -LogTypes: - - Cloudflare.Firewall -Tags: - - Cloudflare - - GreyNoise - - Deprecated -Severity: Info -CreateAlert: false -Description: Monitors high volume events blocked from the same IP enriched with GreyNoise -Runbook: Inspect and monitor internet-facing services for potential outages -Reference: https://docs.greynoise.io/docs/understanding-greynoise-enrichments -DedupPeriodMinutes: 60 # 1 hour -Threshold: 200 -SummaryAttributes: - - ClientRequestUserAgent - - ClientRequestPath - - Action - - EdgeResponseStatus - - OriginResponseStatus -Tests: - - Name: Blocked Event - Malicious - ExpectedResult: true - Log: - { - "Action": "block", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - "p_enrichment": - { - "greynoise_noise_basic": - { - "ClientIP": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": ["Mirai", "ZMap Client"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - } - - Name: Skip Event - Non-Malicious - ExpectedResult: false - Log: - { - "Action": "skip", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "127.0.0.1", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - "p_enrichment": - { - "greynoise_noise_basic": - { - "ClientIP": - { - "actor": "unknown", - "bot": false, - "classification": "benign", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": [], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - } - - Name: Skip Event - Malicious - ExpectedResult: false - Log: - { - "Action": "skip", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - "p_enrichment": - { - "greynoise_noise_basic": - { - "ClientIP": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": ["Mirai", "ZMap Client"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - } diff --git a/rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.py b/rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.py deleted file mode 100644 index e29f37982..000000000 --- a/rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.py +++ /dev/null @@ -1,50 +0,0 @@ -from ipaddress import ip_address - -from global_filter_cloudflare import filter_include_event -from panther_cloudflare_helpers import cloudflare_fw_alert_context -from panther_greynoise_helpers import GetGreyNoiseObject, GetGreyNoiseRiotObject - - -def rule(event): - if not filter_include_event(event): - return False - if event.get("Action") == "block": - return False - # Validate the IP is actually an IP - try: - ip_address(event.get("ClientIP")) - except ValueError: - return False - - # Setup GreyNoise variables - global NOISE # pylint: disable=global-variable-undefined - NOISE = GetGreyNoiseObject(event) - riot = GetGreyNoiseRiotObject(event) - - # If IP is in the RIOT dataset, we can assume it is safe - if riot.is_riot("ClientIP"): - return False - - # Check if IP classified as malicious - return NOISE.classification("ClientIP") == "malicious" - - -def title(event): - return ( - f"Cloudflare: Non-blocked requests - Greynoise malicious IP -" - f"from [{event.get('ClientIP', '')}] " - f"to [{event.get('ClientRequestHost', '')}]" - ) - - -def dedup(event): - return ( - f"{event.get('ClientIP', '')}:" - f"{event.get('ClientRequestHost', '')}" - ) - - -def alert_context(event): - ctx = cloudflare_fw_alert_context(event) - ctx["GreyNoise"] = NOISE.context("ClientIP") - return ctx diff --git a/rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.yml b/rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.yml deleted file mode 100644 index 6acb6c998..000000000 --- a/rules/cloudflare_rules/cloudflare_firewall_suspicious_event_greynoise.yml +++ /dev/null @@ -1,251 +0,0 @@ -AnalysisType: rule -Filename: cloudflare_firewall_suspicious_event_greynoise.py -RuleID: "Cloudflare.Firewall.SuspiciousEventGreyNoise" -DisplayName: "--DEPRECATED-- Cloudflare Suspicious Event - GreyNoise" -Enabled: false -LogTypes: - - Cloudflare.Firewall -Tags: - - Cloudflare - - GreyNoise - - Deprecated -Severity: Medium -Description: Monitors for non-blocked requests from Greynoise identified malicious IP Addresses -Runbook: Inspect resources accessed for malicious behavior -Reference: https://docs.greynoise.io/docs/understanding-greynoise-enrichments -DedupPeriodMinutes: 60 # 1 hour -Threshold: 1 -SummaryAttributes: - - ClientRequestUserAgent - - ClientRequestPath - - ClientRequestQuery - - Action - - EdgeResponseStatus - - OriginResponseStatus - - Source -Tests: - - Name: Blocked Event - Malicious - ExpectedResult: false - Log: - { - "Action": "block", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - "p_enrichment": - { - "greynoise_noise_basic": - { - "ClientIP": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": ["Mirai", "ZMap Client"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - } - - Name: Skip Event - Non-Malicious - ExpectedResult: false - Log: - { - "Action": "skip", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "127.0.0.1", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - "p_enrichment": - { - "greynoise_noise_basic": - { - "ClientIP": - { - "actor": "unknown", - "bot": false, - "classification": "benign", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": [], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - } - - Name: Skip Event - Malicious - ExpectedResult: true - Log: - { - "Action": "skip", - "ClientASN": 14061, - "ClientASNDescription": "DIGITALOCEAN-ASN", - "ClientCountry": "nl", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRefererHost": "www.example.com", - "ClientRefererPath": "/Visitor/bin/WebStrings.srf", - "ClientRefererQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRefererScheme": "https", - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "/Visitor/bin/WebStrings.srf", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestQuery": "?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fwindows/win.ini&obj_name=aaa", - "ClientRequestScheme": "https", - "ClientRequestUserAgent": "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36", - "Datetime": "2022-05-08 10:19:15", - "EdgeColoCode": "AMS", - "EdgeResponseStatus": 403, - "Kind": "firewall", - "MatchIndex": 0, - "Metadata": - { "ruleset_version": "65", "type": "customer", "version": "59" }, - "OriginResponseStatus": 0, - "OriginatorRayID": "00", - "RayID": "708174c00f61faa8", - "RuleID": "e35c9a670b864a3ba0203ffb1bc977d1", - "Source": "firewallmanaged", - "p_enrichment": - { - "greynoise_noise_basic": - { - "ClientIP": - { - "actor": "unknown", - "bot": false, - "classification": "malicious", - "cve": [], - "first_seen": "2022-03-19", - "ip": "142.93.204.250", - "last_seen": "2022-04-06", - "metadata": - { - "asn": "AS14061", - "category": "hosting", - "city": "North Bergen", - "country": "United States", - "country_code": "US", - "organization": "DigitalOcean, LLC", - "os": "Linux 2.2-3.x", - "rdns": "", - "region": "New Jersey", - "tor": false, - }, - "raw_data": - { - "hassh": [], - "ja3": [], - "scan": [{ "port": 23, "protocol": "TCP" }], - "web": {}, - }, - "seen": true, - "spoofable": false, - "tags": ["Mirai", "ZMap Client"], - "vpn": false, - "vpn_service": "N/A", - }, - }, - }, - } diff --git a/rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.py b/rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.py deleted file mode 100644 index 89399152d..000000000 --- a/rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.py +++ /dev/null @@ -1,56 +0,0 @@ -from ipaddress import ip_address - -from global_filter_cloudflare import filter_include_event -from panther_cloudflare_helpers import cloudflare_http_alert_context -from panther_greynoise_helpers import GetGreyNoiseObject, GetGreyNoiseRiotObject - - -def rule(event): - if not filter_include_event(event): - return False - # Bot scores are [0, 99] where scores >=1 && <30 indicating likely automated - # https://developers.cloudflare.com/bots/concepts/bot-score/ - if not all( - [ - event.get("BotScore", 100) <= 30, - event.get("BotScore", 100) >= 1, - ] - ): - return False - - # Validate the IP is actually an IP - try: - ip_address(event.get("ClientIP")) - except ValueError: - return False - - # Setup GreyNoise variables - global NOISE # pylint: disable=global-variable-undefined - NOISE = GetGreyNoiseObject(event) - riot = GetGreyNoiseRiotObject(event) - - # If IP is in the RIOT dataset, we can assume it is safe - if riot.is_riot("ClientIP"): - return False - return True - - -def title(event): - return ( - f"Cloudflare: High Volume of Bot Requests - GreyNoise non-RIOT - " - f"from [{event.get('ClientIP', '')}] " - f"to [{event.get('ClientRequestHost', '')}]" - ) - - -def dedup(event): - return ( - f"{event.get('ClientIP', '')}:" - f"{event.get('ClientRequestHost', '')}" - ) - - -def alert_context(event): - ctx = cloudflare_http_alert_context(event) - ctx["GreyNoise"] = NOISE.context("ClientIP") - return ctx diff --git a/rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.yml b/rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.yml deleted file mode 100644 index ef8fc9076..000000000 --- a/rules/cloudflare_rules/cloudflare_httpreq_bot_high_volume_greynoise.yml +++ /dev/null @@ -1,312 +0,0 @@ -AnalysisType: rule -Filename: cloudflare_httpreq_bot_high_volume_greynoise.py -RuleID: "Cloudflare.HttpRequest.BotHighVolumeGreyNoise" -DisplayName: "--DEPRECATED-- Cloudflare Bot High Volume GreyNoise" -Enabled: false -LogTypes: - - Cloudflare.HttpRequest -Tags: - - Cloudflare - - GreyNoise - - Deprecated -Severity: Low -Description: Monitors for high volume of likely automated HTTP Requests with GreyNoise enrichment -Runbook: Inspect and monitor internet-facing services for potential outages -Reference: https://docs.greynoise.io/docs/understanding-greynoise-enrichments -DedupPeriodMinutes: 60 # 1 hour -Threshold: 7560 # 2req/sec is 7200 + 5% for just-in-case -SummaryAttributes: - - ClientIP - - ClientRequestUserAgent - - EdgeResponseContentType - - ClientCountry - - ClientRequestURI -Tests: - - Name: Likely Human - ExpectedResult: false - Log: - { - "BotScore": 99, - "CacheCacheStatus": "miss", - "CacheResponseBytes": 76931, - "CacheResponseStatus": 404, - "CacheTieredFill": false, - "ClientASN": 63949, - "ClientCountry": "gb", - "ClientDeviceType": "desktop", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRequestBytes": 2407, - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestReferer": "https://example.com/", - "ClientRequestURI": "", - "ClientRequestUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36", - "ClientSSLProtocol": "TLSv1.3", - "ClientSrcPort": 28057, - "ClientXRequestedWith": "", - "EdgeColoCode": "LHR", - "EdgeColoID": 373, - "EdgeEndTimestamp": "2022-05-07 18:53:13", - "EdgePathingOp": "wl", - "EdgePathingSrc": "macro", - "EdgePathingStatus": "nr", - "EdgeRateLimitAction": "", - "EdgeRateLimitID": "0", - "EdgeRequestHost": "example.com", - "EdgeResponseBytes": 17826, - "EdgeResponseCompressionRatio": 4.55, - "EdgeResponseContentType": "text/html", - "EdgeResponseStatus": 404, - "EdgeServerIP": "", - "EdgeStartTimestamp": "2022-05-07 18:53:12", - "OriginIP": "", - "OriginResponseBytes": 0, - "OriginResponseStatus": 0, - "OriginResponseTime": 0, - "OriginSSLProtocol": "unknown", - "ParentRayID": "00", - "RayID": "707c283ab88274cd", - "SecurityLevel": "med", - "WAFAction": "unknown", - "WAFFlags": "0", - "WAFMatchedVar": "", - "WAFProfile": "unknown", - "WAFRuleID": "", - "WAFRuleMessage": "", - "WorkerCPUTime": 0, - "WorkerStatus": "unknown", - "WorkerSubrequest": false, - "WorkerSubrequestCount": 0, - "ZoneID": 526503649, - "p_any_domain_names": ["https://example.com/", "example.com"], - "p_any_ip_addresses": ["142.93.204.250"], - "p_any_trace_ids": ["00", "707c283ab88274cd"], - "p_event_time": "2022-05-07 18:53:12", - "p_log_type": "Cloudflare.HttpRequest", - "p_parse_time": "2022-05-07 18:54:31.922", - "p_row_id": "a6e3965df054cfcdbdccf3ec10a134", - "p_source_id": "2b9fc5ae-9cab-4715-8683-9bfbdb15a313", - "p_source_label": "Cloudflare", - } - - Name: Likely Automated - ExpectedResult: true - Log: - { - "BotScore": 29, - "CacheCacheStatus": "miss", - "CacheResponseBytes": 76931, - "CacheResponseStatus": 404, - "CacheTieredFill": false, - "ClientASN": 63949, - "ClientCountry": "gb", - "ClientDeviceType": "desktop", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRequestBytes": 2407, - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestReferer": "https://example.com/", - "ClientRequestURI": "", - "ClientRequestUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36", - "ClientSSLProtocol": "TLSv1.3", - "ClientSrcPort": 28057, - "ClientXRequestedWith": "", - "EdgeColoCode": "LHR", - "EdgeColoID": 373, - "EdgeEndTimestamp": "2022-05-07 18:53:13", - "EdgePathingOp": "wl", - "EdgePathingSrc": "macro", - "EdgePathingStatus": "nr", - "EdgeRateLimitAction": "", - "EdgeRateLimitID": "0", - "EdgeRequestHost": "example.com", - "EdgeResponseBytes": 17826, - "EdgeResponseCompressionRatio": 4.55, - "EdgeResponseContentType": "text/html", - "EdgeResponseStatus": 404, - "EdgeServerIP": "", - "EdgeStartTimestamp": "2022-05-07 18:53:12", - "OriginIP": "", - "OriginResponseBytes": 0, - "OriginResponseStatus": 0, - "OriginResponseTime": 0, - "OriginSSLProtocol": "unknown", - "ParentRayID": "00", - "RayID": "707c283ab88274cd", - "SecurityLevel": "med", - "WAFAction": "unknown", - "WAFFlags": "0", - "WAFMatchedVar": "", - "WAFProfile": "unknown", - "WAFRuleID": "", - "WAFRuleMessage": "", - "WorkerCPUTime": 0, - "WorkerStatus": "unknown", - "WorkerSubrequest": false, - "WorkerSubrequestCount": 0, - "ZoneID": 526503649, - "p_any_domain_names": ["https://example.com/", "example.com"], - "p_any_ip_addresses": ["142.93.204.250"], - "p_any_trace_ids": ["00", "707c283ab88274cd"], - "p_event_time": "2022-05-07 18:53:12", - "p_log_type": "Cloudflare.HttpRequest", - "p_parse_time": "2022-05-07 18:54:31.922", - "p_row_id": "a6e3965df054cfcdbdccf3ec10a134", - "p_source_id": "2b9fc5ae-9cab-4715-8683-9bfbdb15a313", - "p_source_label": "Cloudflare", - } - - Name: Likely Automated - B2B - ExpectedResult: false - Log: - { - "BotScore": 29, - "CacheCacheStatus": "miss", - "CacheResponseBytes": 76931, - "CacheResponseStatus": 404, - "CacheTieredFill": false, - "ClientASN": 63949, - "ClientCountry": "gb", - "ClientDeviceType": "desktop", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRequestBytes": 2407, - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestReferer": "https://example.com/", - "ClientRequestURI": "", - "ClientRequestUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36", - "ClientSSLProtocol": "TLSv1.3", - "ClientSrcPort": 28057, - "ClientXRequestedWith": "", - "EdgeColoCode": "LHR", - "EdgeColoID": 373, - "EdgeEndTimestamp": "2022-05-07 18:53:13", - "EdgePathingOp": "wl", - "EdgePathingSrc": "macro", - "EdgePathingStatus": "nr", - "EdgeRateLimitAction": "", - "EdgeRateLimitID": "0", - "EdgeRequestHost": "example.com", - "EdgeResponseBytes": 17826, - "EdgeResponseCompressionRatio": 4.55, - "EdgeResponseContentType": "text/html", - "EdgeResponseStatus": 404, - "EdgeServerIP": "", - "EdgeStartTimestamp": "2022-05-07 18:53:12", - "OriginIP": "", - "OriginResponseBytes": 0, - "OriginResponseStatus": 0, - "OriginResponseTime": 0, - "OriginSSLProtocol": "unknown", - "ParentRayID": "00", - "RayID": "707c283ab88274cd", - "SecurityLevel": "med", - "WAFAction": "unknown", - "WAFFlags": "0", - "WAFMatchedVar": "", - "WAFProfile": "unknown", - "WAFRuleID": "", - "WAFRuleMessage": "", - "WorkerCPUTime": 0, - "WorkerStatus": "unknown", - "WorkerSubrequest": false, - "WorkerSubrequestCount": 0, - "ZoneID": 526503649, - "p_any_domain_names": ["https://example.com/", "example.com"], - "p_any_ip_addresses": ["142.93.204.250"], - "p_any_trace_ids": ["00", "707c283ab88274cd"], - "p_event_time": "2022-05-07 18:53:12", - "p_log_type": "Cloudflare.HttpRequest", - "p_parse_time": "2022-05-07 18:54:31.922", - "p_row_id": "a6e3965df054cfcdbdccf3ec10a134", - "p_source_id": "2b9fc5ae-9cab-4715-8683-9bfbdb15a313", - "p_source_label": "Cloudflare", - "p_enrichment": - { - "greynoise_riot_basic": - { - "ClientIP": - { - "ip_address": "142.93.204.250", - "is_riot": true, - "ip_cidr": "142.93.204.250/32", - }, - }, - }, - } - - Name: Bot Score Not Computed - ExpectedResult: False - Log: - { - "BotScore": 0, - "CacheCacheStatus": "miss", - "CacheResponseBytes": 76931, - "CacheResponseStatus": 404, - "CacheTieredFill": false, - "ClientASN": 63949, - "ClientCountry": "gb", - "ClientDeviceType": "desktop", - "ClientIP": "142.93.204.250", - "ClientIPClass": "noRecord", - "ClientRequestBytes": 2407, - "ClientRequestHost": "example.com", - "ClientRequestMethod": "GET", - "ClientRequestPath": "", - "ClientRequestProtocol": "HTTP/1.1", - "ClientRequestReferer": "https://example.com/", - "ClientRequestURI": "", - "ClientRequestUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36", - "ClientSSLProtocol": "TLSv1.3", - "ClientSrcPort": 28057, - "ClientXRequestedWith": "", - "EdgeColoCode": "LHR", - "EdgeColoID": 373, - "EdgeEndTimestamp": "2022-05-07 18:53:13", - "EdgePathingOp": "wl", - "EdgePathingSrc": "macro", - "EdgePathingStatus": "nr", - "EdgeRateLimitAction": "", - "EdgeRateLimitID": "0", - "EdgeRequestHost": "example.com", - "EdgeResponseBytes": 17826, - "EdgeResponseCompressionRatio": 4.55, - "EdgeResponseContentType": "text/html", - "EdgeResponseStatus": 404, - "EdgeServerIP": "", - "EdgeStartTimestamp": "2022-05-07 18:53:12", - "OriginIP": "", - "OriginResponseBytes": 0, - "OriginResponseStatus": 0, - "OriginResponseTime": 0, - "OriginSSLProtocol": "unknown", - "ParentRayID": "00", - "RayID": "707c283ab88274cd", - "SecurityLevel": "med", - "WAFAction": "unknown", - "WAFFlags": "0", - "WAFMatchedVar": "", - "WAFProfile": "unknown", - "WAFRuleID": "", - "WAFRuleMessage": "", - "WorkerCPUTime": 0, - "WorkerStatus": "unknown", - "WorkerSubrequest": false, - "WorkerSubrequestCount": 0, - "ZoneID": 526503649, - "p_any_domain_names": ["https://example.com/", "example.com"], - "p_any_ip_addresses": ["142.93.204.250"], - "p_any_trace_ids": ["00", "707c283ab88274cd"], - "p_event_time": "2022-05-07 18:53:12", - "p_log_type": "Cloudflare.HttpRequest", - "p_parse_time": "2022-05-07 18:54:31.922", - "p_row_id": "a6e3965df054cfcdbdccf3ec10a134", - "p_source_id": "2b9fc5ae-9cab-4715-8683-9bfbdb15a313", - "p_source_label": "Cloudflare", - } diff --git a/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.py b/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.py deleted file mode 100644 index 847bbd9ea..000000000 --- a/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.py +++ /dev/null @@ -1,31 +0,0 @@ -from fnmatch import fnmatch - -from panther_base_helpers import deep_get, get_binding_deltas - -ADMIN_ROLES = { - # Primitive Roles - "roles/owner", - # Predefined Roles - "roles/*Admin", -} - - -def rule(event): - for delta in get_binding_deltas(event): - if delta.get("action") != "ADD": - continue - if any( - ( - fnmatch(delta.get("role", ""), admin_role_pattern) - for admin_role_pattern in ADMIN_ROLES - ) - ): - return True - return False - - -def title(event): - return ( - f"An admin role has been configured in GCP project " - f"{deep_get(event, 'resource', 'labels', 'project_id', default='')}" - ) diff --git a/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml b/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml deleted file mode 100644 index 4011a50d8..000000000 --- a/rules/gcp_audit_rules/gcp_iam_admin_role_assigned.yml +++ /dev/null @@ -1,494 +0,0 @@ -AnalysisType: rule -Filename: gcp_iam_admin_role_assigned.py -RuleID: "GCP.IAM.AdminRoleAssigned" -DisplayName: "--DEPRECATED-- GCP IAM Admin Role Assigned" -Enabled: false -LogTypes: - - GCP.AuditLog -Tags: - - GCP - - Identity & Access Management - - Privilege Escalation:Valid Accounts - - Configuration Required - - Deprecated -Reports: - MITRE ATT&CK: - - TA0004:T1078 -Severity: Medium -Description: Attaching an admin role manually could be a sign of privilege escalation -Runbook: Verify with the user who attached the role or add to a allowlist -Reference: https://cloud.google.com/looker/docs/admin-panel-users-roles -SummaryAttributes: - - severity - - p_any_ip_addresses - - p_any_domain_names -Tests: - - Name: Service Admin Role Assigned - ExpectedResult: true - Log: - { - "logName": "projects/eastern-nurve-222999/logs/cloudaudit.googleapis.com%2Factivity", - "severity": "NOTICE", - "insertId": "-4fgf8odw6xy", - "resource": - { - "type": "project", - "labels": { "project_id": "eastern-nurve-222999" }, - }, - "timestamp": "2020-05-04 20:53:02.915000000", - "receiveTimestamp": "2020-05-04 20:53:04.281679681", - "protoPayload": - { - "@type": "type.googleapis.com/google.cloud.audit.AuditLog", - "serviceName": "cloudresourcemanager.googleapis.com", - "methodName": "SetIamPolicy", - "resourceName": "projects/eastern-nurve-222999", - "status": {}, - "authenticationInfo": { "principalEmail": "test@runpanther.io" }, - "authorizationInfo": - [ - { - "resource": "projects/eastern-nurve-222999", - "permission": "resourcemanager.projects.setIamPolicy", - "granted": true, - }, - { - "resource": "projects/eastern-nurve-222999", - "permission": "resourcemanager.projects.setIamPolicy", - "granted": true, - }, - ], - "requestMetadata": - { - "callerIP": "136.24.229.58", - "callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36,gzip(gfe)", - "requestAttributes": {}, - "destinationAttributes": {}, - }, - "request": - { - "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest", - "policy": - { - "bindings": - [ - { - "members": - [ - "serviceAccount:service-951849100836@compute-system.iam.gserviceaccount.com", - ], - "role": "roles/compute.serviceAgent", - }, - { - "members": - [ - "serviceAccount:951849100836-compute@developer.gserviceaccount.com", - "serviceAccount:951849100836@cloudservices.gserviceaccount.com", - ], - "role": "roles/editor", - }, - { - "members": ["user:test@runpanther.io"], - "role": "roles/owner", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.subscriber", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.viewer", - }, - { - "members": ["user:test@gmail.com"], - "role": "roles/browser", - }, - ], - "etag": "BwWk11rbCfY=", - }, - "resource": "eastern-nurve-222999", - }, - "response": - { - "@type": "type.googleapis.com/google.iam.v1.Policy", - "bindings": - [ - { - "members": ["user:test@gmail.com"], - "role": "roles/browser", - }, - { - "members": - [ - "serviceAccount:service-951849100836@compute-system.iam.gserviceaccount.com", - ], - "role": "roles/compute.serviceAgent", - }, - { - "members": - [ - "serviceAccount:951849100836-compute@developer.gserviceaccount.com", - "serviceAccount:951849100836@cloudservices.gserviceaccount.com", - ], - "role": "roles/editor", - }, - { - "members": ["user:test@runpanther.io"], - "role": "roles/owner", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.subscriber", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.viewer", - }, - ], - "etag": "BwWk2LeSpmA=", - }, - "serviceData": - { - "@type": "type.googleapis.com/google.iam.v1.logging.AuditData", - "policyDelta": - { - "bindingDeltas": - [ - { - "action": "ADD", - "member": "user:test@runpanther.io", - "role": "roles/actions.Admin", - }, - { - "action": "ADD", - "member": "user:test@runpanther.io", - "role": "roles/appengine.appAdmin", - }, - { - "action": "REMOVE", - "member": "user:test@runpanther.io", - "role": "roles/browser", - }, - ], - }, - }, - }, - } - - Name: Admin Role Assigned - ExpectedResult: true - Log: - { - "logName": "projects/eastern-nurve-222999/logs/cloudaudit.googleapis.com%2Factivity", - "severity": "NOTICE", - "insertId": "-4fgf8odw6xy", - "resource": - { - "type": "project", - "labels": { "project_id": "eastern-nurve-222999" }, - }, - "timestamp": "2020-05-04 20:53:02.915000000", - "receiveTimestamp": "2020-05-04 20:53:04.281679681", - "protoPayload": - { - "@type": "type.googleapis.com/google.cloud.audit.AuditLog", - "serviceName": "cloudresourcemanager.googleapis.com", - "methodName": "SetIamPolicy", - "resourceName": "projects/eastern-nurve-222999", - "status": {}, - "authenticationInfo": { "principalEmail": "test@runpanther.io" }, - "authorizationInfo": - [ - { - "resource": "projects/eastern-nurve-222999", - "permission": "resourcemanager.projects.setIamPolicy", - "granted": true, - }, - { - "resource": "projects/eastern-nurve-222999", - "permission": "resourcemanager.projects.setIamPolicy", - "granted": true, - }, - ], - "requestMetadata": - { - "callerIP": "136.24.229.58", - "callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36,gzip(gfe)", - "requestAttributes": {}, - "destinationAttributes": {}, - }, - "request": - { - "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest", - "policy": - { - "bindings": - [ - { - "members": - [ - "serviceAccount:service-951849100836@compute-system.iam.gserviceaccount.com", - ], - "role": "roles/compute.serviceAgent", - }, - { - "members": - [ - "serviceAccount:951849100836-compute@developer.gserviceaccount.com", - "serviceAccount:951849100836@cloudservices.gserviceaccount.com", - ], - "role": "roles/editor", - }, - { - "members": ["user:test@runpanther.io"], - "role": "roles/owner", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.subscriber", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.viewer", - }, - { - "members": ["user:test@gmail.com"], - "role": "roles/browser", - }, - ], - "etag": "BwWk11rbCfY=", - }, - "resource": "eastern-nurve-222999", - }, - "response": - { - "@type": "type.googleapis.com/google.iam.v1.Policy", - "bindings": - [ - { - "members": ["user:test@gmail.com"], - "role": "roles/browser", - }, - { - "members": - [ - "serviceAccount:service-951849100836@compute-system.iam.gserviceaccount.com", - ], - "role": "roles/compute.serviceAgent", - }, - { - "members": - [ - "serviceAccount:951849100836-compute@developer.gserviceaccount.com", - "serviceAccount:951849100836@cloudservices.gserviceaccount.com", - ], - "role": "roles/editor", - }, - { - "members": ["user:test@runpanther.io"], - "role": "roles/owner", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.subscriber", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.viewer", - }, - ], - "etag": "BwWk2LeSpmA=", - }, - "serviceData": - { - "@type": "type.googleapis.com/google.iam.v1.logging.AuditData", - "policyDelta": - { - "bindingDeltas": - [ - { - "action": "ADD", - "member": "user:test@gmail.com", - "role": "roles/owner", - }, - ], - }, - }, - }, - } - - Name: Browser Role Assigned - ExpectedResult: false - Log: - { - "logName": "projects/eastern-nurve-222999/logs/cloudaudit.googleapis.com%2Factivity", - "severity": "NOTICE", - "insertId": "-4fgf8odw6xy", - "resource": - { - "type": "project", - "labels": { "project_id": "eastern-nurve-222999" }, - }, - "timestamp": "2020-05-04 20:53:02.915000000", - "receiveTimestamp": "2020-05-04 20:53:04.281679681", - "protoPayload": - { - "@type": "type.googleapis.com/google.cloud.audit.AuditLog", - "serviceName": "cloudresourcemanager.googleapis.com", - "methodName": "SetIamPolicy", - "resourceName": "projects/eastern-nurve-222999", - "status": {}, - "authenticationInfo": { "principalEmail": "test@runpanther.io" }, - "authorizationInfo": - [ - { - "resource": "projects/eastern-nurve-222999", - "permission": "resourcemanager.projects.setIamPolicy", - "granted": true, - }, - { - "resource": "projects/eastern-nurve-222999", - "permission": "resourcemanager.projects.setIamPolicy", - "granted": true, - }, - ], - "requestMetadata": - { - "callerIP": "136.24.229.58", - "callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36,gzip(gfe)", - "requestAttributes": {}, - "destinationAttributes": {}, - }, - "request": - { - "@type": "type.googleapis.com/google.iam.v1.SetIamPolicyRequest", - "policy": - { - "bindings": - [ - { - "members": - [ - "serviceAccount:service-951849100836@compute-system.iam.gserviceaccount.com", - ], - "role": "roles/compute.serviceAgent", - }, - { - "members": - [ - "serviceAccount:951849100836-compute@developer.gserviceaccount.com", - "serviceAccount:951849100836@cloudservices.gserviceaccount.com", - ], - "role": "roles/editor", - }, - { - "members": ["user:test@runpanther.io"], - "role": "roles/owner", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.subscriber", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.viewer", - }, - { - "members": ["user:test@gmail.com"], - "role": "roles/browser", - }, - ], - "etag": "BwWk11rbCfY=", - }, - "resource": "eastern-nurve-222999", - }, - "response": - { - "@type": "type.googleapis.com/google.iam.v1.Policy", - "bindings": - [ - { - "members": ["user:test@gmail.com"], - "role": "roles/browser", - }, - { - "members": - [ - "serviceAccount:service-951849100836@compute-system.iam.gserviceaccount.com", - ], - "role": "roles/compute.serviceAgent", - }, - { - "members": - [ - "serviceAccount:951849100836-compute@developer.gserviceaccount.com", - "serviceAccount:951849100836@cloudservices.gserviceaccount.com", - ], - "role": "roles/editor", - }, - { - "members": ["user:test@runpanther.io"], - "role": "roles/owner", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.subscriber", - }, - { - "members": - [ - "serviceAccount:pubsub-reader@eastern-nurve-222999.iam.gserviceaccount.com", - ], - "role": "roles/pubsub.viewer", - }, - ], - "etag": "BwWk2LeSpmA=", - }, - "serviceData": - { - "@type": "type.googleapis.com/google.iam.v1.logging.AuditData", - "policyDelta": - { - "bindingDeltas": - [ - { - "action": "ADD", - "member": "user:test@gmail.com", - "role": "roles/browser", - }, - ], - }, - }, - }, - } diff --git a/rules/gsuite_activityevent_rules/gsuite_brute_force_login.py b/rules/gsuite_activityevent_rules/gsuite_brute_force_login.py deleted file mode 100644 index b25e2fa5a..000000000 --- a/rules/gsuite_activityevent_rules/gsuite_brute_force_login.py +++ /dev/null @@ -1,17 +0,0 @@ -from panther_base_helpers import deep_get - - -def rule(event): - # Filter login events - if event.get("type") != "login": - return False - - # Pattern match this event to the recon actions - return bool(event.get("name") == "login_failure") - - -def title(event): - return ( - f"Brute force login suspected for user " - f"[{deep_get(event, 'actor', 'email', default='')}]" - ) diff --git a/rules/gsuite_activityevent_rules/gsuite_brute_force_login.yml b/rules/gsuite_activityevent_rules/gsuite_brute_force_login.yml deleted file mode 100644 index 12b9c2195..000000000 --- a/rules/gsuite_activityevent_rules/gsuite_brute_force_login.yml +++ /dev/null @@ -1,43 +0,0 @@ -AnalysisType: rule -Filename: gsuite_brute_force_login.py -RuleID: "GSuite.BruteForceLogin" -DisplayName: "--DEPRECATED-- GSuite Brute Force Login" -Enabled: false -LogTypes: - - GSuite.ActivityEvent -Tags: - - GSuite -Severity: Medium -Threshold: 10 -DedupPeriodMinutes: 10 -Description: A GSuite user was denied login access several times -Reference: https://support.google.com/a/answer/7281227?hl=en&sjid=864417124752637253-EU -Runbook: Analyze the IP they came from and actions taken before/after. -Tests: - - Name: Failed Login - ExpectedResult: true - Log: - { - "id": { "applicationName": "login" }, - "actor": { "email": "some.user@somedomain.com" }, - "type": "login", - "name": "login_failure", - } - - Name: Successful Login - ExpectedResult: false - Log: - { - "id": { "applicationName": "login" }, - "actor": { "email": "some.user@somedomain.com" }, - "type": "login", - "name": "login_success", - } - - Name: Other Login Event - ExpectedResult: false - Log: - { - "id": { "applicationName": "login" }, - "actor": { "email": "some.user@somedomain.com" }, - "type": "login", - "name": "login_verification", - } diff --git a/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.py b/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.py deleted file mode 100644 index fb5938292..000000000 --- a/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.py +++ /dev/null @@ -1,26 +0,0 @@ -from panther_base_helpers import deep_get - -PERMISSION_DELEGATED_EVENTS = { - "ASSIGN_ROLE", -} - - -def rule(event): - if deep_get(event, "id", "applicationName") != "admin": - return False - if event.get("type") == "DELEGATED_ADMIN_SETTINGS": - return bool(event.get("name") in PERMISSION_DELEGATED_EVENTS) - return False - - -def title(event): - role = deep_get(event, "parameters", "ROLE_NAME") - user = deep_get(event, "parameters", "USER_EMAIL") - if not role: - role = "" - if not user: - user = "" - return ( - f"User [{deep_get(event, 'actor', 'email', default='')}] delegated new" - f" administrator privileges [{role}] to [{user}]" - ) diff --git a/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml b/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml deleted file mode 100644 index a7a419c2b..000000000 --- a/rules/gsuite_activityevent_rules/gsuite_permissions_delegated.yml +++ /dev/null @@ -1,46 +0,0 @@ -AnalysisType: rule -Filename: gsuite_permissions_delegated.py -RuleID: "GSuite.PermisssionsDelegated" -DisplayName: "--DEPRECATED-- GSuite User Delegated Admin Permissions" -Enabled: false -LogTypes: - - GSuite.ActivityEvent -Tags: - - GSuite - - Configuration Required - - Deprecated -Severity: Low -Description: > - A GSuite user was granted new administrator privileges. -Reference: https://support.google.com/a/answer/167094?hl=en&sjid=864417124752637253-EU -Runbook: > - Valdiate that this users should have these permissions and they are not the result of a privilege escalation attack. -SummaryAttributes: - - actor:email -Tests: - - Name: Other Admin Action - ExpectedResult: false - Log: - { - "id": { "applicationName": "admin" }, - "type": "DELEGATED_ADMIN_SETTINGS", - "name": "RENAME_ROLE", - "parameters": - { - "ROLE_NAME": "Vault Admins", - "USER_EMAIL": "homer.simpson@example.com", - }, - } - - Name: Privileges Assigned - ExpectedResult: true - Log: - { - "id": { "applicationName": "admin" }, - "type": "DELEGATED_ADMIN_SETTINGS", - "name": "ASSIGN_ROLE", - "parameters": - { - "ROLE_NAME": "Vault Admins", - "USER_EMAIL": "homer.simpson@example.com", - }, - } diff --git a/rules/notion_rules/notion_account_changed_after_login.py b/rules/notion_rules/notion_account_changed_after_login.py deleted file mode 100644 index a09945643..000000000 --- a/rules/notion_rules/notion_account_changed_after_login.py +++ /dev/null @@ -1,80 +0,0 @@ -import time - -from global_filter_notion import filter_include_event -from panther_detection_helpers.caching import get_string_set, put_string_set -from panther_notion_helpers import notion_alert_context - -# Length of time in minutes. If a user logs in, then changes their email within this many -# minutes, raise an alert. -DEFAULT_EMAIL_CHANGE_WINDOW_MINUTES = 10 - -# Prefix for cached key. This ensures we don't accidently tamper with cached data from other -# detections. -CACHE_PREFIX = "Notion.AccountChangedAfterLogin" - - -LOGIN_TS = None # Default Value - - -def rule(event): - if not filter_include_event(event): - return False - - # If this is neither a login, nor an email/password change event, then exit - allowed_event_types = { - "user.login", - "user.settings.login_method.email_updated", - "user.settings.login_method.password_updated", - "user.settings.login_method.password_added", - "user.settings.login_method.password_removed", - } - if event.deep_walk("event", "type") not in allowed_event_types: - return False - - # Global Variable Stuff - # pylint: disable=global-statement - global LOGIN_TS - - # Extract user info - userid = event.deep_walk("event", "actor", "id") - cache_key = f"{CACHE_PREFIX}-{userid}" - - # If this is a login event, record it - if event.deep_walk("event", "type") == "user.login": - # Returning this as a bool allows us to write a unit test to determine if we cache login - # events when we're supposed to. - return bool( - put_string_set( - cache_key, - [str(event.get("p_event_time"))], # We'll save this for the alert context later - time.time() + DEFAULT_EMAIL_CHANGE_WINDOW_MINUTES * 60, - ) - ) - - # If we made it here, then this is an account change event. - # We first check if the user recently logged in: - if last_login := get_string_set(cache_key, force_ttl_check=True): - LOGIN_TS = list(last_login)[0] # Save the last login timestamp for the alert context - return True - # If they haven't logged in recently, then return false - return False - - -def title(event): - user_email = event.deep_walk("event", "actor", "person", "email", default="UNKNOWN EMAIL") - mins = DEFAULT_EMAIL_CHANGE_WINDOW_MINUTES - action_taken = { - "user.settings.login_method.email_updated": "changed their email", - "user.settings.login_method.password_updated": "changed their password", - "user.settings.login_method.password_added": "added a password to their account", - "user.settings.login_method.password_removed": "removed the password from their account", - }.get(event.deep_get("event", "type"), "altered their account info") - return f"Notion User [{user_email}] {action_taken} within [{mins}] minutes of logging in." - - -def alert_context(event): - context = notion_alert_context(event) - global LOGIN_TS - if LOGIN_TS: - context["login_timestamp"] = LOGIN_TS - return context diff --git a/rules/notion_rules/notion_account_changed_after_login.yml b/rules/notion_rules/notion_account_changed_after_login.yml deleted file mode 100644 index f96a3ae38..000000000 --- a/rules/notion_rules/notion_account_changed_after_login.yml +++ /dev/null @@ -1,366 +0,0 @@ -AnalysisType: rule -Filename: notion_account_changed_after_login.py -RuleID: "Notion.AccountChangedAfterLogin" -DisplayName: "DEPRECATED - Notion Account Changed Shortly After Login" -Enabled: true -LogTypes: - - Notion.AuditLogs -Tags: - - Notion - - Identity & Access Management - - Persistence - - DEPRECATED -Severity: Medium -Description: A Notion User logged in then changed their account details. -DedupPeriodMinutes: 60 -Threshold: 1 -Runbook: Possible account takeover. Follow up with the Notion User to determine if this email change is genuine. -Reference: https://www.notion.so/help/account-settings -Tests: - - # This unit test is to make sure the logic for handling login events successfully results in - # caching the login info. The outputted title/alert_context are not important. - Name: Login event - ExpectedResult: true - Mocks: - - objectName: put_string_set - returnValue: true - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "user.login", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } - - Name: Email Changed Shortly After Login - ExpectedResult: true - Mocks: - - objectName: get_string_set - returnValue: >- - [ - "2023-06-12 21:40:28.690000000" - ] - - objectName: put_string_set - returnValue: "" - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "user.settings.login_method.email_updated", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "0.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } - - Name: Password Changed Shortly After Login - ExpectedResult: true - Mocks: - - objectName: get_string_set - returnValue: >- - [ - "2023-06-12 21:40:28.690000000" - ] - - objectName: put_string_set - returnValue: "" - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "user.settings.login_method.password_updated", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "0.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } - - Name: Password Added Shortly After Login - ExpectedResult: true - Mocks: - - objectName: get_string_set - returnValue: >- - [ - "2023-06-12 21:40:28.690000000" - ] - - objectName: put_string_set - returnValue: "" - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "user.settings.login_method.password_added", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "0.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } - - Name: Password Removed Shortly After Login - ExpectedResult: true - Mocks: - - objectName: get_string_set - returnValue: >- - [ - "2023-06-12 21:40:28.690000000" - ] - - objectName: put_string_set - returnValue: "" - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "user.settings.login_method.password_removed", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "0.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } - - Name: Email Changed Not Shortly After Login - ExpectedResult: false - Mocks: - - objectName: get_string_set - returnValue: False - - objectName: put_string_set - returnValue: "" - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "user.settings.login_method.email_updated", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "0.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } - - Name: Unrelated event - ExpectedResult: false - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": { "authType": "email" }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-06-12 21:40:28.690000000", - "type": "page.viewed", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "0.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-06-12 21:40:28.690000000", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-06-12 22:53:51.602223297", - "p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion Logs", - } diff --git a/rules/notion_rules/notion_page_view_impossible_travel.py b/rules/notion_rules/notion_page_view_impossible_travel.py deleted file mode 100644 index 08c4b22fe..000000000 --- a/rules/notion_rules/notion_page_view_impossible_travel.py +++ /dev/null @@ -1,192 +0,0 @@ -from datetime import datetime, timedelta -from json import dumps, loads - -from panther_base_helpers import deep_get -from panther_lookuptable_helpers import LookupTableMatches -from panther_oss_helpers import ( - get_string_set, - km_between_ipinfo_loc, - put_string_set, - resolve_timestamp_string, -) - -# pylint: disable=global-variable-undefined - - -def gen_key(event): - """ - gen_key uses the data_model for the logtype to cache - an entry that is specific to the Log Source ID - - The data_model needs to answer to "actor_user" - """ - rule_name = deep_get(event, "p_source_label") - actor = event.udm("actor_user") - if None in [rule_name, actor]: - return None - return f"{rule_name.replace(' ', '')}..{actor}" - - -def rule(event): - # too-many-return-statements due to error checking - # pylint: disable=global-statement,too-many-return-statements,too-complex - global EVENT_CITY_TRACKING - global CACHE_KEY - global IS_VPN - global IS_APPLE_PRIVATE_RELAY - - EVENT_CITY_TRACKING = {} - CACHE_KEY = None - IS_VPN = False - IS_APPLE_PRIVATE_RELAY = False - # Only evaluate page views - if event.deep_get("event", "type") != "page.viewed": - return False - - p_event_datetime = resolve_timestamp_string(deep_get(event, "p_event_time")) - if p_event_datetime is None: - # we couldn't go from p_event_time to a datetime object - # we need to do this in order to make later time comparisons generic - return False - - new_login_stats = { - "p_event_time": p_event_datetime.isoformat(), - "source_ip": event.udm("source_ip"), - } - # - src_ip_enrichments = LookupTableMatches().p_matches(event, event.udm("source_ip")) - - # stuff everything from ipinfo_location into the new_login_stats - # new_login_stats is the value that we will cache for this key - ipinfo_location = deep_get(src_ip_enrichments, "ipinfo_location") - if ipinfo_location is None: - return False - new_login_stats.update(ipinfo_location) - - # Bail out if we have a None value in set as it causes false positives - if None in new_login_stats.values(): - return False - - ## Check for VPN or Apple Private Relay - ipinfo_privacy = deep_get(src_ip_enrichments, "ipinfo_privacy") - if ipinfo_privacy is not None: - ### Do VPN/Apple private relay - IS_APPLE_PRIVATE_RELAY = all( - [ - deep_get(ipinfo_privacy, "relay", default=False), - deep_get(ipinfo_privacy, "service", default="") == "Apple Private Relay", - ] - ) - # We've found that some places, like WeWork locations, - # have the VPN attribute set to true, but do not have a - # service name entry. - # We have noticed VPN connections with commercial VPN - # offerings have the VPN attribute set to true, and - # do have a service name entry - IS_VPN = all( - [ - deep_get(ipinfo_privacy, "vpn", default=False), - deep_get(ipinfo_privacy, "service", default="") != "", - ] - ) - if IS_VPN or IS_APPLE_PRIVATE_RELAY: - new_login_stats.update( - { - "is_vpn": f"{IS_VPN}", - "is_apple_priv_relay": f"{IS_APPLE_PRIVATE_RELAY}", - "service_name": f"{deep_get(ipinfo_privacy, 'service', default='')}", - "NOTE": "APPLE PRIVATE RELAY AND VPN LOGINS ARE NOT CACHED FOR COMPARISON", - } - ) - - # Generate a unique cache key for each user per log type - CACHE_KEY = gen_key(event) - if CACHE_KEY is None: - # We can't save without a cache key - return False - # Retrieve the prior login info from the cache, if any - last_login = get_string_set(CACHE_KEY) - # If we haven't seen this user login in the past 1 day, - # store this login for future use and don't alert - if not last_login and not IS_APPLE_PRIVATE_RELAY and not IS_VPN: - put_string_set( - key=CACHE_KEY, - val=[dumps(new_login_stats)], - epoch_seconds=int((datetime.utcnow() + timedelta(days=1)).timestamp()), - ) - return False - # Load the last login from the cache into an object we can compare - # str check is in place for unit test mocking - if isinstance(last_login, str): - tmp_last_login = loads(last_login) - last_login = [] - for l_l in tmp_last_login: - last_login.append(dumps(l_l)) - last_login_stats = loads(last_login.pop()) - - distance = km_between_ipinfo_loc(last_login_stats, new_login_stats) - old_time = resolve_timestamp_string(deep_get(last_login_stats, "p_event_time")) - new_time = resolve_timestamp_string(deep_get(new_login_stats, "p_event_time")) - time_delta = (new_time - old_time).total_seconds() / 3600 # seconds in an hour - - # Don't let time_delta be 0 (divide by zero error below) - time_delta = time_delta or 0.0001 - # Calculate speed in Kilometers / Hour - speed = distance / time_delta - - # Calculation is complete, write the current login to the cache - put_string_set( - key=CACHE_KEY, - val=[dumps(new_login_stats)], - epoch_seconds=int((datetime.utcnow() + timedelta(days=1)).timestamp()), - ) - - EVENT_CITY_TRACKING["previous"] = last_login_stats - EVENT_CITY_TRACKING["current"] = new_login_stats - EVENT_CITY_TRACKING["speed"] = int(speed) - EVENT_CITY_TRACKING["speed_units"] = "km/h" - EVENT_CITY_TRACKING["distance"] = int(distance) - EVENT_CITY_TRACKING["distance_units"] = "km" - - return speed > 900 # Boeing 747 cruising speed - - -def title(event): - # - log_source = deep_get(event, "p_source_label", default="") - old_city = deep_get(EVENT_CITY_TRACKING, "previous", "city", default="") - new_city = deep_get(EVENT_CITY_TRACKING, "current", "city", default="") - speed = deep_get(EVENT_CITY_TRACKING, "speed", default="") - distance = deep_get(EVENT_CITY_TRACKING, "distance", default="") - return ( - f"Impossible Travel: [{event.udm('actor_user')}] " - f"in [{log_source}] went [{speed}] km/h for [{distance}] km " - f"between [{old_city}] and [{new_city}]" - ) - - -def dedup(event): # pylint: disable=W0613 - return CACHE_KEY - - -def alert_context(event): - context = { - "actor_user": event.udm("actor_user"), - } - context.update(EVENT_CITY_TRACKING) - return context - - -def severity(_): - if IS_VPN or IS_APPLE_PRIVATE_RELAY: - return "INFO" - # time = distance/speed - distance = deep_get(EVENT_CITY_TRACKING, "distance", default=None) - speed = deep_get(EVENT_CITY_TRACKING, "speed", default=None) - if speed and distance: - time = distance / speed - # time of 0.1666 is 10 minutes - if time < 0.1666 and distance < 50: - # This is likely a GEOIP inaccuracy - return "LOW" - return "HIGH" diff --git a/rules/notion_rules/notion_page_view_impossible_travel.yml b/rules/notion_rules/notion_page_view_impossible_travel.yml deleted file mode 100644 index 36bced065..000000000 --- a/rules/notion_rules/notion_page_view_impossible_travel.yml +++ /dev/null @@ -1,171 +0,0 @@ -AnalysisType: rule -Filename: notion_page_view_impossible_travel.py -RuleID: "Notion.PageViews.ImpossibleTravel" -DisplayName: "Notion Page View Impossible Travel DEPRECATED" -Enabled: false -LogTypes: - - Notion.AuditLogs -Tags: - - Notion - - Identity & Access Management - - Login & Access Patterns - - Account Compromise -Severity: High -Description: A Notion User viewed a page from 2 locations simultaneously -DedupPeriodMinutes: 60 -Threshold: 1 -Runbook: Possible account compromise. Review activity of this user. -Reference: https://raxis.com/blog/simultaneous-sessions/ -Tests: - - Name: Normal Page View - ExpectedResult: False - Mocks: - - objectName: get_string_set - returnValue: >- - [ - { - "p_event_time": "2023-09-20T16:11:44.067000", - "source_ip": "192.168.100.100", - "city": "Minas Tirith", - "country": "Gondor", - "lat": "0.00000", - "lng": "0.00000", - "p_match": "192.168.100.100", - "postal_code": "55555", - "region": "Pellenor", - "region_code": "PL", - "timezone": "Middle Earth/Pellenor" - } - ] - - objectName: put_string_set - returnValue: False - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": - { - "page_audience": "shared_internally", - "page_name": "Notes: Council of Elrond", - "target": - { - "page_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "type": "page_id", - }, - }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-09-20 16:11:44.067000000", - "type": "page.viewed", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Minas Tirith", - "country": "Gondor", - "lat": "0.00000", - "lng": "0.00000", - "p_match": "192.168.100.100", - "postal_code": "55555", - "region": "Pellenor", - "region_code": "PL", - "timezone": "Middle Earth/Pellenor", - }, - }, - }, - "p_event_time": "2023-09-20 16:11:44.067", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-09-20 16:18:27.542", - "p_row_id": "52d6bafb77d1a7fb8bbdbfd81a0e", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion-Panther-Labs", - } - - Name: Evil Page View - ExpectedResult: True - Mocks: - - objectName: get_string_set - returnValue: >- - [ - { - "p_event_time": "2023-09-20T15:11:44.067000", - "source_ip": "192.168.100.100", - "city": "Minas Tirith", - "country": "Gondor", - "lat": "0.00000", - "lng": "0.00000", - "p_match": "192.168.100.100", - "postal_code": "55555", - "region": "Pellenor", - "region_code": "PL", - "timezone": "Middle Earth/Pellenor" - } - ] - - objectName: put_string_set - returnValue: False - Log: - { - "event": - { - "actor": - { - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "object": "user", - "person": { "email": "aragorn.elessar@lotr.com" }, - "type": "person", - }, - "details": - { - "page_audience": "shared_internally", - "page_name": "Notes: Council of Elrond", - "target": - { - "page_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "type": "page_id", - }, - }, - "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "ip_address": "192.168.100.100", - "platform": "web", - "timestamp": "2023-09-20 16:11:44.067000000", - "type": "page.viewed", - "workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - }, - "p_enrichment": - { - "ipinfo_location": - { - "event.ip_address": - { - "city": "Barad-Dur", - "lat": "100.00000", - "lng": "0.00000", - "country": "Mordor", - "postal_code": "55555", - "p_match": "192.168.100.100", - "region": "Mount Doom", - "region_code": "MD", - "timezone": "Middle Earth/Mordor", - }, - }, - }, - "p_event_time": "2023-09-20 16:11:44.067", - "p_log_type": "Notion.AuditLogs", - "p_parse_time": "2023-09-20 16:18:27.542", - "p_row_id": "52d6bafb77d1a7fb8bbdbfd81a0e", - "p_schema_version": 0, - "p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "p_source_label": "Notion-Panther-Labs", - } diff --git a/rules/okta_rules/okta_brute_force_logins.py b/rules/okta_rules/okta_brute_force_logins.py deleted file mode 100644 index c4a8e9ffe..000000000 --- a/rules/okta_rules/okta_brute_force_logins.py +++ /dev/null @@ -1,20 +0,0 @@ -from panther_base_helpers import deep_get, okta_alert_context - - -def rule(event): - return ( - deep_get(event, "outcome", "result") == "FAILURE" - and event.get("eventType") == "user.session.start" - ) - - -def title(event): - return ( - f"Suspected brute force Okta logins to account " - f"{deep_get(event, 'actor', 'alternateId', default='')}, due to " - f"[{deep_get(event, 'outcome', 'reason', default='')}]" - ) - - -def alert_context(event): - return okta_alert_context(event) diff --git a/rules/okta_rules/okta_brute_force_logins.yml b/rules/okta_rules/okta_brute_force_logins.yml deleted file mode 100644 index 41dbc3176..000000000 --- a/rules/okta_rules/okta_brute_force_logins.yml +++ /dev/null @@ -1,62 +0,0 @@ -AnalysisType: rule -Filename: okta_brute_force_logins.py -RuleID: "Okta.BruteForceLogins" -DisplayName: "--DEPRECATED-- Okta Brute Force Logins" -Enabled: false -LogTypes: - - Okta.SystemLog -Tags: - - Identity & Access Management - - Okta -Severity: Medium -Description: A user has failed to login more than 5 times in 15 minutes -Reference: https://support.okta.com/help/s/article/How-to-Configure-the-Number-of-Failed-Login-Attempts-Before-User-Lockout?language=en_US -Runbook: Reach out to the user if needed to validate the activity, and then block the IP -Threshold: 5 -DedupPeriodMinutes: 15 -SummaryAttributes: - - eventType - - severity - - displayMessage - - p_any_ip_addresses -Tests: - - Name: Failed Login Alert - ExpectedResult: true - Log: - { - "uuid": "2a992f80-d1ad-4f62-900e-8c68bb72a21b", - "published": "2020-11-25 21:27:03.496000000", - "eventType": "user.session.start", - "version": "0", - "severity": "INFO", - "displayMessage": "User login to Okta", - "actor": - { - "id": "00uu1uuuuIlllaaaa356", - "type": "User", - "alternateId": "jack@acme.io", - "displayName": "Jack Naglieri", - }, - "client": - { - "userAgent": - { - "browser": "CHROME", - "os": "Mac OS X", - "rawUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", - }, - "geographicalContext": - { - "geolocation": { "lat": 37.7852, "lon": -122.3874 }, - "city": "San Francisco", - "state": "California", - "country": "United States", - "postalCode": "94105", - }, - "zone": "null", - "ipAddress": "136.24.229.58", - "device": "Computer", - }, - "request": {}, - "outcome": { "result": "FAILURE", "reason": "VERIFICATION_ERROR" }, - } diff --git a/rules/okta_rules/okta_geo_improbable_access.py b/rules/okta_rules/okta_geo_improbable_access.py deleted file mode 100644 index e37a3c527..000000000 --- a/rules/okta_rules/okta_geo_improbable_access.py +++ /dev/null @@ -1,128 +0,0 @@ -from datetime import datetime, timedelta -from json import dumps, loads -from math import asin, cos, radians, sin, sqrt - -from panther_base_helpers import deep_get, okta_alert_context -from panther_detection_helpers.caching import get_string_set, put_string_set - -PANTHER_TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f" -EVENT_CITY_TRACKING = {} - - -def rule(event): - # Only evaluate successful logins - if ( - event.get("eventType") != "user.session.start" - or deep_get(event, "outcome", "result") == "FAILURE" - ): - return False - - new_login_stats = { - "city": deep_get(event, "client", "geographicalContext", "city"), - "lon": deep_get(event, "client", "geographicalContext", "geolocation", "lon"), - "lat": deep_get(event, "client", "geographicalContext", "geolocation", "lat"), - } - # Bail out if we have a None value in set as it causes false positives - if None in new_login_stats.values(): - return False - - # Generate a unique cache key for each user - login_key = gen_key(event) - # Retrieve the prior login info from the cache, if any - last_login = get_string_set(login_key) - # If we haven't seen this user login recently, store this login for future use and don't alert - if not last_login: - store_login_info(login_key, event) - return False - # Load the last login from the cache into an object we can compare - old_login_stats = loads(last_login.pop()) - - distance = haversine_distance(old_login_stats, new_login_stats) - old_time = datetime.strptime(old_login_stats["time"][:26], PANTHER_TIME_FORMAT) - new_time = datetime.strptime(event.get("p_event_time")[:26], PANTHER_TIME_FORMAT) - time_delta = (new_time - old_time).total_seconds() / 3600 # seconds in an hour - - # Don't let time_delta be 0 (divide by zero error below) - time_delta = time_delta or 0.0001 - # Calculate speed in Kilometers / Hour - speed = distance / time_delta - - # Calculation is complete, so store the most recent login for the next check - store_login_info(login_key, event) - EVENT_CITY_TRACKING[event.get("p_row_id")] = { - "new_city": new_login_stats.get("city", ""), - "old_city": old_login_stats.get("city", ""), - } - - return speed > 900 # Boeing 747 cruising speed - - -def gen_key(event): - return f"Okta.Login.GeographicallyImprobable{deep_get(event, 'actor', 'alternateId')}" - - -# Taken from stack overflow user Michael0x2a: https://stackoverflow.com/a/19412565/6645635 -def haversine_distance(grid_one, grid_two): - # approximate radius of earth in km - radius = 6371.0 - - # Convert the grid elements to radians - lon1, lat1, lon2, lat2 = map( - radians, [grid_one["lon"], grid_one["lat"], grid_two["lon"], grid_two["lat"]] - ) - - dlat = lat2 - lat1 - dlon = lon2 - lon1 - - distance_a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 - distance_c = 2 * asin(sqrt(distance_a)) - - return radius * distance_c - - -def store_login_info(key, event): - # Map the user to the lon/lat and time of the most recent login - put_string_set( - key, - [ - dumps( - { - "city": deep_get(event, "client", "geographicalContext", "city"), - "lon": deep_get(event, "client", "geographicalContext", "geolocation", "lon"), - "lat": deep_get(event, "client", "geographicalContext", "geolocation", "lat"), - "time": event.get("p_event_time"), - } - ) - ], - epoch_seconds=event.event_time_epoch() + timedelta(days=7).total_seconds(), - ) - - -def title(event): - # (Optional) Return a string which will be shown as the alert title. - old_city = deep_get( - EVENT_CITY_TRACKING.get(event.get("p_row_id")), "old_city", default="" - ) - new_city = deep_get( - EVENT_CITY_TRACKING.get(event.get("p_row_id")), "new_city", default="" - ) - return ( - f"Geographically improbable login for user [{deep_get(event, 'actor', 'alternateId')}] " - f"from [{old_city}] to [{new_city}]" - ) - - -def dedup(event): - # (Optional) Return a string which will de-duplicate similar alerts. - return deep_get(event, "actor", "alternateId") - - -def alert_context(event): - context = okta_alert_context(event) - context["old_city"] = deep_get( - EVENT_CITY_TRACKING.get(event.get("p_row_id")), "old_city", default="" - ) - context["new_city"] = deep_get( - EVENT_CITY_TRACKING.get(event.get("p_row_id")), "new_city", default="" - ) - return context diff --git a/rules/okta_rules/okta_geo_improbable_access.yml b/rules/okta_rules/okta_geo_improbable_access.yml deleted file mode 100644 index c2a5ffe69..000000000 --- a/rules/okta_rules/okta_geo_improbable_access.yml +++ /dev/null @@ -1,488 +0,0 @@ -AnalysisType: rule -Filename: okta_geo_improbable_access.py -RuleID: "Okta.GeographicallyImprobableAccess" -DisplayName: "Geographically Improbable Okta Login - DEPRECATED" -Enabled: false -LogTypes: - - Okta.SystemLog -Tags: - - Identity & Access Management - - Okta - - Initial Access:Valid Accounts -Reports: - MITRE ATT&CK: - - TA0001:T1078 -Severity: Info -CreateAlert: false -Description: A user has subsequent logins from two geographic locations that are very far apart -Runbook: Reach out to the user if needed to validate the activity, then lock the account -Reference: https://www.blinkops.com/blog/how-to-detect-and-remediate-okta-impossible-traveler-alerts -SummaryAttributes: - - eventType - - severity - - p_any_ip_addresses - - p_any_domain_names -Tests: - - Name: Non Login - ExpectedResult: false - Log: { "eventType": "logout" } - - Name: Failed Login - ExpectedResult: false - Log: - { - "actor": - { - "alternateId": "admin", - "displayName": "unknown", - "id": "unknown", - "type": "User", - }, - "authenticationContext": - { "authenticationStep": 0, "externalSessionId": "unknown" }, - "client": - { - "device": "Computer", - "geographicalContext": - { - "city": "Dois Irmaos", - "country": "Brazil", - "geolocation": { "lat": -29.6116, "lon": -51.0933 }, - "postalCode": "93950", - "state": "Rio Grande do Sul", - }, - "ipAddress": "redacted", - "userAgent": - { - "browser": "CHROME", - "os": "Linux", - "rawUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36", - }, - "zone": "null", - }, - "debugContext": - { - "debugData": - { - "loginResult": "VERIFICATION_ERROR", - "requestId": "redacted", - "requestUri": "redacted", - "threatSuspected": "false", - "url": "redacted", - }, - }, - "displayMessage": "User login to Okta", - "eventType": "user.session.start", - "legacyEventType": "core.user_auth.login_failed", - "outcome": { "reason": "VERIFICATION_ERROR", "result": "FAILURE" }, - "p_any_domain_names": ["rnvtelecom.com.br"], - "p_any_ip_addresses": ["redacted"], - "p_event_time": "redacted", - "p_log_type": "Okta.SystemLog", - "p_parse_time": "redacted", - "p_row_id": "redacted", - "p_source_id": "redacted", - "p_source_label": "Okta", - "published": "redacted", - "request": - { - "ipChain": - [ - { - "geographicalContext": - { - "city": "Dois Irmaos", - "country": "Brazil", - "geolocation": { "lat": -29.6116, "lon": -51.0933 }, - "postalCode": "93950", - "state": "Rio Grande do Sul", - }, - "ip": "redacted", - "version": "V4", - }, - ], - }, - "securityContext": - { - "asNumber": 263297, - "asOrg": "renovare telecom", - "domain": "rnvtelecom.com.br", - "isProxy": false, - "isp": "renovare telecom", - }, - "severity": "INFO", - "transaction": { "detail": {}, "id": "redacted", "type": "WEB" }, - "uuid": "redacted", - "version": "0", - } - - - Name: Incomplete GeoLocation info - ExpectedResult: false - Log: - { - "actor": - { - "alternateId": "admin", - "displayName": "unknown", - "id": "unknown", - "type": "User", - }, - "authenticationContext": - { "authenticationStep": 0, "externalSessionId": "unknown" }, - "client": - { - "device": "Computer", - "geographicalContext": - { - "country": "Brazil", - "geolocation": { "lat": -29.6116, "lon": -51.0933 }, - "postalCode": "93950", - "state": "Rio Grande do Sul", - }, - "ipAddress": "redacted", - "userAgent": - { - "browser": "CHROME", - "os": "Linux", - "rawUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36", - }, - "zone": "null", - }, - "debugContext": - { - "debugData": - { - "loginResult": "VERIFICATION_ERROR", - "requestId": "redacted", - "requestUri": "redacted", - "threatSuspected": "false", - "url": "redacted", - }, - }, - "displayMessage": "User login to Okta", - "eventType": "user.session.start", - "legacyEventType": "core.user_auth.login_failed", - "outcome": { "result": "SUCCESS" }, - "p_any_domain_names": ["rnvtelecom.com.br"], - "p_any_ip_addresses": ["redacted"], - "p_event_time": "redacted", - "p_log_type": "Okta.SystemLog", - "p_parse_time": "redacted", - "p_row_id": "redacted", - "p_source_id": "redacted", - "p_source_label": "Okta", - "published": "redacted", - "request": - { - "ipChain": - [ - { - "geographicalContext": - { - "country": "Brazil", - "geolocation": { "lat": -29.6116, "lon": -51.0933 }, - "postalCode": "93950", - "state": "Rio Grande do Sul", - }, - "ip": "redacted", - "version": "V4", - }, - ], - }, - "securityContext": - { - "asNumber": 263297, - "asOrg": "renovare telecom", - "domain": "rnvtelecom.com.br", - "isProxy": false, - "isp": "renovare telecom", - }, - "severity": "INFO", - "transaction": { "detail": {}, "id": "redacted", "type": "WEB" }, - "uuid": "redacted", - "version": "0", - } -# These tests can be enabled if testing is done through the UI or from the CLI with valid AWS -# credentials loaded to talk to the panther-kv table. - -# - -# Name: First Login - Baseline -# ExpectedResult: false -# Log: -# { -# "actor": { -# "alternateId": "buser@example.com", -# "displayName": "Bobert User", -# "id": "111", -# "type": "User" -# }, -# "authenticationContext": { -# "authenticationStep": 0, -# "externalSessionId": "111" -# }, -# "client": { -# "device": "Computer", -# "geographicalContext": { -# "city": "Baltimore", -# "country": "United States", -# "geolocation": { -# "lat": 39.2891, -# "lon": -76.5583 -# }, -# "postalCode": "21224", -# "state": "Maryland" -# }, -# "ipAddress": "192.168.0.9", -# "userAgent": { -# "browser": "CHROME", -# "os": "Windows 10", -# "rawUserAgent": "Mozilla/5.0" -# }, -# "zone": "null" -# }, -# "debugContext": { -# "debugData": { -# "requestId": "11111", -# "requestUri": "/api/v1/authn/factors/password/verify", -# "threatSuspected": "false", -# "url": "/api/v1/authn/factors/password/verify?rememberDevice=false" -# } -# }, -# "displayMessage": "User login to Okta", -# "eventType": "user.session.start", -# "legacyEventType": "core.user_auth.login_success", -# "outcome": { -# "result": "SUCCESS" -# }, -# "p_any_domain_names": [ -# "comcast.net" -# ], -# "p_any_ip_addresses": [ -# "192.168.0.9" -# ], -# "p_event_time": "2020-01-01 00:00:00.000000000", -# "p_log_type": "Okta.SystemLog", -# "p_parse_time": "2020-01-01 00:00:01.000000000", -# "p_row_id": "111222", -# "published": "2020-01-01 00:00:00.000000000", -# "request": { -# "ipChain": [ -# { -# "geographicalContext": { -# "city": "Baltimore", -# "country": "United States", -# "geolocation": { -# "lat": 39.2891, -# "lon": -76.5583 -# }, -# "postalCode": "21224", -# "state": "Maryland" -# }, -# "ip": "192.168.0.9", -# "version": "V4" -# } -# ] -# }, -# "securityContext": { -# "asNumber": 1234, -# "asOrg": "comcast", -# "domain": "comcast.net", -# "isProxy": false, -# "isp": "comcast cable communications llc" -# }, -# "severity": "INFO", -# "transaction": { -# "detail": {}, -# "id": "AbC", -# "type": "WEB" -# }, -# "uuid": "1234-abc-1234", -# "version": "0" -# } -# - -# Name: Second Login - Safe -# ExpectedResult: false -# Log: -# { -# "actor": { -# "alternateId": "buser@example.com", -# "displayName": "Bobert User", -# "id": "111", -# "type": "User" -# }, -# "authenticationContext": { -# "authenticationStep": 0, -# "externalSessionId": "111" -# }, -# "client": { -# "device": "Computer", -# "geographicalContext": { -# "city": "Bethesda", -# "country": "United States", -# "geolocation": { -# "lat": 38.9846, -# "lon": -77.0947 -# }, -# "postalCode": "20810", -# "state": "Maryland" -# }, -# "ipAddress": "192.168.0.9", -# "userAgent": { -# "browser": "CHROME", -# "os": "Windows 10", -# "rawUserAgent": "Mozilla/5.0" -# }, -# "zone": "null" -# }, -# "debugContext": { -# "debugData": { -# "requestId": "11111", -# "requestUri": "/api/v1/authn/factors/password/verify", -# "threatSuspected": "false", -# "url": "/api/v1/authn/factors/password/verify?rememberDevice=false" -# } -# }, -# "displayMessage": "User login to Okta", -# "eventType": "user.session.start", -# "legacyEventType": "core.user_auth.login_success", -# "outcome": { -# "result": "SUCCESS" -# }, -# "p_any_domain_names": [ -# "comcast.net" -# ], -# "p_any_ip_addresses": [ -# "192.168.0.9" -# ], -# "p_event_time": "2020-01-02 00:00:00.000000000", -# "p_log_type": "Okta.SystemLog", -# "p_parse_time": "2020-01-02 00:00:01.000000000", -# "p_row_id": "111222", -# "published": "2020-01-02 00:00:00.000000000", -# "request": { -# "ipChain": [ -# { -# "geographicalContext": { -# "city": "Bethesda", -# "country": "United States", -# "geolocation": { -# "lat": 38.9846, -# "lon": -77.0947 -# }, -# "postalCode": "20810", -# "state": "Maryland" -# }, -# "ip": "192.168.0.9", -# "version": "V4" -# } -# ] -# }, -# "securityContext": { -# "asNumber": 1234, -# "asOrg": "comcast", -# "domain": "comcast.net", -# "isProxy": false, -# "isp": "comcast cable communications llc" -# }, -# "severity": "INFO", -# "transaction": { -# "detail": {}, -# "id": "AbC", -# "type": "WEB" -# }, -# "uuid": "1234-abc-1234", -# "version": "0" -# } -# - -# Name: Third Login - Too Fast -# ExpectedResult: true -# Log: -# { -# "actor": { -# "alternateId": "buser@example.com", -# "displayName": "Bobert User", -# "id": "111", -# "type": "User" -# }, -# "authenticationContext": { -# "authenticationStep": 0, -# "externalSessionId": "111" -# }, -# "client": { -# "device": "Computer", -# "geographicalContext": { -# "city": "Baltimore", -# "country": "United States", -# "geolocation": { -# "lat": 39.2891, -# "lon": -76.5583 -# }, -# "postalCode": "21224", -# "state": "Maryland" -# }, -# "ipAddress": "192.168.0.9", -# "userAgent": { -# "browser": "CHROME", -# "os": "Windows 10", -# "rawUserAgent": "Mozilla/5.0" -# }, -# "zone": "null" -# }, -# "debugContext": { -# "debugData": { -# "requestId": "11111", -# "requestUri": "/api/v1/authn/factors/password/verify", -# "threatSuspected": "false", -# "url": "/api/v1/authn/factors/password/verify?rememberDevice=false" -# } -# }, -# "displayMessage": "User login to Okta", -# "eventType": "user.session.start", -# "legacyEventType": "core.user_auth.login_success", -# "outcome": { -# "result": "SUCCESS" -# }, -# "p_any_domain_names": [ -# "comcast.net" -# ], -# "p_any_ip_addresses": [ -# "192.168.0.9" -# ], -# "p_event_time": "2020-01-02 00:01:00.000000000", -# "p_log_type": "Okta.SystemLog", -# "p_parse_time": "2020-01-02 00:01:01.000000000", -# "p_row_id": "111222", -# "published": "2020-01-02 00:00:01.000000000", -# "request": { -# "ipChain": [ -# { -# "geographicalContext": { -# "city": "Baltimore", -# "country": "United States", -# "geolocation": { -# "lat": 39.2891, -# "lon": -76.5583 -# }, -# "postalCode": "21224", -# "state": "Maryland" -# }, -# "ip": "192.168.0.9", -# "version": "V4" -# } -# ] -# }, -# "securityContext": { -# "asNumber": 1234, -# "asOrg": "comcast", -# "domain": "comcast.net", -# "isProxy": false, -# "isp": "comcast cable communications llc" -# }, -# "severity": "INFO", -# "transaction": { -# "detail": {}, -# "id": "AbC", -# "type": "WEB" -# }, -# "uuid": "1234-abc-1234", -# "version": "0" -# } diff --git a/rules/onelogin_rules/onelogin_admin_role_assigned.py b/rules/onelogin_rules/onelogin_admin_role_assigned.py deleted file mode 100644 index b36f1758c..000000000 --- a/rules/onelogin_rules/onelogin_admin_role_assigned.py +++ /dev/null @@ -1,11 +0,0 @@ -def rule(event): - # event_type_id 72 is permissions assigned - return str(event.get("event_type_id")) == "72" and event.get("privilege_name") == "Super user" - - -def title(event): - # (Optional) Return a string which will be shown as the alert title. - return ( - f"[{event.get('actor_user_name', '')}] assigned super user" - f" permissions to [{event.get('user_name', '')}]" - ) diff --git a/rules/onelogin_rules/onelogin_admin_role_assigned.yml b/rules/onelogin_rules/onelogin_admin_role_assigned.yml deleted file mode 100644 index a5ce591b1..000000000 --- a/rules/onelogin_rules/onelogin_admin_role_assigned.yml +++ /dev/null @@ -1,32 +0,0 @@ -AnalysisType: rule -Filename: onelogin_admin_role_assigned.py -RuleID: "OneLogin.AdminRoleAssigned" -DisplayName: "--DEPRECATED-- OneLogin Admin Role Assigned" -Enabled: false -LogTypes: - - OneLogin.Events -Tags: - - Identity & Access Management -Reference: https://onelogin.service-now.com/kb_view_customer.do?sysparm_article=KB0010391 -Severity: Low -SummaryAttributes: - - account_id - - user_name - - user_id - - privilege_name -Tests: - - Name: Non permissions assigned event - ExpectedResult: false - Log: { "event_type_id": "8" } - - Name: Non super user permissions assigned - ExpectedResult: false - Log: { "event_type_id": "72", "privilege_name": "Manage users" } - - Name: Super user permissions assigned - ExpectedResult: true - Log: - { - "event_type_id": "72", - "privilege_name": "Super user", - "user_name": "Evil Bob", - "actor_user_name": "Bobert O'Bobly", - } diff --git a/rules/onelogin_rules/onelogin_brute_force_by_ip.py b/rules/onelogin_rules/onelogin_brute_force_by_ip.py deleted file mode 100644 index e6ac85160..000000000 --- a/rules/onelogin_rules/onelogin_brute_force_by_ip.py +++ /dev/null @@ -1,7 +0,0 @@ -def rule(event): - # filter events; event type 6 is a failed authentication - return str(event.get("event_type_id")) == "6" - - -def title(event): - return f"IP [{event.get('ipaddr', '')}] has exceeded the failed logins threshold" diff --git a/rules/onelogin_rules/onelogin_brute_force_by_ip.yml b/rules/onelogin_rules/onelogin_brute_force_by_ip.yml deleted file mode 100644 index 13dab5b8c..000000000 --- a/rules/onelogin_rules/onelogin_brute_force_by_ip.yml +++ /dev/null @@ -1,45 +0,0 @@ -AnalysisType: rule -Filename: onelogin_brute_force_by_ip.py -RuleID: "OneLogin.BruteForceByIP" -DisplayName: "--DEPRECATED-- OneLogin Brute Force IP" -Enabled: false -LogTypes: - - OneLogin.Events -Tags: - - OneLogin - - Credential Access:Brute Force -Severity: Medium -Reports: - MITRE ATT&CK: - - TA0006:T1110 -Description: A single ip address was denied access to OneLogin more times than the configured threshold. -Threshold: 10 -DedupPeriodMinutes: 10 -Reference: https://www.fortinet.com/resources/cyberglossary/brute-force-attack#:~:text=A%20brute%20force%20attack%20is,and%20organizations'%20systems%20and%20networks. -Runbook: Analyze the IP they came from, and other actions taken before/after. Check if a user from this ip eventually authenticated successfully. -SummaryAttributes: - - account_id - - user_name - - user_id - - p_any_ip_addresses -Tests: - - Name: Normal Login Event - ExpectedResult: false - Log: - { - "event_type_id": "8", - "actor_user_id": 123456, - "actor_user_name": "Bob Cat", - "user_id": 123456, - "user_name": "Bob Cat", - } - - Name: Failed Login Event - ExpectedResult: true - Log: - { - "event_type_id": "6", - "actor_user_id": 123456, - "actor_user_name": "Bob Cat", - "user_id": 123456, - "user_name": "Bob Cat", - } diff --git a/rules/onelogin_rules/onelogin_brute_force_by_username.py b/rules/onelogin_rules/onelogin_brute_force_by_username.py deleted file mode 100644 index afe600280..000000000 --- a/rules/onelogin_rules/onelogin_brute_force_by_username.py +++ /dev/null @@ -1,10 +0,0 @@ -def rule(event): - # filter events; event type 6 is a failed authentication - return str(event.get("event_type_id")) == "6" - - -def title(event): - return ( - f"User [{event.get('user_name', '')}] " - f"has exceeded the failed logins threshold" - ) diff --git a/rules/onelogin_rules/onelogin_brute_force_by_username.yml b/rules/onelogin_rules/onelogin_brute_force_by_username.yml deleted file mode 100644 index 082eda27f..000000000 --- a/rules/onelogin_rules/onelogin_brute_force_by_username.yml +++ /dev/null @@ -1,44 +0,0 @@ -AnalysisType: rule -Filename: onelogin_brute_force_by_username.py -RuleID: "OneLogin.BruteForceByUsername" -DisplayName: "--DEPRECATED-- OneLogin Brute Force Username" -Enabled: false -LogTypes: - - OneLogin.Events -Tags: - - OneLogin - - Credential Access:Brute Force -Severity: Medium -Reports: - MITRE ATT&CK: - - TA0006:T1110 -Description: A OneLogin user was denied access more times than the configured threshold. -Threshold: 10 -DedupPeriodMinutes: 10 -Reference: https://www.fortinet.com/resources/cyberglossary/brute-force-attack#:~:text=A%20brute%20force%20attack%20is,and%20organizations'%20systems%20and%20networks. -Runbook: Analyze the IP they came from, and other actions taken before/after. Check if this user eventually authenticated successfully. -SummaryAttributes: - - account_id - - user_name - - user_id -Tests: - - Name: Normal Login Event - ExpectedResult: false - Log: - { - "event_type_id": "8", - "actor_user_id": 123456, - "actor_user_name": "Bob Cat", - "user_id": 123456, - "user_name": "Bob Cat", - } - - Name: Failed Login Event - ExpectedResult: true - Log: - { - "event_type_id": "6", - "actor_user_id": 123456, - "actor_user_name": "Bob Cat", - "user_id": 123456, - "user_name": "Bob Cat", - } diff --git a/rules/onelogin_rules/onelogin_high_risk_login.py b/rules/onelogin_rules/onelogin_high_risk_login.py deleted file mode 100644 index 9600ce362..000000000 --- a/rules/onelogin_rules/onelogin_high_risk_login.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import timedelta - -from panther_detection_helpers.caching import get_counter, increment_counter, reset_counter - -THRESH_TTL = timedelta(minutes=10).total_seconds() - - -def rule(event): - # Filter events down to successful and failed login events - if not event.get("user_id") or str(event.get("event_type_id")) not in ["5", "6"]: - return False - - event_key = get_key(event) - # check risk associated with this event - if event.get("risk_score", 0) > 50: - # a failed authentication attempt with high risk score - if str(event.get("event_type_id")) == "6": - # update a counter for this user's failed login attempts with a high risk score - increment_counter(event_key, event.event_time_epoch() + THRESH_TTL) - - # Trigger alert if this user recently - # failed a high risk login - if str(event.get("event_type_id")) == "5": - if get_counter(event_key) > 0: - reset_counter(event_key) - return True - return False - - -def get_key(event): - return __name__ + ":" + event.get("user_name", "") - - -def title(event): - return ( - f"A user [{event.get('user_name', '')}] successfully logged in " - f"after a failed high risk login event" - ) diff --git a/rules/onelogin_rules/onelogin_high_risk_login.yml b/rules/onelogin_rules/onelogin_high_risk_login.yml deleted file mode 100644 index 5abbd0c11..000000000 --- a/rules/onelogin_rules/onelogin_high_risk_login.yml +++ /dev/null @@ -1,30 +0,0 @@ -AnalysisType: rule -Filename: onelogin_high_risk_login.py -RuleID: "OneLogin.HighRiskLogin" -DisplayName: "DEPRECATED - OneLogin High Risk Login" -Enabled: true -LogTypes: - - OneLogin.Events -Tags: - - OneLogin - - DEPRECATED -Severity: Medium -Description: A OneLogin user successfully logged in after a failed high-risk login attempt. -Reference: https://resources.onelogin.com/OneLogin_RiskBasedAuthentication-WP-v5.pdf -Runbook: Investigate whether this was caused by expected user activity. -SummaryAttributes: - - account_id - - event_type_id - - user_name - - user_id -Tests: - - Name: Normal Login Event - ExpectedResult: false - Log: - { - "event_type_id": "6", - "actor_user_id": 123456, - "actor_user_name": "Bob Cat", - "user_id": 123456, - "user_name": "Bob Cat", - } diff --git a/rules/onelogin_rules/onelogin_unusual_login.py b/rules/onelogin_rules/onelogin_unusual_login.py deleted file mode 100644 index 17032c717..000000000 --- a/rules/onelogin_rules/onelogin_unusual_login.py +++ /dev/null @@ -1,73 +0,0 @@ -import json - -import requests -from panther_detection_helpers.caching import get_string_set, put_string_set - -FINGERPRINT_THRESHOLD = 3 -EVENT_LOGIN_INFO = {} - - -def rule(event): - # Pre-filter to save compute time where possible. event_type_id = 5 is login events. - if str(event.get("event_type_id")) != "5" or event.get("ipaddr") is None: - return False - - # Lookup geo-ip data via API call - url = "https://ipinfo.io/" + event.get("ipaddr") + "/geo" - - # Skip API call if this is a unit test - if "panther_api_data" in event: - resp = lambda: None # pylint: disable=C3001 - setattr(resp, "status_code", 200) - setattr(resp, "text", event.get("panther_api_data")) - else: - # This response looks like the following: - # {‘ip': '8.8.8.8', 'city': 'Mountain View', 'region': 'California', 'country': 'US', - # 'loc': '37.4056,-122.0775', 'postal': '94043', 'timezone': 'America/Los_Angeles'} - resp = requests.get(url, timeout=5) - - if resp.status_code != 200: - # Could raise an exception here for ops team to look into - return False - login_info = json.loads(resp.text) - # The idea is to create a fingerprint of this login, and then keep track of all the fingerprints - # for a given user's logins. In this way, we can detect unusual logins. - login_tuple = login_info.get("region", "") + ":" + login_info.get("city", "") - EVENT_LOGIN_INFO[event.get("p_row_id")] = login_tuple - - # Lookup & store persistent data - event_key = get_key(event) - last_login_info = get_string_set(event_key) - if not last_login_info: - # Store this as the first login if we've never seen this user login before - put_string_set(event_key, [json.dumps({login_tuple: 1})]) - return False - last_login_info = json.loads(last_login_info.pop()) - - last_login_info[login_tuple] = last_login_info.get(login_tuple, 0) + 1 - put_string_set(event_key, [json.dumps(last_login_info)]) - - # Here we are checking if this login's fingerprint is one of the top three most common - # fingerprints for this user. If it is not, we fire an alert. - tuple_count = last_login_info.get(login_tuple) - higher_tuples = 0 - for tcount in last_login_info.values(): - if tcount > tuple_count: - higher_tuples += 1 - if higher_tuples >= FINGERPRINT_THRESHOLD: - return True - - return False - - -def get_key(event): - # Use the name so that test data doesn't interfere with live data - return __name__ + ":" + str(event.get("user_id", "")) - - -def title(event): - # (Optional) Return a string which will be shown as the alert title. - return ( - f"Unusual OneLogin access for user [{event.get('user_name', '')}]" - f" from [{EVENT_LOGIN_INFO[event.get('p_row_id')]}]" - ) diff --git a/rules/onelogin_rules/onelogin_unusual_login.yml b/rules/onelogin_rules/onelogin_unusual_login.yml deleted file mode 100644 index ee2164613..000000000 --- a/rules/onelogin_rules/onelogin_unusual_login.yml +++ /dev/null @@ -1,97 +0,0 @@ -AnalysisType: rule -Filename: onelogin_unusual_login.py -RuleID: "OneLogin.UnusualLogin" -DisplayName: "--DEPRECATED-- Unusual OneLogin Login" -# This rule is disabled by default because it makes API calls out to the internet. At high volumes -# of OneLogin activity, this could get throttled unless you buy a subscription to ipinfo. -Enabled: false -LogTypes: - - OneLogin.Events -Tags: - - Identity & Access Management -Reference: https://actzero.ai/resources/blog/a-smarter-way-to-detect-suspicious-cloud-logins -Severity: Medium -Description: Deprecated. Please see Standard.UnusualLogin instead. -SummaryAttributes: - - account_id - - user_id - - user_name - - ipaddr -Tests: - - Name: Non Login - ExpectedResult: false - Log: { "event_type_id": "8" } -# These tests can be enabled if testing is done through the UI or from the CLI with valid AWS -# credentials loaded to talk to the panther-kv table. - -# - -# Name: Login1 -# ExpectedResult: false -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Mountain View\", \"region\": \"California\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123 -# } -# - -# Name: Login2 -# ExpectedResult: false -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Mountain View\", \"region\": \"California\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123 -# } -# - -# Name: Login3 -# ExpectedResult: false -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Palo Alto\", \"region\": \"California\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123 -# } -# - -# Name: Login4 -# ExpectedResult: false -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Palo Alto\", \"region\": \"California\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123 -# } -# - -# Name: Login5 -# ExpectedResult: false -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Walnut Creek\", \"region\": \"California\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123 -# } -# - -# Name: Login6 -# ExpectedResult: false -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Walnut Creek\", \"region\": \"California\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123 -# } -# - -# Name: Login7 -# ExpectedResult: true -# Log: -# { -# "panther_api_data": "{\"ip\": \"8.8.8.8\", \"city\": \"Seattle\", \"region\": \"Washington\", \"country\": \"US\", \"loc\": \"37.4056,-122.0775\", \"postal\": \"94043\", \"timezone\": \"America/Los_Angeles\", \"readme\": \"https://ipinfo.io/missingauth\"}", -# "event_type_id": "5", -# "ipaddr": "8.8.8.8", -# "user_id": 123, -# "user_name": "Bobert Bobson" -# } diff --git a/rules/panther_ioc_rules/atlassian_confluence_ip_iocs.py b/rules/panther_ioc_rules/atlassian_confluence_ip_iocs.py deleted file mode 100644 index 7f2427001..000000000 --- a/rules/panther_ioc_rules/atlassian_confluence_ip_iocs.py +++ /dev/null @@ -1,12 +0,0 @@ -from panther_iocs import ioc_match - -VOLEXITY_CONFLUENCE_IP_IOCS = [] - - -def rule(_): - return False # any(ioc_match(event.get("p_any_ip_addresses"), VOLEXITY_CONFLUENCE_IP_IOCS)) - - -def title(event): - ips = ",".join(ioc_match(event.get("p_any_ip_addresses"), VOLEXITY_CONFLUENCE_IP_IOCS)) - return f"IP seen from May 2022 exploitation of Confluence 0-Day: {ips}" diff --git a/rules/panther_ioc_rules/atlassian_confluence_ip_iocs.yml b/rules/panther_ioc_rules/atlassian_confluence_ip_iocs.yml deleted file mode 100644 index 8e469e48d..000000000 --- a/rules/panther_ioc_rules/atlassian_confluence_ip_iocs.yml +++ /dev/null @@ -1,48 +0,0 @@ -AnalysisType: rule -Filename: atlassian_confluence_ip_iocs.py -RuleID: "Confluence.0DayIPs" -DisplayName: "DEPRECATED - Confluence 0-Day Indicators of Compromise (IOCs)" -Enabled: False -LogTypes: - - AWS.ALB - - AWS.CloudTrail - - AWS.GuardDuty - - AWS.S3ServerAccess - - AWS.VPCFlow - - GCP.AuditLog - - Apache.AccessCombined - - Apache.AccessCommon - - Cloudflare.Firewall - - Cloudflare.HttpRequest - - Juniper.Access - - Nginx.Access -Tags: - - AWS - - DNS - - GCP - - Apache - - Cloudflare - - Nginx - - Juniper - - Deprecated -Severity: High -Description: > - Detects IP addresses observed exploiting the 0-Day CVE-2022-26134 -Reference: > - https://confluence.atlassian.com/doc/confluence-security-advisory-2022-06-02-1130377146.html - https://www.volexity.com/blog/2022/06/02/zero-day-exploitation-of-atlassian-confluence/ -Runbook: > - Investigate traffic from these IP addresses and look for other IOCs associated with the 0-Day exploit CVE-2022-26134 -SummaryAttributes: - - p_any_domain_names - - p_any_ip_addresses -Tests: - - Name: Non-matching traffic - ExpectedResult: false - Log: - { - "dstport": 53, - "dstaddr": "1.1.1.1", - "srcaddr": "10.0.0.1", - "p_any_ip_addresses": ["1.1.1.1"], - } diff --git a/rules/panther_ioc_rules/log4j_exploit_iocs.py b/rules/panther_ioc_rules/log4j_exploit_iocs.py deleted file mode 100644 index 16f699bbb..000000000 --- a/rules/panther_ioc_rules/log4j_exploit_iocs.py +++ /dev/null @@ -1,14 +0,0 @@ -# from panther_iocs import LOG4J_EXPLOIT_IOCS - - -def rule(_): - return False - # event_string = str(event).lower() - # for exploit in LOG4J_EXPLOIT_IOCS: - # if exploit.lower() in event_string: - # return True - # return False - - -def title(event): - return f"Log4J exploit attempt detected in log source {event.get('p_log_type')}" diff --git a/rules/panther_ioc_rules/log4j_exploit_iocs.yml b/rules/panther_ioc_rules/log4j_exploit_iocs.yml deleted file mode 100644 index eb72203d8..000000000 --- a/rules/panther_ioc_rules/log4j_exploit_iocs.yml +++ /dev/null @@ -1,51 +0,0 @@ -AnalysisType: rule -Filename: log4j_exploit_iocs.py -RuleID: "IOC.Log4jExploit" -DisplayName: "DEPRECATED - Log4J Exploit IOC Search" -Enabled: False -LogTypes: - - AWS.ALB - - AWS.CloudTrail - - AWS.S3ServerAccess - - Apache.AccessCombined - - Apache.AccessCommon - - Cloudflare.Firewall - - Cloudflare.HttpRequest - - Fastly.Access - - GCP.AuditLog - - Juniper.Access - - Juniper.Firewall - - Nginx.Access - - Syslog.RFC3164 - - Syslog.RFC5424 -Tags: - - AWS - - GCP - - Web - - Log4J - - Execution:Exploitation for Client Execution - - Deprecated -Reports: - MITRE ATT&CK: - - TA0002:T1203 -Severity: Info -Description: > - Monitors for potential exploit attempts agains CVE-2021-44228, Log4J remote code execution -Reference: > - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228 -Runbook: > - Investigate exploit attempt event content to determine scope and type of exploitation. Patch Log4J to the latest patched version -SummaryAttributes: - - p_any_domain_names - - p_any_ip_addresses - - p_log_type - - p_source_label -Tests: - - Name: No Exploit found - ExpectedResult: false - Log: - { - "src_addr": "10.10.10.123", - "dst_addr": "10.10.10.124", - "payload": "Nothing to see here", - } diff --git a/rules/panther_ioc_rules/log4j_ip_iocs.py b/rules/panther_ioc_rules/log4j_ip_iocs.py deleted file mode 100644 index e52ee0de9..000000000 --- a/rules/panther_ioc_rules/log4j_ip_iocs.py +++ /dev/null @@ -1,12 +0,0 @@ -from panther_iocs import ioc_match - -LOG4J_IP_IOCS = [] - - -def rule(_): - return False # any(ioc_match(event.get("p_any_ip_addresses"), LOG4J_IP_IOCS)) - - -def title(event): - ips = ",".join(ioc_match(event.get("p_any_ip_addresses"), LOG4J_IP_IOCS)) - return f"IP seen in LOG4J exploit scanning detected IP: {ips}" diff --git a/rules/panther_ioc_rules/log4j_ip_iocs.yml b/rules/panther_ioc_rules/log4j_ip_iocs.yml deleted file mode 100644 index dfca45d90..000000000 --- a/rules/panther_ioc_rules/log4j_ip_iocs.yml +++ /dev/null @@ -1,47 +0,0 @@ -AnalysisType: rule -Filename: log4j_ip_iocs.py -RuleID: "IOC.Log4JIPs" -DisplayName: "DEPRECATED - LOG4J Indicators of Compromise (IP)" -Enabled: false -LogTypes: - - AWS.ALB - - AWS.CloudTrail - - AWS.GuardDuty - - AWS.S3ServerAccess - - AWS.VPCFlow - - GCP.AuditLog - - Apache.AccessCombined - - Apache.AccessCommon - - Cloudflare.HttpRequest - - Juniper.Access - - Nginx.Access - -Tags: - - AWS - - DNS - - GCP - - Apache - - Cloudflare - - Nginx - - Juniper - - Deprecated -Severity: High -Description: > - Deprecated rule. IP addresses involved in LOG4j scanning have been largely recycled at this point, this generates a large amount of false alerts at this point -Reference: > - https://blog.cloudflare.com/actual-cve-2021-44228-payloads-captured-in-the-wild -Runbook: > - Investigate traffic from these IP addresses and look for other IOCs associated with the LOG4J exploit CVE-2021-44228 -SummaryAttributes: - - p_any_domain_names - - p_any_ip_addresses -Tests: - - Name: Non-matching traffic - ExpectedResult: false - Log: - { - "dstport": 53, - "dstaddr": "1.1.1.1", - "srcaddr": "10.0.0.1", - "p_any_ip_addresses": ["1.1.1.1"], - } diff --git a/rules/panther_ioc_rules/sunburst_fqdn_iocs.py b/rules/panther_ioc_rules/sunburst_fqdn_iocs.py deleted file mode 100644 index 3ce7b7401..000000000 --- a/rules/panther_ioc_rules/sunburst_fqdn_iocs.py +++ /dev/null @@ -1,12 +0,0 @@ -from panther_iocs import ioc_match, sanitize_domain - -SUNBURST_FQDN_IOCS = [] - - -def rule(_): - return False # any(ioc_match(event.get("p_any_domain_names"), SUNBURST_FQDN_IOCS)) - - -def title(event): - domains = ",".join(ioc_match(event.get("p_any_domain_names"), SUNBURST_FQDN_IOCS)) - return sanitize_domain(f"Sunburst Indicator of Compromise Detected [Domains]: {domains}") diff --git a/rules/panther_ioc_rules/sunburst_fqdn_iocs.yml b/rules/panther_ioc_rules/sunburst_fqdn_iocs.yml deleted file mode 100644 index 6cc5f5b13..000000000 --- a/rules/panther_ioc_rules/sunburst_fqdn_iocs.yml +++ /dev/null @@ -1,54 +0,0 @@ -AnalysisType: rule -Filename: sunburst_fqdn_iocs.py -RuleID: "IOC.SunburstFQDNIOCs" -DisplayName: "DEPRECATED - Sunburst Indicators of Compromise (FQDN)" -Enabled: false -LogTypes: - - AWS.ALB - - AWS.CloudTrail - - AWS.GuardDuty - - AWS.S3ServerAccess - - AWS.VPCFlow - - Box.Event - - CiscoUmbrella.DNS - - GCP.AuditLog - - Gravitational.TeleportAudit - - GSuite.Reports - - Okta.SystemLog - - OneLogin.Events - - Osquery.Differential -Tags: - - AWS - - Box - - DNS - - GCP - - GSuite - - SSH - - OneLogin - - Osquery - - Initial Access:Trusted Relationship - - Deprecated -Reports: - MITRE ATT&CK: - - TA0001:T1199 -Severity: High -Description: > - Monitors for communication to known Sunburst Backdoor FQDNs. These IOCs indicate a potential breach and have been associated with a sophisticated nation-state actor. -Reference: > - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html -Runbook: > - Investigate the resources communicating with the matched IOC for signs of compromise or other malicious activity. Consider rotating credentials on any systems observed communicating with these known malicious systems. -SummaryAttributes: - - p_any_domain_names - - p_any_ip_addresses - - p_any_sha256_hashes -Tests: - - Name: Non-matching traffic - ExpectedResult: false - Log: - { - "dstport": 53, - "dstaddr": "1.1.1.1", - "srcaddr": "10.0.0.1", - "p_any_domain_names": ["example.com"], - } diff --git a/rules/panther_ioc_rules/sunburst_ip_iocs.py b/rules/panther_ioc_rules/sunburst_ip_iocs.py deleted file mode 100644 index 81cb90cd3..000000000 --- a/rules/panther_ioc_rules/sunburst_ip_iocs.py +++ /dev/null @@ -1,12 +0,0 @@ -from panther_iocs import ioc_match - -SUNBURST_IP_IOCS = [] - - -def rule(_): - return False # any(ioc_match(event.get("p_any_ip_addresses"), SUNBURST_IP_IOCS)) - - -def title(event): - ips = ",".join(ioc_match(event.get("p_any_ip_addresses"), SUNBURST_IP_IOCS)) - return f"Sunburst Indicator of Compromise Detected [IPs]: {ips}" diff --git a/rules/panther_ioc_rules/sunburst_ip_iocs.yml b/rules/panther_ioc_rules/sunburst_ip_iocs.yml deleted file mode 100644 index 737dad030..000000000 --- a/rules/panther_ioc_rules/sunburst_ip_iocs.yml +++ /dev/null @@ -1,44 +0,0 @@ -AnalysisType: rule -Filename: sunburst_ip_iocs.py -RuleID: "IOC.SunburstIPIOCs" -DisplayName: "--Deprecated-- Sunburst Indicators of Compromise (IP)" -Enabled: false -LogTypes: - - AWS.ALB - - AWS.CloudTrail - - AWS.GuardDuty - - AWS.S3ServerAccess - - AWS.VPCFlow - - Box.Event - - CiscoUmbrella.DNS - - GCP.AuditLog - - Gravitational.TeleportAudit - - GSuite.Reports - - Okta.SystemLog - - OneLogin.Events - - Osquery.Differential -Tags: - - AWS - - Box - - DNS - - GCP - - GSuite - - SSH - - OneLogin - - Osquery - - Deprecated -Severity: High -Description: > - Monitors for communication to known Sunburst Backdoor IPs. These IOCs indicate a potential breach and have been associated with a sophisticated nation-state actor. -Reference: > - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html -Runbook: > - Investigate the resources communicating with the matched IOC for signs of compromise or other malicious activity. Consider rotating credentials on any systems observed communicating with these known malicious systems. -SummaryAttributes: - - p_any_domain_names - - p_any_ip_addresses - - p_any_sha256_hashes -Tests: - - Name: Non-matching traffic - ExpectedResult: false - Log: { "dstport": 53, "dstaddr": "1.1.1.1", "srcaddr": "10.0.0.1" } diff --git a/rules/panther_ioc_rules/sunburst_sha256_iocs.py b/rules/panther_ioc_rules/sunburst_sha256_iocs.py deleted file mode 100644 index a319fad40..000000000 --- a/rules/panther_ioc_rules/sunburst_sha256_iocs.py +++ /dev/null @@ -1,12 +0,0 @@ -from panther_iocs import ioc_match - -SUNBURST_SHA256_IOCS = [] - - -def rule(_): - return False # any(ioc_match(event.get("p_any_sha256_hashes"), SUNBURST_SHA256_IOCS)) - - -def title(event): - hashes = ",".join(ioc_match(event.get("p_any_sha256_hashes"), SUNBURST_SHA256_IOCS)) - return f"Sunburst Indicator of Compromise Detected [SHA256 hash]: {hashes}" diff --git a/rules/panther_ioc_rules/sunburst_sha256_iocs.yml b/rules/panther_ioc_rules/sunburst_sha256_iocs.yml deleted file mode 100644 index 03c381aa2..000000000 --- a/rules/panther_ioc_rules/sunburst_sha256_iocs.yml +++ /dev/null @@ -1,53 +0,0 @@ -AnalysisType: rule -Filename: sunburst_sha256_iocs.py -RuleID: "IOC.SunburstSHA256IOCs" -DisplayName: "DEPRECATED - Sunburst Indicators of Compromise (SHA-256)" -Enabled: false -LogTypes: - - AWS.ALB - - AWS.CloudTrail - - AWS.GuardDuty - - AWS.S3ServerAccess - - Box.Event - - GCP.AuditLog - - Gravitational.TeleportAudit - - GSuite.Reports - - Okta.SystemLog - - OneLogin.Events - - Osquery.Differential -Tags: - - AWS - - Box - - DNS - - GCP - - GSuite - - SSH - - OneLogin - - Osquery - - Initial Access:Trusted Relationship - - Deprecated -Reports: - MITRE ATT&CK: - - TA0001:T1199 -Severity: High -Description: > - Monitors for hashes to known Sunburst Backdoor SHA256. These IOCs indicate a potential breach and have been associated with a sophisticated nation-state actor. -Reference: > - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html -Runbook: > - Investigate the resources communicating with the matched IOC for signs of compromise or other malicious activity. Consider rotating credentials on any systems observed communicating with these known malicious systems. -SummaryAttributes: - - p_any_domain_names - - p_any_ip_addresses - - p_any_sha256_hashes -Tests: - - Name: Non-matching traffic - ExpectedResult: false - Log: - { - "dstport": 53, - "dstaddr": "1.1.1.1", - "srcaddr": "10.0.0.1", - "p_any_sha256_hashes": - ["98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4"], - } diff --git a/rules/standard_rules/unusual_login_deprecated.py b/rules/standard_rules/unusual_login_deprecated.py deleted file mode 100644 index a2277beaf..000000000 --- a/rules/standard_rules/unusual_login_deprecated.py +++ /dev/null @@ -1,145 +0,0 @@ -# This rule is disabled by default because it makes API calls to a third party geolocation -# service. At high rates of log processing, the third party service may throttle requests -# unless you buy a subscription to it, which may cause this rule to no longer work. - -import json -import logging - -import panther_event_type_helpers as event_type -from panther_detection_helpers.caching import get_string_set, put_string_set -from panther_ipinfo_helpers import geoinfo_from_ip -from panther_oss_helpers import add_parse_delay - -# number of unique geolocation city:region combinations retained in the -# panther-kv-table in Dynamo to suppress alerts -GEO_HISTORY_LENGTH = 5 -GEO_INFO = {} -GEO_HISTORY = {} - - -def rule(event): - # pylint: disable=too-complex - # pylint: disable=too-many-branches - # unique key for global dictionary - log = event.get("p_row_id") - - # GEO_INFO is mocked as a string in unit tests and redeclared as a dict - global GEO_INFO # pylint: disable=global-statement - # Pre-filter to save compute time where possible. - if event.udm("event_type") != event_type.SUCCESSFUL_LOGIN: - return False - - # we use udm 'actor_user' field as a ddb and 'source_ip' in the api call - if not event.udm("actor_user") or not event.udm("source_ip"): - return False - - # Lookup geo-ip data via API call - # Mocked during unit testing - GEO_INFO[log] = geoinfo_from_ip(event=event, match_field=event.udm_path("source_ip")) - - # As of Panther 1.19, mocking returns all mocked objects in a string - # GEO_INFO must be converted back to a dict to mimic the API call - if isinstance(GEO_INFO[log], str): - GEO_INFO[log] = json.loads(GEO_INFO[log]) - - # Look up history of unique geolocations - event_key = get_key(event) - # Mocked during unit testing - previous_geo_logins = get_string_set(event_key) - - # As of Panther 1.19, mocking returns all mocked objects in a string - # previous_geo_logins must be converted back to a set to mimic the API call - if isinstance(previous_geo_logins, str): - logging.debug("previous_geo_logins is a mocked string:") - logging.debug(previous_geo_logins) - if previous_geo_logins: - previous_geo_logins = set([previous_geo_logins]) - else: - previous_geo_logins = set() - logging.debug("new type of previous_geo_logins should be 'set':") - logging.debug(type(previous_geo_logins)) - - new_login_geo = ( - f"{GEO_INFO[log].get('region', '')}" - ":" - f"{GEO_INFO[log].get('city', '')}" - ) - new_login_timestamp = event.get("p_event_time", "") - - # convert set of single string to dictionary - if previous_geo_logins: - previous_geo_logins = json.loads(previous_geo_logins.pop()) - else: - previous_geo_logins = {} - logging.debug("new type of previous_geo_logins should be 'dict':") - logging.debug(type(previous_geo_logins)) - - # don't alert if the geo is already in the history - if previous_geo_logins.get(new_login_geo): - # update timestamp of the existing geo in the history - previous_geo_logins[new_login_geo] = new_login_timestamp - - # write the dictionary of geolocs:timestamps back to Dynamo - # Mocked during unit testing - put_string_set(event_key, [json.dumps(previous_geo_logins)]) - return False - - # fire an alert when there are more unique geolocs:timestamps in the login history - # add a new geo to the dictionary - updated_geo_logins = previous_geo_logins - updated_geo_logins[new_login_geo] = new_login_timestamp - - # remove the oldest geo from the history if the updated dict exceeds the - # specified history length - if len(updated_geo_logins) > GEO_HISTORY_LENGTH: - oldest = updated_geo_logins[new_login_geo] - for geo, time in updated_geo_logins.items(): - if time < oldest: - oldest = time - oldest_login = geo - logging.debug("updated_geo_logins before removing oldest entry:") - logging.debug(updated_geo_logins) - updated_geo_logins.pop(oldest_login) - logging.debug("updated_geo_logins after removing oldest entry:") - logging.debug(updated_geo_logins) - - # Mocked during unit testing - put_string_set(event_key, [json.dumps(updated_geo_logins)]) - - global GEO_HISTORY # pylint: disable=global-statement - GEO_HISTORY[log] = updated_geo_logins - logging.debug("GEO_HISTORY in main rule:\n%s", json.dumps(GEO_HISTORY[log])) - - # Don't alert on first seen logins - if len(updated_geo_logins) <= 1: - return False - - return True - - -def get_key(event) -> str: - # Use the name to deconflict with other rules that may also use actor_user - return __name__ + ":" + str(event.udm("actor_user")) - - -def title(event): - log = event.get("p_row_id") - return ( - f"{event.get('p_log_type')}: New access location for user" - f" [{event.udm('actor_user')}]" - f" from {GEO_INFO[log].get('city')}, {GEO_INFO[log].get('region')}" - f" in {GEO_INFO[log].get('country')}" - f" (not in last [{GEO_HISTORY_LENGTH}] login locations)" - ) - - -def alert_context(event): - log = event.get("p_row_id") - context = {} - context["ip"] = event.udm("source_ip") - context["reverse_lookup"] = GEO_INFO[log].get("hostname", "No reverse lookup hostname") - context["ip_org"] = GEO_INFO[log].get("org", "No organization listed") - if GEO_HISTORY[log]: - context["geoHistory"] = f"{json.dumps(GEO_HISTORY[log])}" - context = add_parse_delay(event, context) - return context diff --git a/rules/standard_rules/unusual_login_deprecated.yml b/rules/standard_rules/unusual_login_deprecated.yml deleted file mode 100644 index 27be950e9..000000000 --- a/rules/standard_rules/unusual_login_deprecated.yml +++ /dev/null @@ -1,512 +0,0 @@ -AnalysisType: rule -Filename: unusual_login_deprecated.py -RuleID: "Standard.UnusualLogin" -DisplayName: "--DEPRECATED-- Unusual Login" -# This rule is disabled by default because it makes API calls to a third party geolocation -# service. At high rates of log processing, the third party service may throttle requests -# unless you buy a subscription to it, which may cause this rule to no longer work. -Enabled: false -LogTypes: - - Asana.Audit - - Atlassian.Audit - - AWS.CloudTrail - - GSuite.Reports - - Okta.SystemLog - - OneLogin.Events - - Zendesk.Audit - - Zoom.Activity - - OnePassword.SignInAttempt -Tags: - - DataModel - - Identity & Access Management - - Initial Access:Valid Accounts -Reports: - MITRE ATT&CK: - - TA0001:T1078 -Severity: Medium -Description: A user logged in from a new geolocation. -Runbook: > - Reach out to the user to ensure the login was legitimate. Be sure to use a means outside the one the unusual login originated from, if one is available. CC an individual that works with the user for visibility, usually the user’s manager if they’re available. The second user is not expected to respond, unless they find the response unusual or the location unexpected. - - To reduce noise, geolocation history length can be configured in the rule body to increase the number of allowed locations per user. -Reference: https://d3fend.mitre.org/technique/d3f:UserGeolocationLogonPatternAnalysis/ -SummaryAttributes: - - p_any_ip_addresses -Tests: - - Name: AWS.CloudTrail - Successful Login - New Geo - Exceeds History Length of 5 - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCityNew", - "country": "UnitTestCountry", - "hostname": "somedomain.com", - "org": "Some Org" - } - - objectName: get_string_set - returnValue: >- - { - "UnitTestRegion:UnitTestCity1": "2021-06-04 09:59:53.650801", - "UnitTestRegion:UnitTestCity2": "2021-06-04 09:59:53.650802", - "UnitTestRegion:UnitTestCity3": "2021-06-04 09:59:53.650803", - "UnitTestRegion:UnitTestCity4": "2021-06-04 09:59:53.650804", - "UnitTestRegion:UnitTestCity5": "2021-06-04 09:59:53.650805" - } - - objectName: put_string_set - returnValue: >- - Log: - { - "userIdentity": { "type": "IAMUser", "userName": "some_user" }, - "eventName": "ConsoleLogin", - "sourceIPAddress": "111.111.111.111", - "responseElements": { "ConsoleLogin": "Success" }, - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "AWS.CloudTrail", - } - - Name: AWS.CloudTrail - Successful Login - New Geo - Does Not Exceed History Length of 5 - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCityNew", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - {"UnitTestRegion:UnitTestCity": "2021-06-04 09:59:53.650801"} - - objectName: put_string_set - returnValue: >- - Log: - { - "userIdentity": { "type": "IAMUser", "userName": "some_user" }, - "eventName": "ConsoleLogin", - "sourceIPAddress": "111.111.111.111", - "responseElements": { "ConsoleLogin": "Success" }, - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "AWS.CloudTrail", - } - - Name: AWS.CloudTrail - Successful Login - New Geo - No History - ExpectedResult: false - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCityNew", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - - objectName: put_string_set - returnValue: >- - Log: - { - "userIdentity": { "type": "IAMUser", "userName": "some_user" }, - "eventName": "ConsoleLogin", - "sourceIPAddress": "111.111.111.111", - "responseElements": { "ConsoleLogin": "Success" }, - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "AWS.CloudTrail", - } - - Name: AWS.CloudTrail - Successful Login - New Geo - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCityNew", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - {"UnitTestRegion:UnitTestCity": "2021-06-04 09:59:53.650801"} - - objectName: put_string_set - returnValue: >- - Log: - { - "userIdentity": { "type": "IAMUser", "userName": "some_user" }, - "eventName": "ConsoleLogin", - "sourceIPAddress": "111.111.111.111", - "responseElements": { "ConsoleLogin": "Success" }, - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "AWS.CloudTrail", - } - - - Name: AWS.CloudTrail - Successful Login - Existing Geo - ExpectedResult: false - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - { - "UnitTestRegion:UnitTestCity1": "2021-06-04 09:59:53.650801", - "UnitTestRegion:UnitTestCity2": "2021-06-04 09:59:53.650802", - "UnitTestRegion:UnitTestCity3": "2021-06-04 09:59:53.650803", - "UnitTestRegion:UnitTestCity4": "2021-06-04 09:59:53.650804", - "UnitTestRegion:UnitTestCity5": "2021-06-04 09:59:53.650805" - } - - objectName: put_string_set - returnValue: >- - Log: - { - "userIdentity": { "type": "IAMUser", "userName": "some_user" }, - "eventName": "ConsoleLogin", - "sourceIPAddress": "111.111.111.111", - "responseElements": { "ConsoleLogin": "Success" }, - "p_log_type": "AWS.CloudTrail", - } - - Name: AWS.CloudTrail - Failed Login - ExpectedResult: false - Log: - { - "userIdentity": - { - "type": "IAMUser", - "principalId": "1111", - "arn": "arn:aws:iam::123456789012:user/tester", - "accountId": "123456789012", - "userName": "tester", - }, - "eventTime": "2019-01-01T00:00:00Z", - "eventSource": "signin.amazonaws.com", - "eventName": "ConsoleLogin", - "awsRegion": "us-east-1", - "sourceIPAddress": "111.111.111.111", - "userAgent": "Mozilla", - "requestParameters": null, - "responseElements": { "ConsoleLogin": "Failure" }, - "additionalEventData": - { - "LoginTo": "https://console.aws.amazon.com/console/", - "MobileVersion": "No", - "MFAUsed": "No", - }, - "eventID": "1", - "eventType": "AwsConsoleSignIn", - "recipientAccountId": "123456789012", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "AWS.CloudTrail", - } - - Name: GSuite - New Geo - No History - ExpectedResult: false - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - - objectName: put_string_set - returnValue: >- - Log: - { - "actor": - { "email": "nick@acme.io", "profileId": "11949494222400014922" }, - "id": { "applicationName": "login" }, - "ipAddress": "111.111.111.111", - "events": [{ "type": "login", "name": "login_success" }], - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "GSuite.Reports", - } - - Name: GSuite - New Geo - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - {"UnitTestRegion:UnitTestCity": "2021-06-04 09:59:53.650801"} - - objectName: put_string_set - returnValue: >- - Log: - { - "actor": - { "email": "nick@acme.io", "profileId": "11949494222400014922" }, - "id": { "applicationName": "login" }, - "ipAddress": "111.111.111.111", - "events": [{ "type": "login", "name": "login_success" }], - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - "p_log_type": "GSuite.Reports", - } - - Name: Okta - Non Login - ExpectedResult: false - Log: { "eventType": "logout", "p_log_type": "Okta.SystemLog" } - - Name: Okta - Failed Login - ExpectedResult: false - Log: - { - "actor": - { - "alternateId": "admin", - "displayName": "unknown", - "id": "unknown", - "type": "User", - }, - "client": { "ipAddress": "redacted" }, - "eventType": "user.session.start", - "outcome": { "reason": "VERIFICATION_ERROR", "result": "FAILURE" }, - "p_log_type": "Okta.SystemLog", - } - - Name: OneLogin - Non Login - ExpectedResult: false - Log: { "event_type_id": 8, "p_log_type": "OneLogin.Events" } - - Name: Zendesk - Successful Login - No History - ExpectedResult: false - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - - objectName: put_string_set - returnValue: >- - Log: - { - "url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json", - "id": 123456789123, - "action_label": "Sign in", - "actor_id": 123, - "actor_name": "Bob Cat", - "source_id": 123, - "source_type": "user", - "source_label": "Bob Cat", - "action": "login", - "change_description": "Successful sign-in using Zendesk password from https://myzendesk.zendesk.com/access/login", - "ip_address": "127.0.0.1", - "created_at": "2021-05-28T18:39:50Z", - "p_log_type": "Zendesk.Audit", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } - - Name: Zendesk - Successful Login - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - {"UnitTestRegion:UnitTestCity": "2021-06-04 09:59:53.650801"} - - objectName: put_string_set - returnValue: >- - Log: - { - "url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json", - "id": 123456789123, - "action_label": "Sign in", - "actor_id": 123, - "actor_name": "Bob Cat", - "source_id": 123, - "source_type": "user", - "source_label": "Bob Cat", - "action": "login", - "change_description": "Successful sign-in using Zendesk password from https://myzendesk.zendesk.com/access/login", - "ip_address": "127.0.0.1", - "created_at": "2021-05-28T18:39:50Z", - "p_log_type": "Zendesk.Audit", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } - - Name: GSuite - Successful Login Event - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - {"UnitTestRegion:UnitTestCity": "2021-06-04 09:59:53.650801"} - - objectName: put_string_set - returnValue: >- - Log: - { - "actor": - { "email": "nick@acme.io", "profileId": "11949494222400014922" }, - "id": { "applicationName": "login" }, - "ipAddress": "127.0.0.1", - "events": [{ "type": "login", "name": "login_success" }], - "p_log_type": "GSuite.Reports", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } - - Name: Zoom - Successful Login Event - No History - ExpectedResult: false - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - - objectName: put_string_set - returnValue: >- - Log: - { - "email": "homer.simpson@example.io", - "time": "2021-10-22 10:39:04Z", - "type": "Sign in", - "ip_address": "1.1.1.1", - "client_type": "Browser", - "p_log_type": "Zoom.Activity", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } - - Name: Zoom - Successful Login Event - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "region": "UnitTestRegion", - "city": "UnitTestCity1", - "country": "UnitTestCountry" - } - - objectName: get_string_set - returnValue: >- - {"UnitTestRegion:UnitTestCity": "2021-06-04 09:59:53.650801"} - - objectName: put_string_set - returnValue: >- - Log: - { - "email": "homer.simpson@example.io", - "time": "2021-10-22 10:39:04Z", - "type": "Sign in", - "ip_address": "1.1.1.1", - "client_type": "Browser", - "p_log_type": "Zoom.Activity", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } - - Name: 1Password - Regular Login - ExpectedResult: true - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "ip": "111.111.111.111", - "region": "UnitTestRegion", - "city": "UnitTestCityNew", - "country": "UnitTestCountry", - "hostname": "somedomain.com", - "org": "Some Org" - } - - objectName: get_string_set - returnValue: >- - { - "UnitTestRegion:UnitTestCity1": "2021-06-04 09:59:53.650801", - "UnitTestRegion:UnitTestCity2": "2021-06-04 09:59:53.650802", - "UnitTestRegion:UnitTestCity3": "2021-06-04 09:59:53.650803", - "UnitTestRegion:UnitTestCity4": "2021-06-04 09:59:53.650804", - "UnitTestRegion:UnitTestCity5": "2021-06-04 09:59:53.650805" - } - - objectName: put_string_set - returnValue: >- - Log: - { - "uuid": "1234", - "session_uuid": "5678", - "timestamp": "2021-12-03 19:52:52", - "category": "success", - "type": "credentials_ok", - "country": "US", - "target_user": - { - "email": "homer@springfield.gov", - "name": "Homer Simpson", - "uuid": "1234", - }, - "client": - { - "app_name": "1Password Browser Extension", - "app_version": "20184", - "ip_address": "1.1.1.1", - "os_name": "Solaris", - "os_version": "10", - "platform_name": "Chrome", - "platform_version": "96.0.4664.55", - }, - "p_log_type": "OnePassword.SignInAttempt", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } - - Name: 1Password - Failed Login - ExpectedResult: false - Mocks: - - objectName: geoinfo_from_ip - returnValue: >- - { - "ip": "111.111.111.111", - "region": "UnitTestRegion", - "city": "UnitTestCityNew", - "country": "UnitTestCountry", - "hostname": "somedomain.com", - "org": "Some Org" - } - Log: - { - "uuid": "1234", - "session_uuid": "5678", - "timestamp": "2021-12-03 19:52:52", - "category": "credentials_failed", - "type": "password_secret_bad", - "country": "US", - "target_user": - { - "email": "homer@springfield.gov", - "name": "Homer Simpson", - "uuid": "1234", - }, - "client": - { - "app_name": "1Password Browser Extension", - "app_version": "20184", - "ip_address": "111.111.111.111", - "os_name": "Solaris", - "os_version": "10", - "platform_name": "Chrome", - "platform_version": "96.0.4664.55", - }, - "p_log_type": "OnePassword.SignInAttempt", - "p_parse_time": "2021-06-04 10:02:33.650807", - "p_event_time": "2021-06-04 09:59:53.650807", - } diff --git a/rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.py b/rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.py deleted file mode 100644 index d32b5bef1..000000000 --- a/rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.py +++ /dev/null @@ -1,16 +0,0 @@ -from panther_zoom_helpers import get_zoom_user_context as get_context - - -def rule(event): - if event.get("Action") != "Update" or event.get("category_type") != "User": - return False - - context = get_context(event) - - return "Member to Admin" in context["Change"] - - -def title(event): - context = get_context(event) - - return f"Zoom User {context['User']} was made an admin by {event.get('operator')}" diff --git a/rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.yml b/rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.yml deleted file mode 100644 index dc0905ec5..000000000 --- a/rules/zoom_operation_rules/zoom_operation_user_granted_admin_deprecated.yml +++ /dev/null @@ -1,40 +0,0 @@ -AnalysisType: rule -Filename: zoom_operation_user_granted_admin_deprecated.py -RuleID: "Zoom.UserGrantedAdmin" -DisplayName: "--DEPRECATED -- Zoom User Granted Admin Rights" -Enabled: false -LogTypes: - - Zoom.Operation -Tags: - - Zoom - - Privilege Escalation:Valid Accounts -Severity: Medium -Description: > - A Zoom user has been granted admin access -Reports: - MITRE ATT&CK: - - TA0004:T1078 -Reference: https://support.zoom.us/hc/en-us/articles/115001078646-Using-role-management -Runbook: > - Contact Zoom admin and ensure this access level is intended and appropriate -SummaryAttributes: - - p_any_emails -Tests: - - Name: User Granted Admin - ExpectedResult: True - Log: - { - "operator": "homer@panther.io", - "category_type": "User", - "action": "Update", - "operation_detail": "Update User bart@panther.io - User Role: from Member to Admin", - } - - Name: Non-admin user update - ExpectedResult: False - Log: - { - "operator": "homer@panther.io", - "category_type": "User", - "action": "Update", - "operation_detail": "Update User lisa@panther.io - Job Title: set to Contractor", - } From 6de8c9f9daa333750851dc6cd0129faddf710f9f Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Thu, 26 Sep 2024 13:00:29 -0500 Subject: [PATCH 02/13] rename deprecated text file --- decomissioned_rules.txt => deprecated.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename decomissioned_rules.txt => deprecated.txt (100%) diff --git a/decomissioned_rules.txt b/deprecated.txt similarity index 100% rename from decomissioned_rules.txt rename to deprecated.txt From 05fb313bb46e42f8b84a5bec48588987208a36b0 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 10:09:56 -0500 Subject: [PATCH 03/13] added script for performing deletion --- .scripts/deleted_rules.py | 85 +++++++++++++++++++++++++++++++++++++++ Makefile | 3 ++ 2 files changed, 88 insertions(+) create mode 100644 .scripts/deleted_rules.py diff --git a/.scripts/deleted_rules.py b/.scripts/deleted_rules.py new file mode 100644 index 000000000..895994a2d --- /dev/null +++ b/.scripts/deleted_rules.py @@ -0,0 +1,85 @@ +""" Checks to see if an Analysis item was removed from the repo, and whether it was added to the +deprecated.txt file. """ + +import argparse +import os +import re +import subprocess + +diff_pattern = re.compile(r'^-(?:RuleID|PolicyID|QueryName):\s*"?([\w.]+)"?') + + +def get_deleted_ids() -> set[str]: + # Run git diff, get output + result = subprocess.run(['git', 'diff', 'origin/release', 'HEAD'], capture_output=True) + if result.stderr: + raise Exception(result.stderr.decode("utf-8")) + + ids = set() + for line in result.stdout.decode("utf-8").split("\n"): + if m := diff_pattern.match(line): + # Add the ID to the list + ids.add(m.group(1)) + + return ids + + +def get_deprecated_ids() -> set[str]: + """ Returns all the IDs listed in `deprecated.txt`. """ + with open("deprecated.txt", "r") as f: + return set(f.read().split("\n")) + + +def check(_): + if ids := get_deleted_ids() - get_deprecated_ids(): + print("❌ The following rule IDs may have been deleted:") + for id_ in ids: + print(f"\t{id_}") + exit(1) + else: + print("✅ No unaccounted deletions found! You're in the clear! 👍") + +def remove(args): + api_token = args.api_token or os.environ.get("PANTHER_API_TOKEN") + api_host = args.api_host or os.environ.get("PANTHER_API_HOST") + + if not (api_token and api_host): + if not api_token: + print("No API token was found or provided!") + if not api_host: + print("No API host was found or provided!") + print("You can pass these values using --api-token or --api-host in your command.") + exit(1) + + ids = list(get_deprecated_ids()) + cmd = [ + "pipenv", "run", "panther_analysis_tool", "delete", "--no-confirm", "--api-token", api_token, "--api-host", api_host, "--analysis-id" + ] + + result = subprocess.run(cmd+ids, capture_output=True) + if result.stderr: + raise Exception(result.stderr.decode("utf-8")) + + print(result.stdout.decode("utf-8")) + + +def main(): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(title="subcommands") + + check_help = "Check if any items have been removed and not added to deprecated.txt" + parser_check = subparsers.add_parser("check", help=check_help) + parser_check.set_defaults(func=check) + + remove_help = "Delete the entires listed in deprecated.txt" + parser_remove = subparsers.add_parser("remove", help=remove_help) + parser_remove.add_argument("--api-token", type=str, required=False) + parser_remove.add_argument("--api-host", type=str, required=False) + parser_remove.set_defaults(func=remove) + + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/Makefile b/Makefile index 08377a946..38501322c 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,9 @@ install: test: global-helpers-unit-test pipenv run panther_analysis_tool test $(TEST_ARGS) +check-deprecated: + pipenv run python3 ./.scripts/deleted_rules.py check + docker-build: docker build -t panther-analysis:latest . From 977ccdefa6f1ed6de3915c5a0d9cd140c0b3380d Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 14:24:03 -0500 Subject: [PATCH 04/13] update script to not invoke shell directly for PAT --- .scripts/deleted_rules.py | 40 +++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/.scripts/deleted_rules.py b/.scripts/deleted_rules.py index 895994a2d..0edabe635 100644 --- a/.scripts/deleted_rules.py +++ b/.scripts/deleted_rules.py @@ -2,10 +2,14 @@ deprecated.txt file. """ import argparse +import logging import os import re import subprocess +import panther_analysis_tool.command.bulk_delete as pat_delete +import panther_analysis_tool.util as pat_util + diff_pattern = re.compile(r'^-(?:RuleID|PolicyID|QueryName):\s*"?([\w.]+)"?') @@ -44,23 +48,39 @@ def remove(args): api_host = args.api_host or os.environ.get("PANTHER_API_HOST") if not (api_token and api_host): + opts = [] if not api_token: print("No API token was found or provided!") + opts.append("--api-token") if not api_host: print("No API host was found or provided!") - print("You can pass these values using --api-token or --api-host in your command.") + opts.append("--api-host") + print(f"You can pass API credentials using {' and '.join(opts)} in your command.") exit(1) ids = list(get_deprecated_ids()) - cmd = [ - "pipenv", "run", "panther_analysis_tool", "delete", "--no-confirm", "--api-token", api_token, "--api-host", api_host, "--analysis-id" - ] - - result = subprocess.run(cmd+ids, capture_output=True) - if result.stderr: - raise Exception(result.stderr.decode("utf-8")) - - print(result.stdout.decode("utf-8")) + + pat_args = argparse.Namespace( + analysis_id = ids, + query_id = [], + confirm_bypass = True, + api_token = api_token, + api_host = api_host + ) + + logging.basicConfig( + format="[%(levelname)s][%(name)s]: %(message)s", + level=logging.INFO, + ) + + return_code, out = pat_util.func_with_api_backend(pat_delete.run)(pat_args) + + if return_code == 1: + if out: + logging.error(out) + elif return_code == 0: + if out: + logging.info(out) def main(): From 046523cca654e0bbc4049f2938ed1676f0f6bc77 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 14:31:13 -0500 Subject: [PATCH 05/13] add gh action to detect removed rules --- .github/workflows/check-deprecated.yml | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/check-deprecated.yml diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml new file mode 100644 index 000000000..54aaf6c62 --- /dev/null +++ b/.github/workflows/check-deprecated.yml @@ -0,0 +1,37 @@ +on: + pull_request: + +permissions: + contents: read + +jobs: + check_removed_rules: + name: Check Removed Rules + runs-on: ubuntu-latest + + steps: + - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + disable-sudo: true + egress-policy: block + allowed-endpoints: > + github.com:443 + pypi.org:443 + - name: Checkout panther-analysis + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + + - name: Set python version + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 + with: + python-version: "3.11" + + - name: Install pipenv + run: pip install pipenv + + - name: Setup venv + run: make venv + + - name: Check for Removed Rules + run: | + pipenv run make check-deprecated + \ No newline at end of file From 582b3c84524839265c6f914cc8b75ebd054f5538 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:17:32 -0500 Subject: [PATCH 06/13] fixed check-deprecated workflow? --- .github/workflows/check-deprecated.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index 54aaf6c62..e304e9181 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -15,6 +15,7 @@ jobs: disable-sudo: true egress-policy: block allowed-endpoints: > + files.pythonhosted.org:443 github.com:443 pypi.org:443 - name: Checkout panther-analysis From 5977a92b734a00b391c6a46cfea2b0a9893e5f04 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:29:03 -0500 Subject: [PATCH 07/13] adjust fetch depth in workflow --- .github/workflows/check-deprecated.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index e304e9181..33e58aff7 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -20,6 +20,8 @@ jobs: pypi.org:443 - name: Checkout panther-analysis uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + with: + fetch-depth: 0 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 From b5807c3b2202fec3cdaed3e096f5c6c024edd704 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:37:27 -0500 Subject: [PATCH 08/13] remove workflow - will add as separate PR --- .github/workflows/check-deprecated.yml | 40 -------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/check-deprecated.yml diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml deleted file mode 100644 index 33e58aff7..000000000 --- a/.github/workflows/check-deprecated.yml +++ /dev/null @@ -1,40 +0,0 @@ -on: - pull_request: - -permissions: - contents: read - -jobs: - check_removed_rules: - name: Check Removed Rules - runs-on: ubuntu-latest - - steps: - - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 - with: - disable-sudo: true - egress-policy: block - allowed-endpoints: > - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 - - name: Checkout panther-analysis - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 - with: - fetch-depth: 0 - - - name: Set python version - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 - with: - python-version: "3.11" - - - name: Install pipenv - run: pip install pipenv - - - name: Setup venv - run: make venv - - - name: Check for Removed Rules - run: | - pipenv run make check-deprecated - \ No newline at end of file From cfe55bbbf96059c6ac5a8b3c160f2bbc5a564984 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:39:07 -0500 Subject: [PATCH 09/13] create workflow --- .github/workflows/check-deprecated.yml | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/check-deprecated.yml diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml new file mode 100644 index 000000000..779544eec --- /dev/null +++ b/.github/workflows/check-deprecated.yml @@ -0,0 +1,42 @@ +on: + pull_request: + +permissions: + contents: read + +jobs: + check_removed_rules: + name: Check Removed Rules + runs-on: ubuntu-latest + + steps: + - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + disable-sudo: true + egress-policy: block + allowed-endpoints: > + files.pythonhosted.org:443 + github.com:443 + pypi.org:443 + - name: Checkout panther-analysis + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + + - name: Fetch Re;ease + run: | + git fetch --depth=1 origin release + + - name: Set python version + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 + with: + python-version: "3.11" + + - name: Install pipenv + run: pip install pipenv + + - name: Setup venv + run: make venv + + - name: Check for Removed Rules + run: | + pipenv run make check-deprecated + \ No newline at end of file From ec7fceaeea06901277f1348a553bb8c23928338c Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:42:38 -0500 Subject: [PATCH 10/13] add trigger --- .github/workflows/check-deprecated.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index 779544eec..0cc25b0c8 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -1,4 +1,5 @@ on: + workflow_dispatch: pull_request: permissions: From cc3032dcae73bbcaadd9eeda01aee6890dc15370 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:44:50 -0500 Subject: [PATCH 11/13] make it only workflow dispatch and see if that works --- .github/workflows/check-deprecated.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index 0cc25b0c8..8a26934f3 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -1,6 +1,6 @@ on: workflow_dispatch: - pull_request: + # pull_request: permissions: contents: read From 5b9b91097643ca93766c755649e1ed038f589af6 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Fri, 27 Sep 2024 15:49:54 -0500 Subject: [PATCH 12/13] revert trigger to PR only --- .github/workflows/check-deprecated.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index 8a26934f3..779544eec 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -1,6 +1,5 @@ on: - workflow_dispatch: - # pull_request: + pull_request: permissions: contents: read From 27ad9f8ffc9a5c7cc32b3724081f64623daf3eb9 Mon Sep 17 00:00:00 2001 From: Ben Airey Date: Tue, 1 Oct 2024 11:53:03 -0500 Subject: [PATCH 13/13] add make command to remove deprecated rules --- .github/workflows/check-deprecated.yml | 2 +- Makefile | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index 779544eec..c34c2555e 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout panther-analysis uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 - - name: Fetch Re;ease + - name: Fetch Release run: | git fetch --depth=1 origin release diff --git a/Makefile b/Makefile index 38501322c..37f486500 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,9 @@ test: global-helpers-unit-test check-deprecated: pipenv run python3 ./.scripts/deleted_rules.py check +remove-deprecated: + pipenv run python3 ./.scripts/deleted_rules.py remove + docker-build: docker build -t panther-analysis:latest .